Coverage for sparkle/platform/settings_objects.py: 31%
544 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"""Classes and Enums to control settings."""
2from __future__ import annotations
3import configparser
4from enum import Enum
5from pathlib import Path
6from pathlib import PurePath
8from sparkle.types import SparkleObjective, resolve_objective
9from sparkle.types.objective import PAR
10from sparkle.solver import Selector
11from sparkle.configurator.configurator import Configurator
12from sparkle.solver.verifier import SATVerifier
13from sparkle.configurator import implementations as cim
15from runrunner import Runner
16from sparkle.platform.cli_types import VerbosityLevel
19class SettingState(Enum):
20 """Enum of possible setting states."""
22 NOT_SET = 0
23 DEFAULT = 1
24 FILE = 2
25 CMD_LINE = 3
28class Settings:
29 """Class to read, write, set, and get settings."""
30 # CWD Prefix
31 cwd_prefix = Path() # Empty for now
33 # Library prefix
34 lib_prefix = Path(__file__).parent.parent.resolve()
36 # Default directory names
37 rawdata_dir = Path("Raw_Data")
38 analysis_dir = Path("Analysis")
39 __settings_dir = Path("Settings")
40 __settings_file = Path("sparkle_settings.ini")
42 # Default settings path
43 DEFAULT_settings_path = PurePath(cwd_prefix / __settings_dir / __settings_file)
45 # Default library pathing
46 DEFAULT_components = lib_prefix / "Components"
48 # Example settings path
49 DEFAULT_example_settings_path = PurePath(DEFAULT_components / "sparkle_settings.ini")
51 # Runsolver component
52 DEFAULT_runsolver_dir = DEFAULT_components / "runsolver" / "src"
53 DEFAULT_runsolver_exec = DEFAULT_runsolver_dir / "runsolver"
55 # Ablation component
56 DEFAULT_ablation_dir = DEFAULT_components / "ablationAnalysis-0.9.4"
57 DEFAULT_ablation_exec = DEFAULT_ablation_dir / "ablationAnalysis"
58 DEFAULT_ablation_validation_exec = DEFAULT_ablation_dir / "ablationValidation"
60 # Autofolio component
61 DEFAULT_general_sparkle_selector = DEFAULT_components / "AutoFolio/scripts/autofolio"
63 # Report component
64 DEFAULT_latex_source = DEFAULT_components / "Sparkle-latex-source"
65 DEFAULT_latex_bib = DEFAULT_latex_source / "SparkleReport.bib"
67 # Default input directory pathing
68 DEFAULT_solver_dir = cwd_prefix / "Solvers"
69 DEFAULT_instance_dir = cwd_prefix / "Instances"
70 DEFAULT_extractor_dir = cwd_prefix / "Extractors"
71 DEFAULT_snapshot_dir = cwd_prefix / "Snapshots"
73 # Default output directory pathing
74 DEFAULT_tmp_output = cwd_prefix / "Tmp"
75 DEFAULT_output = cwd_prefix / "Output"
76 DEFAULT_configuration_output = DEFAULT_output / "Configuration"
77 DEFAULT_selection_output = DEFAULT_output / "Selection"
78 DEFAULT_validation_output = DEFAULT_output / "Validation"
79 DEFAULT_parallel_portfolio_output = DEFAULT_output / "Parallel_Portfolio"
80 DEFAULT_ablation_output = DEFAULT_output / "Ablation"
81 DEFAULT_log_output = DEFAULT_output / "Log"
83 # Default output subdirs
84 DEFAULT_configuration_output_raw = DEFAULT_configuration_output / rawdata_dir
85 DEFAULT_configuration_output_analysis = DEFAULT_configuration_output / analysis_dir
86 DEFAULT_selection_output_raw = DEFAULT_selection_output / rawdata_dir
87 DEFAULT_selection_output_analysis = DEFAULT_selection_output / analysis_dir
88 DEFAULT_parallel_portfolio_output_raw =\
89 DEFAULT_parallel_portfolio_output / rawdata_dir
90 DEFAULT_parallel_portfolio_output_analysis =\
91 DEFAULT_parallel_portfolio_output / analysis_dir
93 # Old default output dirs which should be part of something else
94 DEFAULT_feature_data = DEFAULT_output / "Feature_Data"
95 DEFAULT_performance_data = DEFAULT_output / "Performance_Data"
97 # Collection of all working dirs for platform
98 DEFAULT_working_dirs = [
99 DEFAULT_output, DEFAULT_configuration_output,
100 DEFAULT_selection_output, DEFAULT_validation_output,
101 DEFAULT_tmp_output,
102 DEFAULT_log_output,
103 DEFAULT_solver_dir, DEFAULT_instance_dir,
104 DEFAULT_feature_data, DEFAULT_performance_data,
105 DEFAULT_extractor_dir,
106 ]
108 # Old default file paths from GV which should be turned into variables
109 DEFAULT_feature_data_path =\
110 DEFAULT_feature_data / "feature_data.csv"
111 DEFAULT_performance_data_path =\
112 DEFAULT_performance_data / "performance_data.csv"
114 # Constant default values
115 DEFAULT_general_sparkle_objective = PAR(10)
116 DEFAULT_general_sparkle_configurator = cim.SMAC2.__name__
117 DEFAULT_general_solution_verifier = str(None)
118 DEFAULT_general_target_cutoff_time = 60
119 DEFAULT_general_extractor_cutoff_time = 60
120 DEFAULT_number_of_jobs_in_parallel = 25
121 DEFAULT_general_verbosity = VerbosityLevel.STANDARD
122 DEFAULT_general_check_interval = 10
124 DEFAULT_config_wallclock_time = 600
125 DEFAULT_config_cpu_time = None
126 DEFAULT_config_solver_calls = None
127 DEFAULT_config_number_of_runs = 25
128 DEFAULT_configurator_target_cutoff_length = "max"
130 DEFAULT_portfolio_construction_timeout = None
132 DEFAULT_slurm_max_parallel_runs_per_node = 8
134 DEFAULT_ablation_racing = False
136 DEFAULT_parallel_portfolio_check_interval = 4
137 DEFAULT_parallel_portfolio_num_seeds_per_solver = 1
139 def __init__(self: Settings, file_path: PurePath = None) -> None:
140 """Initialise a settings object."""
141 # Settings 'dictionary' in configparser format
142 self.__settings = configparser.ConfigParser()
144 # Setting flags
145 self.__general_sparkle_objective_set = SettingState.NOT_SET
146 self.__general_sparkle_configurator_set = SettingState.NOT_SET
147 self.__general_sparkle_selector_set = SettingState.NOT_SET
148 self.__general_solution_verifier_set = SettingState.NOT_SET
149 self.__general_target_cutoff_time_set = SettingState.NOT_SET
150 self.__general_extractor_cutoff_time_set = SettingState.NOT_SET
151 self.__general_verbosity_set = SettingState.NOT_SET
152 self.__general_check_interval_set = SettingState.NOT_SET
154 self.__config_wallclock_time_set = SettingState.NOT_SET
155 self.__config_cpu_time_set = SettingState.NOT_SET
156 self.__config_solver_calls_set = SettingState.NOT_SET
157 self.__config_number_of_runs_set = SettingState.NOT_SET
159 self.__run_on_set = SettingState.NOT_SET
160 self.__number_of_jobs_in_parallel_set = SettingState.NOT_SET
161 self.__slurm_max_parallel_runs_per_node_set = SettingState.NOT_SET
162 self.__configurator_target_cutoff_length_set = SettingState.NOT_SET
163 self.__slurm_extra_options_set = dict()
164 self.__ablation_racing_flag_set = SettingState.NOT_SET
166 self.__parallel_portfolio_check_interval_set = SettingState.NOT_SET
167 self.__parallel_portfolio_num_seeds_per_solver_set = SettingState.NOT_SET
169 self.__general_sparkle_configurator = None
171 if file_path is None:
172 # Initialise settings from default file path
173 self.read_settings_ini()
174 else:
175 # Initialise settings from a given file path
176 self.read_settings_ini(file_path)
178 def read_settings_ini(self: Settings, file_path: PurePath = DEFAULT_settings_path,
179 state: SettingState = SettingState.FILE) -> None:
180 """Read the settings from an INI file."""
181 # Read file
182 file_settings = configparser.ConfigParser()
183 file_settings.read(file_path)
185 # Set internal settings based on data read from FILE if they were read
186 # successfully
187 if file_settings.sections() != []:
188 section = "general"
189 option_names = ("objective", )
190 for option in option_names:
191 if file_settings.has_option(section, option):
192 value = [resolve_objective(obj) for obj in
193 file_settings.get(section, option).split(",")]
194 self.set_general_sparkle_objectives(value, state)
195 file_settings.remove_option(section, option)
197 # Comma so python understands it's a tuple...
198 option_names = ("configurator", )
199 for option in option_names:
200 if file_settings.has_option(section, option):
201 value = file_settings.get(section, option)
202 self.set_general_sparkle_configurator(value, state)
203 file_settings.remove_option(section, option)
205 option_names = ("selector", )
206 for option in option_names:
207 if file_settings.has_option(section, option):
208 value = file_settings.get(section, option)
209 self.set_general_sparkle_selector(value, state)
210 file_settings.remove_option(section, option)
212 option_names = ("solution_verifier", )
213 for option in option_names:
214 if file_settings.has_option(section, option):
215 value = file_settings.get(section, option).lower()
216 self.set_general_solution_verifier(value, state)
217 file_settings.remove_option(section, option)
219 option_names = ("target_cutoff_time",
220 "cutoff_time_each_solver_call")
221 for option in option_names:
222 if file_settings.has_option(section, option):
223 value = file_settings.getint(section, option)
224 self.set_general_target_cutoff_time(value, state)
225 file_settings.remove_option(section, option)
227 option_names = ("extractor_cutoff_time",
228 "cutoff_time_each_feature_computation")
229 for option in option_names:
230 if file_settings.has_option(section, option):
231 value = file_settings.getint(section, option)
232 self.set_general_extractor_cutoff_time(value, state)
233 file_settings.remove_option(section, option)
235 option_names = ("run_on", )
236 for option in option_names:
237 if file_settings.has_option(section, option):
238 value = file_settings.get(section, option)
239 self.set_run_on(value, state)
240 file_settings.remove_option(section, option)
242 option_names = ("verbosity", )
243 for option in option_names:
244 if file_settings.has_option(section, option):
245 value = VerbosityLevel.from_string(
246 file_settings.get(section, option))
247 self.set_general_verbosity(value, state)
248 file_settings.remove_option(section, option)
250 option_names = ("check_interval", )
251 for option in option_names:
252 if file_settings.has_option(section, option):
253 value = int(file_settings.get(section, option))
254 self.set_general_check_interval(value, state)
255 file_settings.remove_option(section, option)
257 section = "configuration"
258 option_names = ("wallclock_time", )
259 for option in option_names:
260 if file_settings.has_option(section, option):
261 value = file_settings.getint(section, option)
262 self.set_config_wallclock_time(value, state)
263 file_settings.remove_option(section, option)
265 option_names = ("cpu_time", )
266 for option in option_names:
267 if file_settings.has_option(section, option):
268 value = file_settings.getint(section, option)
269 self.set_config_cpu_time(value, state)
270 file_settings.remove_option(section, option)
272 option_names = ("solver_calls", )
273 for option in option_names:
274 if file_settings.has_option(section, option):
275 value = file_settings.getint(section, option)
276 self.set_config_solver_calls(value, state)
277 file_settings.remove_option(section, option)
279 option_names = ("number_of_runs", )
280 for option in option_names:
281 if file_settings.has_option(section, option):
282 value = file_settings.getint(section, option)
283 self.set_config_number_of_runs(value, state)
284 file_settings.remove_option(section, option)
286 option_names = ("target_cutoff_length", "smac_each_run_cutoff_length")
287 for option in option_names:
288 if file_settings.has_option(section, option):
289 value = file_settings.get(section, option)
290 self.set_configurator_target_cutoff_length(value, state)
291 file_settings.remove_option(section, option)
293 section = "slurm"
294 option_names = ("number_of_jobs_in_parallel", "num_job_in_parallel")
295 for option in option_names:
296 if file_settings.has_option(section, option):
297 value = file_settings.getint(section, option)
298 self.set_number_of_jobs_in_parallel(value, state)
299 file_settings.remove_option(section, option)
301 option_names = ("max_parallel_runs_per_node", "clis_per_node")
302 for option in option_names:
303 if file_settings.has_option(section, option):
304 value = file_settings.getint(section, option)
305 self.set_slurm_max_parallel_runs_per_node(value, state)
306 file_settings.remove_option(section, option)
308 section = "ablation"
309 option_names = ("racing", "ablation_racing")
310 for option in option_names:
311 if file_settings.has_option(section, option):
312 value = file_settings.getboolean(section, option)
313 self.set_ablation_racing_flag(value, state)
314 file_settings.remove_option(section, option)
316 section = "parallel_portfolio"
317 option_names = ("check_interval", )
318 for option in option_names:
319 if file_settings.has_option(section, option):
320 value = int(file_settings.get(section, option))
321 self.set_parallel_portfolio_check_interval(value, state)
322 file_settings.remove_option(section, option)
324 option_names = ("num_seeds_per_solver", )
325 for option in option_names:
326 if file_settings.has_option(section, option):
327 value = int(file_settings.get(section, option))
328 self.set_parallel_portfolio_number_of_seeds_per_solver(value, state)
329 file_settings.remove_option(section, option)
331 # TODO: Report on any unknown settings that were read
332 sections = file_settings.sections()
334 for section in sections:
335 for option in file_settings[section]:
336 # TODO: Should check the options are valid Slurm options
337 if section == "slurm":
338 value = file_settings.get(section, option)
339 self.add_slurm_extra_option(option, value, state)
340 else:
341 print(f'Unrecognised section - option combination: "{section} '
342 f'{option}" in file {file_path} ignored')
344 # Print error if unable to read the settings
345 else:
346 print(f"ERROR: Failed to read settings from {file_path} The file may have "
347 "been empty, located in a different path, or be in another format than"
348 " INI. Default Settings values be used.")
350 def write_used_settings(self: Settings) -> None:
351 """Write the used settings to the default locations."""
352 # Write to latest settings file
353 self.write_settings_ini(self.__settings_dir / "latest.ini")
355 def write_settings_ini(self: Settings, file_path: Path) -> None:
356 """Write the settings to an INI file."""
357 # Create needed directories if they don't exist
358 file_path.parent.mkdir(parents=True, exist_ok=True)
359 slurm_extra_section_options = None
360 if self.__settings.has_section("slurm_extra"):
361 # Slurm extra options are not written as a seperate section
362 slurm_extra_section_options = {}
363 for key in self.__settings["slurm_extra"]:
364 self.__settings["slurm"][key] = self.__settings["slurm_extra"][key]
365 slurm_extra_section_options[key] = self.__settings["slurm_extra"][key]
366 self.__settings.remove_section("slurm_extra")
367 # Write the settings to file
368 with file_path.open("w") as settings_file:
369 self.__settings.write(settings_file)
370 # Rebuild slurm extra if needed
371 if slurm_extra_section_options is not None:
372 self.__settings.add_section("slurm_extra")
373 for key in slurm_extra_section_options:
374 self.__settings["slurm_extra"][key] = slurm_extra_section_options[key]
376 def __init_section(self: Settings, section: str) -> None:
377 if section not in self.__settings:
378 self.__settings[section] = {}
380 @staticmethod
381 def __check_setting_state(current_state: SettingState,
382 new_state: SettingState, name: str) -> bool:
383 change_setting_ok = True
385 if current_state == SettingState.FILE and new_state == SettingState.DEFAULT:
386 change_setting_ok = False
387 print(f"Warning: Attempting to overwrite setting for {name} with default "
388 "value; keeping the value read from file!")
389 elif (current_state == SettingState.CMD_LINE
390 and new_state == SettingState.DEFAULT):
391 change_setting_ok = False
392 print(f"Warning: Attempting to overwrite setting for {name} with default "
393 "value; keeping the value read from command line!")
394 elif current_state == SettingState.CMD_LINE and new_state == SettingState.FILE:
395 change_setting_ok = False
396 print(f"Warning: Attempting to overwrite setting for {name} with value from "
397 "file; keeping the value read from command line!")
399 return change_setting_ok
401 # General settings ###
402 def set_general_sparkle_objectives(
403 self: Settings,
404 value: list[SparkleObjective] = [DEFAULT_general_sparkle_objective, ],
405 origin: SettingState = SettingState.DEFAULT) -> None:
406 """Set the sparkle objective."""
407 section = "general"
408 name = "objective"
409 if value is not None and self.__check_setting_state(
410 self.__general_sparkle_objective_set, origin, name):
411 if isinstance(value, list):
412 value = ",".join([str(obj) for obj in value])
413 else:
414 value = str(value)
415 # Append standard Sparkle Objectives
416 if "status" not in value:
417 value += ",status"
418 if "cpu_time" not in value:
419 value += ",cpu_time"
420 if "wall_time" not in value:
421 value += ",wall_time"
422 if "memory" not in value:
423 value += ",memory"
424 self.__init_section(section)
425 self.__general_sparkle_objective_set = origin
426 self.__settings[section][name] = value
428 def get_general_sparkle_objectives(self: Settings) -> list[SparkleObjective]:
429 """Return the performance measure."""
430 if self.__general_sparkle_objective_set == SettingState.NOT_SET:
431 self.set_general_sparkle_objectives()
433 return [resolve_objective(obj)
434 for obj in self.__settings["general"]["objective"].split(",")]
436 def set_general_sparkle_configurator(
437 self: Settings,
438 value: str = DEFAULT_general_sparkle_configurator,
439 origin: SettingState = SettingState.DEFAULT) -> None:
440 """Set the Sparkle configurator."""
441 section = "general"
442 name = "configurator"
443 if value is not None and self.__check_setting_state(
444 self.__general_sparkle_configurator_set, origin, name):
445 self.__init_section(section)
446 self.__general_sparkle_configurator_set = origin
447 self.__settings[section][name] = value
449 def get_general_sparkle_configurator(self: Settings) -> Configurator:
450 """Return the configurator init method."""
451 if self.__general_sparkle_configurator_set == SettingState.NOT_SET:
452 self.set_general_sparkle_configurator()
453 if self.__general_sparkle_configurator is None:
454 configurator_subclass =\
455 cim.resolve_configurator(self.__settings["general"]["configurator"])
456 if configurator_subclass is not None:
457 self.__general_sparkle_configurator = configurator_subclass(
458 objectives=self.get_general_sparkle_objectives(),
459 base_dir=Settings.DEFAULT_tmp_output,
460 output_path=Settings.DEFAULT_configuration_output_raw)
461 else:
462 print("WARNING: Configurator class name not recognised:"
463 f'{self.__settings["general"]["configurator"]}. '
464 "Configurator not set.")
465 return self.__general_sparkle_configurator
467 def set_general_sparkle_selector(
468 self: Settings,
469 value: Path = DEFAULT_general_sparkle_selector,
470 origin: SettingState = SettingState.DEFAULT) -> None:
471 """Set the Sparkle selector."""
472 section = "general"
473 name = "selector"
474 if value is not None and self.__check_setting_state(
475 self.__general_sparkle_selector_set, origin, name):
476 self.__init_section(section)
477 self.__general_sparkle_selector_set = origin
478 self.__settings[section][name] = str(value)
480 def get_general_sparkle_selector(self: Settings) -> Selector:
481 """Return the selector init method."""
482 if self.__general_sparkle_selector_set == SettingState.NOT_SET:
483 self.set_general_sparkle_selector()
484 return Selector(Path(self.__settings["general"]["selector"]),
485 self.DEFAULT_selection_output_raw)
487 def set_general_solution_verifier(
488 self: Settings, value: str = DEFAULT_general_solution_verifier,
489 origin: SettingState = SettingState.DEFAULT) -> None:
490 """Set the solution verifier to use."""
491 section = "general"
492 name = "solution_verifier"
494 if value is not None and self.__check_setting_state(
495 self.__general_solution_verifier_set, origin, name):
496 self.__init_section(section)
497 self.__general_solution_verifier_set = origin
498 self.__settings[section][name] = value
500 def get_general_solution_verifier(self: Settings) -> object:
501 """Return the solution verifier to use."""
502 if self.__general_solution_verifier_set == SettingState.NOT_SET:
503 self.set_general_solution_verifier()
504 name = self.__settings["general"]["solution_verifier"].lower()
505 if name == str(SATVerifier()).lower():
506 return SATVerifier()
507 return None
509 def set_general_target_cutoff_time(
510 self: Settings, value: int = DEFAULT_general_target_cutoff_time,
511 origin: SettingState = SettingState.DEFAULT) -> None:
512 """Set the cutoff time in seconds for target algorithms."""
513 section = "general"
514 name = "target_cutoff_time"
516 if value is not None and self.__check_setting_state(
517 self.__general_target_cutoff_time_set, origin, name):
518 self.__init_section(section)
519 self.__general_target_cutoff_time_set = origin
520 self.__settings[section][name] = str(value)
522 def get_general_target_cutoff_time(self: Settings) -> int:
523 """Return the cutoff time in seconds for target algorithms."""
524 if self.__general_target_cutoff_time_set == SettingState.NOT_SET:
525 self.set_general_target_cutoff_time()
526 return int(self.__settings["general"]["target_cutoff_time"])
528 def set_general_extractor_cutoff_time(
529 self: Settings, value: int = DEFAULT_general_extractor_cutoff_time,
530 origin: SettingState = SettingState.DEFAULT) -> None:
531 """Set the cutoff time in seconds for feature extraction."""
532 section = "general"
533 name = "extractor_cutoff_time"
535 if value is not None and self.__check_setting_state(
536 self.__general_extractor_cutoff_time_set, origin, name):
537 self.__init_section(section)
538 self.__general_extractor_cutoff_time_set = origin
539 self.__settings[section][name] = str(value)
541 def get_general_extractor_cutoff_time(self: Settings) -> int:
542 """Return the cutoff time in seconds for feature extraction."""
543 if self.__general_extractor_cutoff_time_set == SettingState.NOT_SET:
544 self.set_general_extractor_cutoff_time()
545 return int(self.__settings["general"]["extractor_cutoff_time"])
547 def set_number_of_jobs_in_parallel(
548 self: Settings, value: int = DEFAULT_number_of_jobs_in_parallel,
549 origin: SettingState = SettingState.DEFAULT) -> None:
550 """Set the number of runs Sparkle can do in parallel."""
551 section = "slurm"
552 name = "number_of_jobs_in_parallel"
554 if value is not None and self.__check_setting_state(
555 self.__number_of_jobs_in_parallel_set, origin, name):
556 self.__init_section(section)
557 self.__number_of_jobs_in_parallel_set = origin
558 self.__settings[section][name] = str(value)
560 def get_number_of_jobs_in_parallel(self: Settings) -> int:
561 """Return the number of runs Sparkle can do in parallel."""
562 if self.__number_of_jobs_in_parallel_set == SettingState.NOT_SET:
563 self.set_number_of_jobs_in_parallel()
565 return int(self.__settings["slurm"]["number_of_jobs_in_parallel"])
567 def set_general_verbosity(
568 self: Settings, value: VerbosityLevel = DEFAULT_general_verbosity,
569 origin: SettingState = SettingState.DEFAULT) -> None:
570 """Set the general verbosity to use."""
571 section = "general"
572 name = "verbosity"
574 if value is not None and self.__check_setting_state(
575 self.__general_verbosity_set, origin, name):
576 self.__init_section(section)
577 self.__general_verbosity_set = origin
578 self.__settings[section][name] = value.name
580 def get_general_verbosity(self: Settings) -> VerbosityLevel:
581 """Return the general verbosity."""
582 if self.__general_verbosity_set == SettingState.NOT_SET:
583 self.set_general_verbosity()
585 return VerbosityLevel.from_string(
586 self.__settings["general"]["verbosity"])
588 def set_general_check_interval(
589 self: Settings,
590 value: int = DEFAULT_general_check_interval,
591 origin: SettingState = SettingState.DEFAULT) -> None:
592 """Set the general check interval."""
593 section = "general"
594 name = "check_interval"
596 if value is not None and self.__check_setting_state(
597 self.__general_check_interval_set, origin, name):
598 self.__init_section(section)
599 self.__general_check_interval_set = origin
600 self.__settings[section][name] = str(value)
602 def get_general_check_interval(self: Settings) -> int:
603 """Return the general check interval."""
604 if self.__general_check_interval_set == SettingState.NOT_SET:
605 self.set_general_check_interval()
607 return int(
608 self.__settings["general"]["check_interval"])
610 # Configuration settings ###
612 def set_config_wallclock_time(
613 self: Settings, value: int = DEFAULT_config_wallclock_time,
614 origin: SettingState = SettingState.DEFAULT) -> None:
615 """Set the budget per configuration run in seconds (wallclock)."""
616 section = "configuration"
617 name = "wallclock_time"
619 if value is not None and self.__check_setting_state(
620 self.__config_wallclock_time_set, origin, name):
621 self.__init_section(section)
622 self.__config_wallclock_time_set = origin
623 self.__settings[section][name] = str(value)
625 def get_config_wallclock_time(self: Settings) -> int:
626 """Return the budget per configuration run in seconds (wallclock)."""
627 if self.__config_wallclock_time_set == SettingState.NOT_SET:
628 self.set_config_wallclock_time()
629 return int(self.__settings["configuration"]["wallclock_time"])
631 def set_config_cpu_time(
632 self: Settings, value: int = DEFAULT_config_cpu_time,
633 origin: SettingState = SettingState.DEFAULT) -> None:
634 """Set the budget per configuration run in seconds (cpu)."""
635 section = "configuration"
636 name = "cpu_time"
638 if value is not None and self.__check_setting_state(
639 self.__config_cpu_time_set, origin, name):
640 self.__init_section(section)
641 self.__config_cpu_time_set = origin
642 self.__settings[section][name] = str(value)
644 def get_config_cpu_time(self: Settings) -> int | None:
645 """Return the budget per configuration run in seconds (cpu)."""
646 if self.__config_cpu_time_set == SettingState.NOT_SET:
647 self.set_config_cpu_time()
648 return None
650 return int(self.__settings["configuration"]["cpu_time"])
652 def set_config_solver_calls(
653 self: Settings, value: int = DEFAULT_config_solver_calls,
654 origin: SettingState = SettingState.DEFAULT) -> None:
655 """Set the number of solver calls."""
656 section = "configuration"
657 name = "solver_calls"
659 if value is not None and self.__check_setting_state(
660 self.__config_solver_calls_set, origin, name):
661 self.__init_section(section)
662 self.__config_solver_calls_set = origin
663 self.__settings[section][name] = str(value)
665 def get_config_solver_calls(self: Settings) -> int | None:
666 """Return the number of solver calls."""
667 if self.__config_solver_calls_set == SettingState.NOT_SET:
668 self.set_config_solver_calls()
669 return None
671 return int(self.__settings["configuration"]["solver_calls"])
673 def set_config_number_of_runs(
674 self: Settings, value: int = DEFAULT_config_number_of_runs,
675 origin: SettingState = SettingState.DEFAULT) -> None:
676 """Set the number of configuration runs."""
677 section = "configuration"
678 name = "number_of_runs"
680 if value is not None and self.__check_setting_state(
681 self.__config_number_of_runs_set, origin, name):
682 self.__init_section(section)
683 self.__config_number_of_runs_set = origin
684 self.__settings[section][name] = str(value)
686 def get_config_number_of_runs(self: Settings) -> int:
687 """Return the number of configuration runs."""
688 if self.__config_number_of_runs_set == SettingState.NOT_SET:
689 self.set_config_number_of_runs()
691 return int(self.__settings["configuration"]["number_of_runs"])
693 # Configuration: SMAC specific settings ###
695 def set_configurator_target_cutoff_length(
696 self: Settings, value: str = DEFAULT_configurator_target_cutoff_length,
697 origin: SettingState = SettingState.DEFAULT) -> None:
698 """Set the target algorithm cutoff length."""
699 section = "configuration"
700 name = "target_cutoff_length"
702 if value is not None and self.__check_setting_state(
703 self.__configurator_target_cutoff_length_set, origin, name):
704 self.__init_section(section)
705 self.__configurator_target_cutoff_length_set = origin
706 self.__settings[section][name] = str(value)
708 def get_configurator_target_cutoff_length(self: Settings) -> str:
709 """Return the target algorithm cutoff length."""
710 if self.__configurator_target_cutoff_length_set == SettingState.NOT_SET:
711 self.set_configurator_target_cutoff_length()
712 return self.__settings["configuration"]["target_cutoff_length"]
714 # Slurm settings ###
716 def set_slurm_max_parallel_runs_per_node(
717 self: Settings,
718 value: int = DEFAULT_slurm_max_parallel_runs_per_node,
719 origin: SettingState = SettingState.DEFAULT) -> None:
720 """Set the number of algorithms Slurm can run in parallel per node."""
721 section = "slurm"
722 name = "max_parallel_runs_per_node"
724 if value is not None and self.__check_setting_state(
725 self.__slurm_max_parallel_runs_per_node_set, origin, name):
726 self.__init_section(section)
727 self.__slurm_max_parallel_runs_per_node_set = origin
728 self.__settings[section][name] = str(value)
730 def get_slurm_max_parallel_runs_per_node(self: Settings) -> int:
731 """Return the number of algorithms Slurm can run in parallel per node."""
732 if self.__slurm_max_parallel_runs_per_node_set == SettingState.NOT_SET:
733 self.set_slurm_max_parallel_runs_per_node()
735 return int(self.__settings["slurm"]["max_parallel_runs_per_node"])
737 # SLURM extra options
739 def add_slurm_extra_option(self: Settings, name: str, value: str,
740 origin: SettingState = SettingState.DEFAULT) -> None:
741 """Add additional Slurm options."""
742 section = "slurm_extra"
744 current_state = (self.__slurm_extra_options_set[name]
745 if name in self.__slurm_extra_options_set
746 else SettingState.NOT_SET)
748 if value is not None and self.__check_setting_state(current_state, origin, name):
749 self.__init_section(section)
750 self.__slurm_extra_options_set[name] = origin
751 self.__settings[section][name] = str(value)
753 def get_slurm_extra_options(self: Settings,
754 as_args: bool = False) -> dict | list:
755 """Return a dict with additional Slurm options."""
756 section = "slurm_extra"
757 options = dict()
759 if "slurm_extra" in self.__settings.sections():
760 for option in self.__settings["slurm_extra"]:
761 options[option] = self.__settings.get(section, option)
762 if as_args:
763 return [f"--{key}={options[key]}" for key in options.keys()]
764 return options
766 # Ablation settings ###
768 def set_ablation_racing_flag(self: Settings, value: bool = DEFAULT_ablation_racing,
769 origin: SettingState = SettingState.DEFAULT) -> None:
770 """Set a flag indicating whether racing should be used for ablation."""
771 section = "ablation"
772 name = "racing"
774 if value is not None and self.__check_setting_state(
775 self.__ablation_racing_flag_set, origin, name):
776 self.__init_section(section)
777 self.__ablation_racing_flag_set = origin
778 self.__settings[section][name] = str(value)
780 def get_ablation_racing_flag(self: Settings) -> bool:
781 """Return a bool indicating whether the racing flag is set for ablation."""
782 if self.__ablation_racing_flag_set == SettingState.NOT_SET:
783 self.set_ablation_racing_flag()
785 return bool(self.__settings["ablation"]["racing"])
787 # Parallel Portfolio settings
789 def set_parallel_portfolio_check_interval(
790 self: Settings,
791 value: int = DEFAULT_parallel_portfolio_check_interval,
792 origin: SettingState = SettingState.DEFAULT) -> None:
793 """Set the parallel portfolio check interval."""
794 section = "parallel_portfolio"
795 name = "check_interval"
797 if value is not None and self.__check_setting_state(
798 self.__parallel_portfolio_check_interval_set, origin, name):
799 self.__init_section(section)
800 self.__parallel_portfolio_check_interval_set = origin
801 self.__settings[section][name] = str(value)
803 def get_parallel_portfolio_check_interval(self: Settings) -> int:
804 """Return the parallel portfolio check interval."""
805 if self.__parallel_portfolio_check_interval_set == SettingState.NOT_SET:
806 self.set_parallel_portfolio_check_interval()
808 return int(
809 self.__settings["parallel_portfolio"]["check_interval"])
811 def set_parallel_portfolio_number_of_seeds_per_solver(
812 self: Settings,
813 value: int = DEFAULT_parallel_portfolio_num_seeds_per_solver,
814 origin: SettingState = SettingState.DEFAULT) -> None:
815 """Set the parallel portfolio seeds per solver to start."""
816 section = "parallel_portfolio"
817 name = "num_seeds_per_solver"
819 if value is not None and self.__check_setting_state(
820 self.__parallel_portfolio_num_seeds_per_solver_set, origin, name):
821 self.__init_section(section)
822 self.__parallel_portfolio_num_seeds_per_solver_set = origin
823 self.__settings[section][name] = str(value)
825 def get_parallel_portfolio_number_of_seeds_per_solver(self: Settings) -> int:
826 """Return the parallel portfolio seeds per solver to start."""
827 if self.__parallel_portfolio_num_seeds_per_solver_set == SettingState.NOT_SET:
828 self.set_parallel_portfolio_number_of_seeds_per_solver()
830 return int(
831 self.__settings["parallel_portfolio"]["num_seeds_per_solver"])
833 def set_run_on(self: Settings, value: Runner = str,
834 origin: SettingState = SettingState.DEFAULT) -> None:
835 """Set the compute on which to run."""
836 section = "general"
837 name = "run_on"
839 if value is not None and self.__check_setting_state(
840 self.__run_on_set, origin, name):
841 self.__init_section(section)
842 self.__run_on_set = origin
843 self.__settings[section][name] = value
845 def get_run_on(self: Settings) -> Runner:
846 """Return the compute on which to run."""
847 return Runner(self.__settings["general"]["run_on"])
849 @staticmethod
850 def check_settings_changes(cur_settings: Settings, prev_settings: Settings) -> bool:
851 """Check if there are changes between the previous and the current settings.
853 Prints any section changes, printing None if no setting was found.
855 Args:
856 cur_settings: The current settings
857 prev_settings: The previous settings
859 Returns:
860 True iff there are no changes.
861 """
862 cur_dict = cur_settings.__settings._sections
863 prev_dict = prev_settings.__settings._sections
865 cur_sections_set = set(cur_dict.keys())
866 prev_sections_set = set(prev_dict.keys())
868 sections_removed = prev_sections_set - cur_sections_set
869 if sections_removed:
870 print("Warning: the following sections have been removed:")
871 for section in sections_removed:
872 print(f" - Section '{section}'")
874 sections_added = cur_sections_set - prev_sections_set
875 if sections_added:
876 print("Warning: the following sections have been added:")
877 for section in sections_added:
878 print(f" - Section '{section}'")
880 sections_remained = cur_sections_set & prev_sections_set
881 option_changed = False
882 for section in sections_remained:
883 printed_section = False
884 names = set(cur_dict[section].keys()) | set(prev_dict[section].keys())
885 for name in names:
886 # if name is not present in one of the two dicts, get None as placeholder
887 cur_val = cur_dict[section].get(name, None)
888 prev_val = prev_dict[section].get(name, None)
889 if cur_val != prev_val:
890 # Have we printed the initial warning?
891 if not option_changed:
892 print("Warning: The following attributes/options have changed:")
893 option_changed = True
895 # do we have yet to print the section?
896 if not printed_section:
897 print(f" - In the section '{section}':")
898 printed_section = True
900 # print actual change
901 print(f" · '{name}' changed from '{prev_val}' to '{cur_val}'")
903 return not (sections_removed or sections_added or option_changed)