Coverage for o2/models/state.py: 85%
53 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
1import datetime
2import xml.etree.ElementTree as ET
3from dataclasses import dataclass, replace
4from typing import TYPE_CHECKING, Any
6import pytz
7from prosimos.simulation_setup import SimDiffSetup
9from o2.models.evaluation import Evaluation
10from o2.models.settings import Settings
11from o2.simulation_runner import SimulationRunner
12from o2.util.logger import warn
13from o2.util.sim_diff_setup_fileless import SimDiffSetupFileless
15if TYPE_CHECKING:
16 from o2.models.timetable import TimetableType
19@dataclass(frozen=True)
20class State:
21 """A state in the optimization process.
23 It's mainly a container class for the timetable.
25 The State should contain all information to run a simulation -- that's why it
26 contains the BPMN definition as well.
27 """
29 bpmn_definition: str
31 timetable: "TimetableType"
32 # TODO: Move to setting class
33 for_testing: bool = False
35 def replace_timetable(self, /, **changes: Any) -> "State": # noqa: ANN401
36 """Replace the timetable with the given changes."""
37 return replace(self, timetable=replace(self.timetable, **changes))
39 def evaluate(self) -> Evaluation:
40 """Evaluate the current state."""
41 if not self.is_valid():
42 warn("Trying to evaluate an invalid state.")
43 return Evaluation.empty()
44 try:
45 if Settings.USE_MEDIAN_SIMULATION_FOR_EVALUATION:
46 result = SimulationRunner.run_simulation_median(self)
47 else:
48 result = SimulationRunner.run_simulation(self)
49 except Exception as e:
50 if Settings.RAISE_SIMULATION_ERRORS:
51 raise e
52 return Evaluation.empty()
53 return Evaluation.from_run_simulation_result(
54 self.timetable.get_hourly_rates(),
55 self.timetable.get_fixed_cost_fns(),
56 self.timetable.batching_rules_exist,
57 result,
58 )
60 def to_sim_diff_setup(self) -> SimDiffSetup:
61 """Convert the state to a SimDiffSetup."""
62 setup = SimDiffSetupFileless(
63 "test",
64 self.bpmn_definition,
65 self.timetable,
66 False,
67 self.timetable.total_cases,
68 )
69 if self.for_testing:
70 # For testing we start on 03.01.2000, a Monday
71 starting_at_datetime = pytz.utc.localize(datetime.datetime(2000, 1, 3))
72 else:
73 starting_at_datetime = pytz.utc.localize(datetime.datetime.now())
75 setup.set_starting_datetime(starting_at_datetime)
77 return setup
79 def get_name_of_task(self, task_id: str) -> str:
80 """Get the name of a task."""
81 bpmn_tree = ET.fromstring(self.bpmn_definition)
82 node = bpmn_tree.find(f".//*[@id='{task_id}']")
83 assert node is not None
84 return node.attrib["name"]
86 def get_task_names(self) -> dict[str, str]:
87 """Get a mapping of task IDs to task names."""
88 bpmn_tree = ET.fromstring(self.bpmn_definition)
89 task_nodes = bpmn_tree.findall(".//*[@id]")
90 return {node.attrib["id"]: node.attrib.get("name", node.attrib["id"]) for node in task_nodes}
92 def is_valid(self) -> bool:
93 """Check if the state is valid."""
94 return self.timetable.is_valid()
97class TabuState(State):
98 """A state that is tabu."""
100 def __init__(self) -> None:
101 """Initialize the tabu state."""
103 def is_valid(self) -> bool:
104 """Check if the state is valid."""
105 return False