Coverage for o2/actions/batching_actions/modify_size_rule_by_cost_fn_action.py: 50%
145 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-16 11:18 +0000
« prev ^ index » next coverage.py v7.6.12, created at 2025-05-16 11:18 +0000
1from collections import defaultdict
2from collections.abc import Mapping
4from o2.actions.base_actions.add_size_rule_base_action import (
5 AddSizeRuleAction,
6 AddSizeRuleBaseActionParamsType,
7)
8from o2.actions.base_actions.base_action import (
9 RateSelfReturnType,
10)
11from o2.actions.base_actions.modify_size_rule_base_action import (
12 ModifySizeRuleBaseAction,
13 ModifySizeRuleBaseActionParamsType,
14)
15from o2.models.rule_selector import RuleSelector
16from o2.models.self_rating import RATING
17from o2.models.solution import Solution
18from o2.models.timetable import RULE_TYPE
19from o2.store import Store
20from o2.util.helper import select_variants
23class ModifySizeRuleByCostFnActionParamsType(ModifySizeRuleBaseActionParamsType):
24 """Parameter for ModifySizeRuleByCostFn."""
26 pass
29def rate_self_helper_by_metric_dict(
30 store: "Store",
31 task_id_metric_dict: Mapping[str, float],
32 task_class: type[ModifySizeRuleBaseAction],
33) -> RateSelfReturnType:
34 """Rate ModifySizeRuleByCostFn actions."""
35 timetable = store.current_timetable
36 constraints = store.constraints
37 state = store.current_state
39 sorted_metric_values = sorted(set(task_id_metric_dict.values()), reverse=True)
41 for metric in sorted_metric_values:
42 task_ids = [task_id for task_id, value in task_id_metric_dict.items() if value == metric]
43 for task_id in select_variants(store, task_ids):
44 firing_rule_selectors = timetable.get_firing_rule_selectors_for_task(
45 task_id, rule_type=RULE_TYPE.SIZE
46 )
47 size_constraints = constraints.get_batching_size_rule_constraints(task_id)
48 if not size_constraints:
49 continue
50 size_constraint = size_constraints[0]
52 # In case we do not have firing_rule selectors, we might want to add a new rule
53 if not firing_rule_selectors:
54 new_cost = size_constraint.cost_fn_lambda(2)
55 old_cost = size_constraint.cost_fn_lambda(1)
56 if new_cost <= old_cost:
57 duration_fn = store.constraints.get_duration_fn_for_task(task_id)
58 yield (
59 RATING.LOW,
60 AddSizeRuleAction(
61 AddSizeRuleBaseActionParamsType(task_id=task_id, size=2, duration_fn=duration_fn)
62 ),
63 )
65 for firing_rule_selector in select_variants(store, firing_rule_selectors, inner=True):
66 firing_rule = firing_rule_selector.get_firing_rule_from_state(state)
67 if firing_rule is None:
68 continue
69 size = firing_rule.value
71 new_cost = size_constraint.cost_fn_lambda(size + 1)
72 old_cost = size_constraint.cost_fn_lambda(size)
73 if new_cost <= old_cost:
74 yield (
75 ModifySizeRuleBaseAction.get_default_rating(),
76 task_class(
77 ModifySizeRuleByCostFnActionParamsType(
78 rule=firing_rule_selector,
79 size_increment=1,
80 duration_fn=size_constraint.duration_fn,
81 )
82 ),
83 )
86class ModifySizeRuleByCostFnRepetitiveTasksAction(ModifySizeRuleBaseAction):
87 """An Action to modify size batching rules based on the cost fn.
89 If batch size increment reduces Costs => Increase Batch Size
90 - For repetitive tasks (higher frequencies first)
91 """
93 params: ModifySizeRuleByCostFnActionParamsType
95 @staticmethod
96 def rate_self(
97 store: "Store", input: "Solution"
98 ) -> RateSelfReturnType["ModifySizeRuleByCostFnRepetitiveTasksAction"]:
99 """Generate a best set of parameters & self-evaluates this action."""
100 evaluation = input.evaluation
102 task_frequencies = evaluation.task_execution_counts
103 yield from rate_self_helper_by_metric_dict(
104 store, task_frequencies, ModifySizeRuleByCostFnRepetitiveTasksAction
105 )
108class ModifySizeRuleByCostFnHighCostsAction(ModifySizeRuleBaseAction):
109 """An Action to modify size batching rules based on the cost fn.
111 If batch size increment reduces Costs => Increase Batch Size
112 - For tasks with high costs (higher costs first).
113 """
115 params: ModifySizeRuleByCostFnActionParamsType
117 @staticmethod
118 def rate_self(
119 store: "Store", input: "Solution"
120 ) -> RateSelfReturnType["ModifySizeRuleByCostFnHighCostsAction"]:
121 """Generate a best set of parameters & self-evaluates this action."""
122 evaluation = input.evaluation
124 task_costs = evaluation.get_total_cost_per_task()
125 yield from rate_self_helper_by_metric_dict(store, task_costs, ModifySizeRuleByCostFnHighCostsAction)
128class ModifySizeRuleByCostFnLowProcessingTimeAction(ModifySizeRuleBaseAction):
129 """An Action to modify size batching rules based on the cost fn.
131 If batch size increment reduces processing time => Increase Batch Size
132 - For tasks with low processing time (lower processing time first)
133 """
135 params: ModifySizeRuleByCostFnActionParamsType
137 @staticmethod
138 def rate_self(
139 store: "Store", input: "Solution"
140 ) -> RateSelfReturnType["ModifySizeRuleByCostFnLowProcessingTimeAction"]:
141 """Generate a best set of parameters & self-evaluates this action."""
142 evaluation = input.evaluation
144 task_processing_times = evaluation.get_average_processing_time_per_task()
145 yield from rate_self_helper_by_metric_dict(
146 store, task_processing_times, ModifySizeRuleByCostFnLowProcessingTimeAction
147 )
150class ModifyBatchSizeIfNoCostImprovementAction(ModifySizeRuleBaseAction):
151 """An Action to modify size batching rules based on the cost fn.
153 If batch size decrement does not increase Costs (looking at the cost fn) => Decrease Batch Size
154 """
156 params: ModifySizeRuleByCostFnActionParamsType
158 @staticmethod
159 def rate_self(
160 store: "Store", input: "Solution"
161 ) -> RateSelfReturnType["ModifyBatchSizeIfNoCostImprovementAction | AddSizeRuleAction"]:
162 """Generate a best set of parameters & self-evaluates this action."""
163 constraints = store.constraints
164 timetable = input.state.timetable
165 state = input.state
167 task_ids = timetable.get_task_ids()
168 rule_selectors_by_cost: dict[float, list[RuleSelector]] = defaultdict(list)
170 for task_id in task_ids:
171 size_constraints = constraints.get_batching_size_rule_constraints(task_id)
172 if not size_constraints:
173 continue
174 size_constraint = size_constraints[0]
176 firing_rule_selectors = timetable.get_firing_rule_selectors_for_task(
177 task_id, rule_type=RULE_TYPE.SIZE
178 )
180 # Because we are aiming to reduce batch size, we do NOT add a new rule here
181 if not firing_rule_selectors:
182 continue
184 for firing_rule_selector in firing_rule_selectors:
185 firing_rule = firing_rule_selector.get_firing_rule_from_state(state)
186 if firing_rule is None:
187 continue
188 size = firing_rule.value
190 new_cost = size_constraint.cost_fn_lambda(size - 1)
191 old_cost = size_constraint.cost_fn_lambda(size)
192 if new_cost <= old_cost:
193 cost = old_cost - new_cost
194 rule_selectors_by_cost[cost].append(firing_rule_selector)
196 costs_sorted = sorted(rule_selectors_by_cost.keys(), reverse=True)
198 for cost in costs_sorted:
199 rule_selectors = rule_selectors_by_cost[cost]
200 for rule_selector in select_variants(store, rule_selectors):
201 duration_fn = constraints.get_duration_fn_for_task(rule_selector.batching_rule_task_id)
202 yield (
203 RATING.LOW,
204 ModifyBatchSizeIfNoCostImprovementAction(
205 ModifySizeRuleByCostFnActionParamsType(
206 rule=rule_selector,
207 size_increment=-1,
208 duration_fn=duration_fn,
209 )
210 ),
211 )
214class ModifySizeRuleByCostFnLowCycleTimeImpactAction(ModifySizeRuleBaseAction):
215 """An Action to modify size batching rules based on the cost fn.
217 If batch size increment reduces Costs => Increase Batch Size
218 - For Tasks which have a low impact on the cycle time (looking at the duration fn)
219 """
221 params: ModifySizeRuleByCostFnActionParamsType
223 @staticmethod
224 def rate_self(
225 store: "Store", input: "Solution"
226 ) -> RateSelfReturnType["ModifySizeRuleByCostFnLowCycleTimeImpactAction | AddSizeRuleAction"]:
227 """Generate a best set of parameters & self-evaluates this action."""
228 timetable = input.state.timetable
229 constraints = store.constraints
230 state = input.state
232 task_ids = timetable.get_task_ids()
233 rule_selectors_by_cycle_time_impact: dict[float, list[RuleSelector]] = defaultdict(list)
235 for task_id in task_ids:
236 size_constraints = constraints.get_batching_size_rule_constraints(task_id)
237 if not size_constraints:
238 continue
239 size_constraint = size_constraints[0]
241 firing_rule_selectors = timetable.get_firing_rule_selectors_for_task(
242 task_id, rule_type=RULE_TYPE.SIZE
243 )
244 if not firing_rule_selectors:
245 new_cost = size_constraint.cost_fn_lambda(2)
246 old_cost = size_constraint.cost_fn_lambda(1)
248 old_duration = size_constraint.duration_fn_lambda(1)
249 new_duration = size_constraint.duration_fn_lambda(2)
250 if new_cost < old_cost:
251 cycle_time_impact = old_duration - new_duration
252 rule_selectors_by_cycle_time_impact[cycle_time_impact].append(
253 RuleSelector(batching_rule_task_id=task_id, firing_rule_index=None)
254 )
256 for firing_rule_selector in firing_rule_selectors:
257 firing_rule = firing_rule_selector.get_firing_rule_from_state(state)
258 if firing_rule is None:
259 continue
260 size = firing_rule.value
262 new_cost = size_constraint.cost_fn_lambda(size + 1)
263 old_cost = size_constraint.cost_fn_lambda(size)
265 old_duration = size_constraint.duration_fn_lambda(size)
266 new_duration = size_constraint.duration_fn_lambda(size + 1)
267 if new_cost < old_cost:
268 cycle_time_impact = old_duration - new_duration
269 rule_selectors_by_cycle_time_impact[cycle_time_impact].append(firing_rule_selector)
271 cycle_time_impacts_sorted = sorted(
272 rule_selectors_by_cycle_time_impact.keys(),
273 reverse=True,
274 )
276 for cycle_time_impact in cycle_time_impacts_sorted:
277 rule_selectors = rule_selectors_by_cycle_time_impact[cycle_time_impact]
278 for rule_selector in select_variants(store, rule_selectors):
279 duration_fn = constraints.get_duration_fn_for_task(rule_selector.batching_rule_task_id)
281 if rule_selector.firing_rule_index is not None:
282 yield (
283 ModifySizeRuleBaseAction.get_default_rating(),
284 ModifySizeRuleByCostFnLowCycleTimeImpactAction(
285 ModifySizeRuleByCostFnActionParamsType(
286 rule=rule_selector,
287 size_increment=1,
288 duration_fn=duration_fn,
289 )
290 ),
291 )
292 else:
293 yield (
294 RATING.LOW,
295 AddSizeRuleAction(
296 AddSizeRuleBaseActionParamsType(
297 task_id=rule_selector.batching_rule_task_id,
298 size=2,
299 duration_fn=duration_fn,
300 )
301 ),
302 )
305class ModifySizeRuleByManySimilarEnablementsAction(ModifySizeRuleBaseAction):
306 """An Action to modify size batching rules based on the cost fn.
308 - Modify those tasks, which have many enablements at the same time
309 """
311 params: ModifySizeRuleByCostFnActionParamsType
313 @staticmethod
314 def rate_self(
315 store: "Store", input: "Solution"
316 ) -> RateSelfReturnType["ModifySizeRuleByManySimilarEnablementsAction"]:
317 """Generate a best set of parameters & self-evaluates this action."""
318 evaluation = input.evaluation
320 tasks_by_number_of_duplicate_enablement_dates = (
321 evaluation.tasks_by_number_of_duplicate_enablement_dates
322 )
323 yield from rate_self_helper_by_metric_dict(
324 store,
325 tasks_by_number_of_duplicate_enablement_dates,
326 ModifySizeRuleByManySimilarEnablementsAction,
327 )