Coverage for sparkle/CLI/help/reporting_scenario.py: 53%
184 statements
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 09:10 +0000
« prev ^ index » next coverage.py v7.6.1, created at 2024-09-27 09:10 +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
9from sparkle.solver import Solver
10from sparkle.instance import instance_set, InstanceSet
13class Scenario(str, Enum):
14 """Enum of possible execution scenarios for Sparkle."""
16 NONE = "NONE"
17 SELECTION = "SELECTION"
18 CONFIGURATION = "CONFIGURATION"
19 PARALLEL_PORTFOLIO = "PARALLEL_PORTFOLIO"
22class ReportingScenario:
23 """Class to manage scenarios executed with Sparkle."""
25 # ReportingScenario path names and defaults
26 __reporting_scenario_file = Path("latest_scenario.ini")
27 __reporting_scenario_dir = Path("Output")
28 DEFAULT_reporting_scenario_path = Path(
29 PurePath(__reporting_scenario_dir / __reporting_scenario_file))
31 # Constant default values
32 DEFAULT_latest_scenario = Scenario.NONE
34 DEFAULT_selection_portfolio_path = Path("")
35 DEFAULT_selection_test_case_directory = Path("")
37 DEFAULT_parallel_portfolio_path = Path("")
38 DEFAULT_parallel_portfolio_instance_list = []
40 DEFAULT_config_solver = Path("")
41 DEFAULT_config_instance_set_train = Path("")
42 DEFAULT_config_instance_set_test = Path("")
44 def __init__(self: ReportingScenario) -> None:
45 """Initialise a ReportingScenario object."""
46 # ReportingScenario 'dictionary' in configparser format
47 self.__scenario = configparser.ConfigParser()
49 # Initialise scenario in default file path
50 self.read_scenario_ini()
52 return
54 def read_scenario_ini(
55 self: ReportingScenario, file_path: Path = DEFAULT_reporting_scenario_path)\
56 -> None:
57 """Read the scenario from an INI file.
59 Args:
60 file_path: Path of the INI file for the scenario. Defaults to
61 DEFAULT_reporting_scenario_path.
62 """
63 # If the file does not exist set default values
64 if not Path(file_path).is_file():
65 self.set_latest_scenario()
66 self.set_selection_scenario_path()
67 self.set_selection_test_case_directory()
68 self.set_parallel_portfolio_path()
69 self.set_parallel_portfolio_instance_path()
70 self.set_config_solver()
71 self.set_config_instance_set_train()
72 self.set_config_instance_set_test()
74 # Read file
75 file_scenario = configparser.ConfigParser()
76 file_scenario.read(str(file_path))
78 # Set internal scenario based on data read from FILE if they were read
79 # successfully
80 if file_scenario.sections() != []:
81 section = "latest"
82 option_names = ("scenario",) # Comma to make it a tuple
83 for option in option_names:
84 if file_scenario.has_option(section, option):
85 value = Scenario(file_scenario.get(section, option))
86 self.set_latest_scenario(value)
87 file_scenario.remove_option(section, option)
89 section = "selection"
90 option_names = ("scenario_path",) # Comma to make it a tuple
91 for option in option_names:
92 if file_scenario.has_option(section, option):
93 value = Path(file_scenario.get(section, option))
94 self.set_selection_scenario_path(value)
95 file_scenario.remove_option(section, option)
97 option_names = ("test_case_directory",) # Comma to make it a tuple
98 for option in option_names:
99 if file_scenario.has_option(section, option):
100 value = Path(file_scenario.get(section, option))
101 self.set_selection_test_case_directory(value)
102 file_scenario.remove_option(section, option)
104 section = "configuration"
105 option_names = ("solver",) # Comma to make it a tuple
106 for option in option_names:
107 if file_scenario.has_option(section, option):
108 value = Path(file_scenario.get(section, option))
109 self.set_config_solver(value)
110 file_scenario.remove_option(section, option)
112 option_names = ("instance_set_train",) # Comma to make it a tuple
113 for option in option_names:
114 if file_scenario.has_option(section, option):
115 value = Path(file_scenario.get(section, option))
116 self.set_config_instance_set_train(value)
117 file_scenario.remove_option(section, option)
119 option_names = ("instance_set_test",) # Comma to make it a tuple
120 for option in option_names:
121 if file_scenario.has_option(section, option):
122 value = Path(file_scenario.get(section, option))
123 self.set_config_instance_set_test(value)
124 file_scenario.remove_option(section, option)
126 section = "parallel_portfolio"
127 option_names = ("portfolio_path",) # Comma to make it a tuple
128 for option in option_names:
129 if file_scenario.has_option(section, option):
130 value = Path(file_scenario.get(section, option))
131 self.set_parallel_portfolio_path(value)
132 file_scenario.remove_option(section, option)
134 section = "parallel_portfolio"
135 option_names = ("instance_path",) # Comma to make it a tuple
136 for option in option_names:
137 if file_scenario.has_option(section, option):
138 value = file_scenario.get(section, option)
139 self.set_parallel_portfolio_instance_path(value)
140 file_scenario.remove_option(section, option)
142 # Report on any unknown settings that were read
143 sections = file_scenario.sections()
145 for section in sections:
146 for option in file_scenario[section]:
147 print(f'Unrecognised section - option combination:"{section} '
148 f'{option}" in file {file_path} ignored')
150 # Print error if unable to read the scenario file
151 else:
152 print(f"WARNING: Failed to read latest scenario from {file_path} The "
153 "file may have been empty, or is in another format than INI. Default "
154 "values will be used.")
156 def write_scenario_ini(
157 self: ReportingScenario, file_path: Path = DEFAULT_reporting_scenario_path)\
158 -> None:
159 """Write the scenario file in INI format.
161 Args:
162 file_path: Path of the INI file for the scenario. Defaults to
163 DEFAULT_reporting_scenario_path.
164 """
165 # Create needed directories if they don't exist
166 file_dir = file_path.parents[0]
167 file_dir.mkdir(parents=True, exist_ok=True)
169 # Write the scenario to file
170 with Path(str(file_path)).open("w") as scenario_file:
171 self.__scenario.write(scenario_file)
173 def __init_section(self: ReportingScenario, section: str) -> None:
174 """Initialise a section in the scenario file.
176 Args:
177 section: Name of the section.
178 """
179 if section not in self.__scenario:
180 self.__scenario[section] = {}
182 # Generic setters ###
184 def path_setter(self: ReportingScenario, section: str, name: str, value: Path)\
185 -> None:
186 """Set a generic Path for the scenario.
188 Args:
189 section: Name of the section.
190 name: Name of the path element.
191 value: Value of the path given.
192 """
193 if value is not None:
194 self.__init_section(section)
195 self.__scenario[section][name] = str(value)
197 # Generic getters ###
199 def none_if_empty_path(self: ReportingScenario, path: Path) -> Path:
200 """Return None if a path is empty or the Path otherwise.
202 Args:
203 path: Path value given.
205 Returns:
206 None if the given path is empty, the given Path value otherwise.
207 """
208 if str(path) == "" or str(path) == ".":
209 return None
210 return path
212 # Latest settings ###
214 def set_latest_scenario(self: ReportingScenario,
215 value: Scenario = DEFAULT_latest_scenario) -> None:
216 """Set the latest Scenario that was executed."""
217 section = "latest"
218 name = "scenario"
220 if value is not None:
221 self.__init_section(section)
222 self.__scenario[section][name] = value.name
224 def get_latest_scenario(self: ReportingScenario) -> Scenario:
225 """Return the latest Scenario that was executed."""
226 return Scenario(self.__scenario["latest"]["scenario"])
228 # Selection settings ###
230 def set_selection_scenario_path(
231 self: ReportingScenario, value: Path = DEFAULT_selection_portfolio_path)\
232 -> None:
233 """Set the path to portfolio selector used for algorithm selection."""
234 section = "selection"
235 name = "scenario_path"
236 self.path_setter(section, name, value)
238 def get_selection_scenario_path(self: ReportingScenario) -> Path:
239 """Return the path to portfolio selector used for algorithm selection."""
240 return Path(self.__scenario["selection"]["scenario_path"])
242 def set_selection_test_case_directory(
243 self: ReportingScenario,
244 value: Path = DEFAULT_selection_test_case_directory) -> None:
245 """Set the path to the testing set that was used for algorithm selection."""
246 section = "selection"
247 name = "test_case_directory"
248 self.path_setter(section, name, value)
250 def get_selection_test_case_directory(self: ReportingScenario) -> str:
251 """Return the path to the testing set that was used for algorithm selection."""
252 try:
253 path = self.__scenario["selection"]["test_case_directory"]
254 if Path(path) == Path("."):
255 path = None
256 except KeyError:
257 path = None
258 return path
260 # Parallel portfolio settings ###
262 def set_parallel_portfolio_path(
263 self: ReportingScenario,
264 value: Path = DEFAULT_parallel_portfolio_path) -> None:
265 """Set the path to the parallel portfolio."""
266 section = "parallel_portfolio"
267 name = "portfolio_path"
268 self.path_setter(section, name, value)
270 def get_parallel_portfolio_path(self: ReportingScenario) -> Path:
271 """Return the path to the parallel portfolio."""
272 return Path(self.__scenario["parallel_portfolio"]["portfolio_path"])
274 def set_parallel_portfolio_instance_path(
275 self: ReportingScenario,
276 value: Path = None) -> None:
277 """Set the instance path used with the parallel portfolio."""
278 section = "parallel_portfolio"
279 name = "instance_path"
280 self.path_setter(section, name, value)
282 def get_parallel_portfolio_instance_set(self: ReportingScenario) -> InstanceSet:
283 """Return the instance list used with the parallel portfolio.
285 If instance list is empty return None.
286 """
287 if self.__scenario["parallel_portfolio"]["instance_path"] is None:
288 return None
289 return instance_set(Path(self.__scenario["parallel_portfolio"]["instance_path"]))
291 # Configuration settings ###
293 def set_config_solver(self: ReportingScenario,
294 value: Solver | Path = DEFAULT_config_solver) -> None:
295 """Set the path to the solver that was configured."""
296 section = "configuration"
297 name = "solver"
298 if isinstance(value, Solver):
299 value = value.directory
300 self.path_setter(section, name, value)
302 def get_config_solver(self: ReportingScenario) -> Solver:
303 """Return the path to the solver that was configured."""
304 path = self.none_if_empty_path(Path(self.__scenario["configuration"]["solver"]))
305 if path is not None:
306 return Solver(path)
307 return None
309 def set_config_instance_set_train(
310 self: ReportingScenario, value: Path = DEFAULT_config_instance_set_train)\
311 -> None:
312 """Set the path to the training instance set used for configuration."""
313 section = "configuration"
314 name = "instance_set_train"
315 self.path_setter(section, name, value)
317 def get_config_instance_set_train(self: ReportingScenario) -> InstanceSet:
318 """Return the path to the training instance set used for configuration."""
319 path = self.none_if_empty_path(
320 Path(self.__scenario["configuration"]["instance_set_train"]))
321 if path is None:
322 return None
323 return instance_set(path)
325 def set_config_instance_set_test(
326 self: ReportingScenario, value: Path = DEFAULT_config_instance_set_test)\
327 -> None:
328 """Set the path to the testing instance set used for configuration."""
329 section = "configuration"
330 name = "instance_set_test"
331 self.path_setter(section, name, value)
333 def get_config_instance_set_test(self: ReportingScenario) -> InstanceSet:
334 """Return the path to the testing instance set used for configuration."""
335 path = self.none_if_empty_path(
336 Path(self.__scenario["configuration"]["instance_set_test"]))
337 if path is None:
338 return None
339 return instance_set(path)