Coverage for sparkle/CLI/help/reporting_scenario.py: 96%
202 statements
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 14:48 +0000
« prev ^ index » next coverage.py v7.6.4, created at 2024-11-05 14:48 +0000
1"""Helper module to manage Sparkle scenarios."""
2# Keep in CLI help
4from __future__ import annotations
5import configparser
6from enum import Enum
7from pathlib import Path
8from pathlib import PurePath
10from sparkle.configurator.configurator import ConfigurationScenario
11from sparkle.solver import Solver
12from sparkle.instance import Instance_Set, InstanceSet
15class Scenario(str, Enum):
16 """Enum of possible execution scenarios for Sparkle."""
18 NONE = "NONE"
19 SELECTION = "SELECTION"
20 CONFIGURATION = "CONFIGURATION"
21 PARALLEL_PORTFOLIO = "PARALLEL_PORTFOLIO"
24class ReportingScenario:
25 """Class to manage scenarios executed with Sparkle."""
27 # ReportingScenario path names and defaults
28 __reporting_scenario_file = Path("latest_scenario.ini")
29 __reporting_scenario_dir = Path("Output")
30 DEFAULT_reporting_scenario_path = Path(
31 PurePath(__reporting_scenario_dir / __reporting_scenario_file))
33 # Constant default values
34 DEFAULT_latest_scenario = Scenario.NONE
36 DEFAULT_selection_portfolio_path = Path("")
37 DEFAULT_selection_test_case_directory = Path("")
39 DEFAULT_parallel_portfolio_path = Path("")
40 DEFAULT_parallel_portfolio_instance_list = []
42 DEFAULT_config_solver = Path("")
43 DEFAULT_config_instance_set_train = Path("")
44 DEFAULT_config_instance_set_test = Path("")
46 def __init__(self: ReportingScenario) -> None:
47 """Initialise a ReportingScenario object."""
48 # ReportingScenario 'dictionary' in configparser format
49 self.__scenario = configparser.ConfigParser()
51 # Initialise scenario in default file path
52 self.read_scenario_ini()
54 return
56 def read_scenario_ini(
57 self: ReportingScenario, file_path: Path = DEFAULT_reporting_scenario_path)\
58 -> None:
59 """Read the scenario from an INI file.
61 Args:
62 file_path: Path of the INI file for the scenario. Defaults to
63 DEFAULT_reporting_scenario_path.
64 """
65 # If the file does not exist set default values
66 if not Path(file_path).is_file():
67 self.set_latest_scenario()
68 self.set_selection_scenario_path()
69 self.set_selection_test_case_directory()
70 self.set_parallel_portfolio_path()
71 self.set_parallel_portfolio_instance_path()
72 self.set_config_solver()
73 self.set_config_instance_set_train()
74 self.set_config_instance_set_test()
76 # Read file
77 file_scenario = configparser.ConfigParser()
78 file_scenario.read(file_path)
80 # Set internal scenario based on data read from FILE if they were read
81 # successfully
82 if file_scenario.sections() != []:
83 section = "latest"
84 option_names = ("scenario",) # Comma to make it a tuple
85 for option in option_names:
86 if file_scenario.has_option(section, option):
87 value = Scenario(file_scenario.get(section, option))
88 self.set_latest_scenario(value)
89 file_scenario.remove_option(section, option)
91 section = "selection"
92 option_names = ("scenario_path",) # Comma to make it a tuple
93 for option in option_names:
94 if file_scenario.has_option(section, option):
95 value = Path(file_scenario.get(section, option))
96 self.set_selection_scenario_path(value)
97 file_scenario.remove_option(section, option)
99 option_names = ("test_case_directory",) # Comma to make it a tuple
100 for option in option_names:
101 if file_scenario.has_option(section, option):
102 value = Path(file_scenario.get(section, option))
103 self.set_selection_test_case_directory(value)
104 file_scenario.remove_option(section, option)
106 section = "configuration"
107 option_names = ("solver",) # Comma to make it a tuple
108 for option in option_names:
109 if file_scenario.has_option(section, option):
110 value = Path(file_scenario.get(section, option))
111 self.set_config_solver(value)
112 file_scenario.remove_option(section, option)
114 option_names = ("instance_set_train",) # Comma to make it a tuple
115 for option in option_names:
116 if file_scenario.has_option(section, option):
117 value = Path(file_scenario.get(section, option))
118 self.set_config_instance_set_train(value)
119 file_scenario.remove_option(section, option)
121 option_names = ("instance_set_test",) # Comma to make it a tuple
122 for option in option_names:
123 if file_scenario.has_option(section, option):
124 value = Path(file_scenario.get(section, option))
125 self.set_config_instance_set_test(value)
126 file_scenario.remove_option(section, option)
128 option_names = ("scenario_file_path", )
129 for option in option_names:
130 if file_scenario.has_option(section, option):
131 value = Path(file_scenario.get(section, option))
132 self.set_configuration_scenario(value)
133 file_scenario.remove_option(section, option)
135 section = "parallel_portfolio"
136 option_names = ("portfolio_path",) # Comma to make it a tuple
137 for option in option_names:
138 if file_scenario.has_option(section, option):
139 value = Path(file_scenario.get(section, option))
140 self.set_parallel_portfolio_path(value)
141 file_scenario.remove_option(section, option)
143 option_names = ("instance_path",) # Comma to make it a tuple
144 for option in option_names:
145 if file_scenario.has_option(section, option):
146 value = file_scenario.get(section, option)
147 self.set_parallel_portfolio_instance_path(value)
148 file_scenario.remove_option(section, option)
150 # Report on any unknown settings that were read
151 sections = file_scenario.sections()
153 for section in sections:
154 for option in file_scenario[section]:
155 print(f'Unrecognised section - option combination:"{section} '
156 f'{option}" in file {file_path} ignored')
158 # Print error if unable to read the scenario file
159 elif not file_path.exists():
160 print(f"WARNING: Failed to read latest scenario from {file_path}. "
161 "Default values will be used.")
163 def write_scenario_ini(
164 self: ReportingScenario, file_path: Path = DEFAULT_reporting_scenario_path)\
165 -> None:
166 """Write the scenario file in INI format.
168 Args:
169 file_path: Path of the INI file for the scenario. Defaults to
170 DEFAULT_reporting_scenario_path.
171 """
172 # Create needed directories if they don't exist
173 file_dir = file_path.parents[0]
174 file_dir.mkdir(parents=True, exist_ok=True)
176 # Write the scenario to file
177 with Path(str(file_path)).open("w") as scenario_file:
178 self.__scenario.write(scenario_file)
180 def __init_section(self: ReportingScenario, section: str) -> None:
181 """Initialise a section in the scenario file.
183 Args:
184 section: Name of the section.
185 """
186 if section not in self.__scenario:
187 self.__scenario[section] = {}
189 # Generic setters ###
191 def path_setter(self: ReportingScenario, section: str, name: str, value: Path)\
192 -> None:
193 """Set a generic Path for the scenario.
195 Args:
196 section: Name of the section.
197 name: Name of the path element.
198 value: Value of the path given.
199 """
200 if value is not None:
201 self.__init_section(section)
202 self.__scenario[section][name] = str(value)
204 # Generic getters ###
206 def none_if_empty_path(self: ReportingScenario, path: Path) -> Path:
207 """Return None if a path is empty or the Path otherwise.
209 Args:
210 path: Path value given.
212 Returns:
213 None if the given path is empty, the given Path value otherwise.
214 """
215 if str(path) == "" or str(path) == ".":
216 return None
217 return path
219 # Latest settings ###
221 def set_latest_scenario(self: ReportingScenario,
222 value: Scenario = DEFAULT_latest_scenario) -> None:
223 """Set the latest Scenario that was executed."""
224 section = "latest"
225 name = "scenario"
227 if value is not None:
228 self.__init_section(section)
229 self.__scenario[section][name] = value.name
231 def get_latest_scenario(self: ReportingScenario) -> Scenario:
232 """Return the latest Scenario that was executed."""
233 return Scenario(self.__scenario["latest"]["scenario"])
235 # Selection settings ###
237 def set_selection_scenario_path(
238 self: ReportingScenario, value: Path = DEFAULT_selection_portfolio_path)\
239 -> None:
240 """Set the path to portfolio selector used for algorithm selection."""
241 section = "selection"
242 name = "scenario_path"
243 self.path_setter(section, name, value)
245 def get_selection_scenario_path(self: ReportingScenario) -> Path:
246 """Return the path to portfolio selector used for algorithm selection."""
247 return Path(self.__scenario["selection"]["scenario_path"])
249 def set_selection_test_case_directory(
250 self: ReportingScenario,
251 value: Path = DEFAULT_selection_test_case_directory) -> None:
252 """Set the path to the testing set that was used for algorithm selection."""
253 section = "selection"
254 name = "test_case_directory"
255 self.path_setter(section, name, value)
257 def get_selection_test_case_directory(self: ReportingScenario) -> str:
258 """Return the path to the testing set that was used for algorithm selection."""
259 try:
260 path = self.__scenario["selection"]["test_case_directory"]
261 if Path(path) == Path("."):
262 path = None
263 except KeyError:
264 path = None
265 return path
267 # Parallel portfolio settings ###
269 def set_parallel_portfolio_path(
270 self: ReportingScenario,
271 value: Path = DEFAULT_parallel_portfolio_path) -> None:
272 """Set the path to the parallel portfolio."""
273 section = "parallel_portfolio"
274 name = "portfolio_path"
275 self.path_setter(section, name, value)
277 def get_parallel_portfolio_path(self: ReportingScenario) -> Path:
278 """Return the path to the parallel portfolio."""
279 return Path(self.__scenario["parallel_portfolio"]["portfolio_path"])
281 def set_parallel_portfolio_instance_path(
282 self: ReportingScenario,
283 value: Path = None) -> None:
284 """Set the instance path used with the parallel portfolio."""
285 section = "parallel_portfolio"
286 name = "instance_path"
287 self.path_setter(section, name, value)
289 def get_parallel_portfolio_instance_set(self: ReportingScenario) -> InstanceSet:
290 """Return the instance list used with the parallel portfolio.
292 If instance list is empty return None.
293 """
294 if self.__scenario["parallel_portfolio"]["instance_path"] is None:
295 return None
296 return Instance_Set(Path(self.__scenario["parallel_portfolio"]["instance_path"]))
298 # Configuration settings ###
300 def set_config_solver(self: ReportingScenario,
301 value: Solver | Path = DEFAULT_config_solver) -> None:
302 """Set the path to the solver that was configured."""
303 section = "configuration"
304 name = "solver"
305 if isinstance(value, Solver):
306 value = value.directory
307 self.path_setter(section, name, value)
309 def get_config_solver(self: ReportingScenario) -> Solver:
310 """Return the path to the solver that was configured."""
311 path = self.none_if_empty_path(Path(self.__scenario["configuration"]["solver"]))
312 if path is not None:
313 return Solver(path)
314 return None
316 def set_config_instance_set_train(
317 self: ReportingScenario, value: Path = DEFAULT_config_instance_set_train)\
318 -> None:
319 """Set the path to the training instance set used for configuration."""
320 section = "configuration"
321 name = "instance_set_train"
322 self.path_setter(section, name, value)
324 def get_config_instance_set_train(self: ReportingScenario) -> InstanceSet:
325 """Return the path to the training instance set used for configuration."""
326 path = self.none_if_empty_path(
327 Path(self.__scenario["configuration"]["instance_set_train"]))
328 if path is None:
329 return None
330 return Instance_Set(path)
332 def set_configuration_scenario(self: ReportingScenario,
333 value: Scenario | Path) -> None:
334 """Set the path to the scenario that was used for configuration."""
335 section = "configuration"
336 name = "scenario_file_path"
337 if isinstance(value, ConfigurationScenario):
338 value = value.scenario_file_path
339 self.path_setter(section, name, value)
341 def get_configuration_scenario(self: ReportingScenario,
342 scenario_class: ConfigurationScenario) -> Scenario:
343 """Return the path to the scenario that was used for configuration."""
344 path = self.none_if_empty_path(
345 Path(self.__scenario["configuration"]["scenario_file_path"]))
346 if path is None:
347 return None
348 return scenario_class.from_file(path)
350 def set_config_instance_set_test(
351 self: ReportingScenario, value: Path = DEFAULT_config_instance_set_test)\
352 -> None:
353 """Set the path to the testing instance set used for configuration."""
354 section = "configuration"
355 name = "instance_set_test"
356 self.path_setter(section, name, value)
358 def get_config_instance_set_test(self: ReportingScenario) -> InstanceSet:
359 """Return the path to the testing instance set used for configuration."""
360 path = self.none_if_empty_path(
361 Path(self.__scenario["configuration"]["instance_set_test"]))
362 if path is None:
363 return None
364 return Instance_Set(path)