Coverage for o2/actions/legacy_optimos_actions/modify_calendar_by_cost_action.py: 97%

36 statements  

« prev     ^ index     » next       coverage.py v7.6.12, created at 2025-05-16 11:18 +0000

1from dataclasses import dataclass 

2 

3from o2.actions.base_actions.base_action import ( 

4 RateSelfReturnType, 

5) 

6from o2.actions.base_actions.modify_calendar_base_action import ( 

7 ModifyCalendarBaseAction, 

8 ModifyCalendarBaseActionParamsType, 

9) 

10from o2.models.days import DAYS 

11from o2.models.solution import Solution 

12from o2.models.timetable.time_period import TimePeriod 

13from o2.store import Store 

14 

15 

16class ModifyCalendarByCostActionParamsType(ModifyCalendarBaseActionParamsType): 

17 """Parameter for `ModifyCalendarByCostAction`.""" 

18 

19 

20@dataclass(frozen=True) 

21class ModifyCalendarByCostAction(ModifyCalendarBaseAction, str=False): 

22 """`ModifyCalendarByCostAction` will modify the resource calendars based on wt. 

23 

24 This action is based on the original optimos implementation, 

25 see `solution_traces_optimize_cost` in that project. 

26 

27 It will fist iterate over all resources sorted by their 

28 cost (cost/hour * available_time), for each resource it will iterate over 

29 each day in their calendar. For each day it will try to modify the 

30 first period (shift), by either removing it (if it's only 1 hour long), 

31 shrinking it from start & end, shrinking it from the start, 

32 or finally shrinking it from the end. 

33 """ 

34 

35 @staticmethod 

36 def rate_self(store: Store, input: "Solution") -> RateSelfReturnType: 

37 """Generate a best set of parameters & self-evaluates this action.""" 

38 resources = store.solution.state.timetable.get_resources_with_cost() 

39 for resource, _cost in resources: 

40 calendar = store.current_timetable.get_calendar(resource.calendar) 

41 if calendar is None: 

42 continue 

43 

44 for day in DAYS: 

45 periods = calendar.get_periods_containing_day(day) 

46 for period in periods: 

47 period_id = period.id 

48 # We need to fix the day period to not change 

49 # change the times of other days 

50 fixed_day_period = TimePeriod( 

51 from_=day, 

52 to=day, 

53 begin_time=period.begin_time, 

54 end_time=period.end_time, 

55 probability=period.probability, 

56 ) 

57 

58 # Try to remove the period if it's only 1 hour long 

59 if fixed_day_period.duration == 1: 

60 yield ( 

61 ModifyCalendarByCostAction.get_default_rating(store), 

62 ModifyCalendarByCostAction( 

63 params=ModifyCalendarByCostActionParamsType( 

64 calendar_id=calendar.id, 

65 period_id=period_id, 

66 day=day, 

67 remove_period=True, 

68 ) 

69 ), 

70 ) 

71 # Try to shrink the period from start & end 

72 new_period = fixed_day_period.add_hours_after(-1) 

73 if new_period is not None: 

74 new_period = new_period.add_hours_before(-1) 

75 if new_period is not None: 

76 yield ( 

77 ModifyCalendarByCostAction.get_default_rating(store), 

78 ModifyCalendarByCostAction( 

79 params=ModifyCalendarByCostActionParamsType( 

80 calendar_id=calendar.id, 

81 period_id=period_id, 

82 day=day, 

83 add_hours_before=-1, 

84 add_hours_after=-1, 

85 ) 

86 ), 

87 ) 

88 # Try to shrink the period from start 

89 new_period = fixed_day_period.add_hours_before(-1) 

90 if new_period is not None: 

91 yield ( 

92 ModifyCalendarByCostAction.get_default_rating(store), 

93 ModifyCalendarByCostAction( 

94 params=ModifyCalendarByCostActionParamsType( 

95 calendar_id=calendar.id, 

96 period_id=period_id, 

97 day=day, 

98 add_hours_before=-1, 

99 ) 

100 ), 

101 ) 

102 # Try to shrink the period from end 

103 new_period = fixed_day_period.add_hours_after(-1) 

104 if new_period is not None: 

105 yield ( 

106 ModifyCalendarByCostAction.get_default_rating(store), 

107 ModifyCalendarByCostAction( 

108 params=ModifyCalendarByCostActionParamsType( 

109 calendar_id=calendar.id, 

110 period_id=period_id, 

111 day=day, 

112 add_hours_after=-1, 

113 ) 

114 ), 

115 ) 

116 

117 return