Coverage for sparkle/platform/settings_objects.py: 90%
727 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"""Classes and Enums to control settings."""
2from __future__ import annotations
3import configparser
4from enum import Enum
5import ast
6from pathlib import Path
7from pathlib import PurePath
9from sparkle.types import SparkleObjective, resolve_objective
10from sparkle.types.objective import PAR
11from sparkle.solver import Selector
12from sparkle.configurator.configurator import Configurator
13from sparkle.solver.verifier import SATVerifier
14from sparkle.configurator import implementations as cim
16from runrunner import Runner
17from sparkle.platform.cli_types import VerbosityLevel
20class SettingState(Enum):
21 """Enum of possible setting states."""
23 NOT_SET = 0
24 DEFAULT = 1
25 FILE = 2
26 CMD_LINE = 3
29class Settings:
30 """Class to read, write, set, and get settings."""
31 # CWD Prefix
32 cwd_prefix = Path() # Empty for now
34 # Library prefix
35 lib_prefix = Path(__file__).parent.parent.resolve()
37 # Default directory names
38 rawdata_dir = Path("Raw_Data")
39 analysis_dir = Path("Analysis")
40 DEFAULT_settings_dir = Path("Settings")
41 __settings_file = Path("sparkle_settings.ini")
43 # Default settings path
44 DEFAULT_settings_path = PurePath(cwd_prefix / DEFAULT_settings_dir / __settings_file)
46 # Default library pathing
47 DEFAULT_components = lib_prefix / "Components"
49 # Example settings path
50 DEFAULT_example_settings_path = PurePath(DEFAULT_components / "sparkle_settings.ini")
52 # Runsolver component
53 DEFAULT_runsolver_dir = DEFAULT_components / "runsolver" / "src"
54 DEFAULT_runsolver_exec = DEFAULT_runsolver_dir / "runsolver"
56 # Ablation component
57 DEFAULT_ablation_dir = DEFAULT_components / "ablationAnalysis-0.9.4"
58 DEFAULT_ablation_exec = DEFAULT_ablation_dir / "ablationAnalysis"
59 DEFAULT_ablation_validation_exec = DEFAULT_ablation_dir / "ablationValidation"
61 # Autofolio component
62 DEFAULT_general_sparkle_selector = DEFAULT_components / "AutoFolio/scripts/autofolio"
64 # Report component
65 DEFAULT_latex_source = DEFAULT_components / "Sparkle-latex-source"
66 DEFAULT_latex_bib = DEFAULT_latex_source / "SparkleReport.bib"
68 # Default input directory pathing
69 DEFAULT_solver_dir = cwd_prefix / "Solvers"
70 DEFAULT_instance_dir = cwd_prefix / "Instances"
71 DEFAULT_extractor_dir = cwd_prefix / "Extractors"
72 DEFAULT_snapshot_dir = cwd_prefix / "Snapshots"
74 # Default output directory pathing
75 DEFAULT_tmp_output = cwd_prefix / "Tmp"
76 DEFAULT_output = cwd_prefix / "Output"
77 DEFAULT_configuration_output = DEFAULT_output / "Configuration"
78 DEFAULT_selection_output = DEFAULT_output / "Selection"
79 DEFAULT_validation_output = DEFAULT_output / "Validation"
80 DEFAULT_parallel_portfolio_output = DEFAULT_output / "Parallel_Portfolio"
81 DEFAULT_ablation_output = DEFAULT_output / "Ablation"
82 DEFAULT_log_output = DEFAULT_output / "Log"
84 # Default output subdirs
85 DEFAULT_configuration_output_raw = DEFAULT_configuration_output / rawdata_dir
86 DEFAULT_configuration_output_analysis = DEFAULT_configuration_output / analysis_dir
87 DEFAULT_selection_output_raw = DEFAULT_selection_output / rawdata_dir
88 DEFAULT_selection_output_analysis = DEFAULT_selection_output / analysis_dir
89 DEFAULT_parallel_portfolio_output_raw =\
90 DEFAULT_parallel_portfolio_output / rawdata_dir
91 DEFAULT_parallel_portfolio_output_analysis =\
92 DEFAULT_parallel_portfolio_output / analysis_dir
94 # Old default output dirs which should be part of something else
95 DEFAULT_feature_data = DEFAULT_output / "Feature_Data"
96 DEFAULT_performance_data = DEFAULT_output / "Performance_Data"
98 # Collection of all working dirs for platform
99 DEFAULT_working_dirs = [
100 DEFAULT_output, DEFAULT_configuration_output,
101 DEFAULT_selection_output, DEFAULT_validation_output,
102 DEFAULT_tmp_output, DEFAULT_log_output,
103 DEFAULT_solver_dir, DEFAULT_instance_dir,
104 DEFAULT_feature_data, DEFAULT_performance_data,
105 DEFAULT_extractor_dir, DEFAULT_settings_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
123 DEFAULT_general_run_on = "local"
125 DEFAULT_configurator_number_of_runs = 25
126 DEFAULT_configurator_solver_calls = 100
127 DEFAULT_configurator_maximum_iterations = None
129 DEFAULT_smac2_wallclock_time = None
130 DEFAULT_smac2_cpu_time = None
131 DEFAULT_smac2_target_cutoff_length = "max"
132 DEFAULT_smac2_use_cpu_time_in_tunertime = None
133 DEFAULT_smac2_max_iterations = None
135 DEFAULT_portfolio_construction_timeout = None
137 DEFAULT_slurm_max_parallel_runs_per_node = 8
139 DEFAULT_ablation_racing = False
141 DEFAULT_parallel_portfolio_check_interval = 4
142 DEFAULT_parallel_portfolio_num_seeds_per_solver = 1
144 # Default IRACE settings
145 DEFAULT_irace_max_time = 0 # IRACE equivalent of None in this case
146 DEFAULT_irace_max_experiments = 0
147 DEFAULT_irace_first_test = None
148 DEFAULT_irace_mu = None
149 DEFAULT_irace_max_iterations = None
151 def __init__(self: Settings, file_path: PurePath = None) -> None:
152 """Initialise a settings object."""
153 # Settings 'dictionary' in configparser format
154 self.__settings = configparser.ConfigParser()
156 # Setting flags
157 self.__general_sparkle_objective_set = SettingState.NOT_SET
158 self.__general_sparkle_configurator_set = SettingState.NOT_SET
159 self.__general_sparkle_selector_set = SettingState.NOT_SET
160 self.__general_solution_verifier_set = SettingState.NOT_SET
161 self.__general_target_cutoff_time_set = SettingState.NOT_SET
162 self.__general_extractor_cutoff_time_set = SettingState.NOT_SET
163 self.__general_verbosity_set = SettingState.NOT_SET
164 self.__general_check_interval_set = SettingState.NOT_SET
166 self.__config_solver_calls_set = SettingState.NOT_SET
167 self.__config_number_of_runs_set = SettingState.NOT_SET
168 self.__config_max_iterations_set = SettingState.NOT_SET
170 self.__smac2_wallclock_time_set = SettingState.NOT_SET
171 self.__smac2_cpu_time_set = SettingState.NOT_SET
172 self.__smac2_use_cpu_time_in_tunertime_set = SettingState.NOT_SET
173 self.__smac2_max_iterations_set = SettingState.NOT_SET
175 self.__run_on_set = SettingState.NOT_SET
176 self.__number_of_jobs_in_parallel_set = SettingState.NOT_SET
177 self.__slurm_max_parallel_runs_per_node_set = SettingState.NOT_SET
178 self.__smac2_target_cutoff_length_set = SettingState.NOT_SET
179 self.__ablation_racing_flag_set = SettingState.NOT_SET
181 self.__parallel_portfolio_check_interval_set = SettingState.NOT_SET
182 self.__parallel_portfolio_num_seeds_per_solver_set = SettingState.NOT_SET
184 self.__irace_max_time_set = SettingState.NOT_SET
185 self.__irace_max_experiments_set = SettingState.NOT_SET
186 self.__irace_first_test_set = SettingState.NOT_SET
187 self.__irace_mu_set = SettingState.NOT_SET
188 self.__irace_max_iterations_set = SettingState.NOT_SET
190 self.__general_sparkle_configurator = None
192 self.__slurm_extra_options_set = dict()
194 if file_path is None:
195 # Initialise settings from default file path
196 self.read_settings_ini()
197 else:
198 # Initialise settings from a given file path
199 self.read_settings_ini(file_path)
201 def read_settings_ini(self: Settings, file_path: PurePath = DEFAULT_settings_path,
202 state: SettingState = SettingState.FILE) -> None:
203 """Read the settings from an INI file."""
204 # Read file
205 file_settings = configparser.ConfigParser()
206 file_settings.read(file_path)
208 # Set internal settings based on data read from FILE if they were read
209 # successfully
210 if file_settings.sections() != []:
211 section = "general"
212 option_names = ("objective", )
213 for option in option_names:
214 if file_settings.has_option(section, option):
215 value = [resolve_objective(obj) for obj in
216 file_settings.get(section, option).split(",")]
217 self.set_general_sparkle_objectives(value, state)
218 file_settings.remove_option(section, option)
220 # Comma so python understands it's a tuple...
221 option_names = ("configurator", )
222 for option in option_names:
223 if file_settings.has_option(section, option):
224 value = file_settings.get(section, option)
225 self.set_general_sparkle_configurator(value, state)
226 file_settings.remove_option(section, option)
228 option_names = ("selector", )
229 for option in option_names:
230 if file_settings.has_option(section, option):
231 value = file_settings.get(section, option)
232 self.set_general_sparkle_selector(value, state)
233 file_settings.remove_option(section, option)
235 option_names = ("solution_verifier", )
236 for option in option_names:
237 if file_settings.has_option(section, option):
238 value = file_settings.get(section, option).lower()
239 self.set_general_solution_verifier(value, state)
240 file_settings.remove_option(section, option)
242 option_names = ("target_cutoff_time",
243 "cutoff_time_each_solver_call")
244 for option in option_names:
245 if file_settings.has_option(section, option):
246 value = file_settings.getint(section, option)
247 self.set_general_target_cutoff_time(value, state)
248 file_settings.remove_option(section, option)
250 option_names = ("extractor_cutoff_time",
251 "cutoff_time_each_feature_computation")
252 for option in option_names:
253 if file_settings.has_option(section, option):
254 value = file_settings.getint(section, option)
255 self.set_general_extractor_cutoff_time(value, state)
256 file_settings.remove_option(section, option)
258 option_names = ("run_on", )
259 for option in option_names:
260 if file_settings.has_option(section, option):
261 value = file_settings.get(section, option)
262 self.set_run_on(value, state)
263 file_settings.remove_option(section, option)
265 option_names = ("verbosity", )
266 for option in option_names:
267 if file_settings.has_option(section, option):
268 value = VerbosityLevel.from_string(
269 file_settings.get(section, option))
270 self.set_general_verbosity(value, state)
271 file_settings.remove_option(section, option)
273 option_names = ("check_interval", )
274 for option in option_names:
275 if file_settings.has_option(section, option):
276 value = int(file_settings.get(section, option))
277 self.set_general_check_interval(value, state)
278 file_settings.remove_option(section, option)
280 section = "configuration"
281 option_names = ("solver_calls", )
282 for option in option_names:
283 if file_settings.has_option(section, option):
284 value = file_settings.getint(section, option)
285 self.set_configurator_solver_calls(value, state)
286 file_settings.remove_option(section, option)
288 option_names = ("number_of_runs", )
289 for option in option_names:
290 if file_settings.has_option(section, option):
291 value = file_settings.getint(section, option)
292 self.set_configurator_number_of_runs(value, state)
293 file_settings.remove_option(section, option)
295 option_name = "max_iterations"
296 if file_settings.has_option(section, option_name):
297 value = file_settings.getint(section, option_name)
298 self.set_configurator_max_iterations(value, state)
299 file_settings.remove_option(section, option_name)
301 section = "smac2"
302 option_names = ("wallclock_time", )
303 for option in option_names:
304 if file_settings.has_option(section, option):
305 value = file_settings.getint(section, option)
306 self.set_smac2_wallclock_time(value, state)
307 file_settings.remove_option(section, option)
309 option_names = ("cpu_time", )
310 for option in option_names:
311 if file_settings.has_option(section, option):
312 value = file_settings.getint(section, option)
313 self.set_smac2_cpu_time(value, state)
314 file_settings.remove_option(section, option)
316 option_names = ("target_cutoff_length", "each_run_cutoff_length")
317 for option in option_names:
318 if file_settings.has_option(section, option):
319 value = file_settings.get(section, option)
320 self.set_smac2_target_cutoff_length(value, state)
321 file_settings.remove_option(section, option)
323 option_names = ("use_cpu_time_in_tunertime", "countSMACTimeAsTunerTime")
324 for option in option_names:
325 if file_settings.has_option(section, option):
326 value = file_settings.getboolean(section, option)
327 self.set_smac2_use_cpu_time_in_tunertime(value, state)
328 file_settings.remove_option(section, option)
330 options_names = ("iteration_limit", "numIterations", "numberOfIterations",
331 "max_iterations")
332 for option in options_names:
333 if file_settings.has_option(section, option):
334 value = file_settings.getint(section, option)
335 self.set_smac2_max_iterations(value, state)
336 file_settings.remove_option(section, option)
338 section = "irace"
339 option_names = ("max_time", )
340 for option in option_names:
341 if file_settings.has_option(section, option):
342 value = file_settings.getint(section, option)
343 self.set_irace_max_time(value, state)
344 file_settings.remove_option(section, option)
346 option_names = ("max_experiments", )
347 for option in option_names:
348 if file_settings.has_option(section, option):
349 value = file_settings.getint(section, option)
350 self.set_irace_max_experiments(value, state)
351 file_settings.remove_option(section, option)
353 option_names = ("first_test", )
354 for option in option_names:
355 if file_settings.has_option(section, option):
356 value = file_settings.getint(section, option)
357 self.set_irace_first_test(value, state)
358 file_settings.remove_option(section, option)
360 option_names = ("mu", )
361 for option in option_names:
362 if file_settings.has_option(section, option):
363 value = file_settings.getint(section, option)
364 self.set_irace_mu(value, state)
365 file_settings.remove_option(section, option)
367 option_names = ("nb_iterations", "iterations", "max_iterations")
368 for option in option_names:
369 if file_settings.has_option(section, option):
370 value = file_settings.getint(section, option)
371 self.set_irace_max_iterations(value, state)
372 file_settings.remove_option(section, option)
374 section = "slurm"
375 option_names = ("number_of_jobs_in_parallel", "num_job_in_parallel")
376 for option in option_names:
377 if file_settings.has_option(section, option):
378 value = file_settings.getint(section, option)
379 self.set_number_of_jobs_in_parallel(value, state)
380 file_settings.remove_option(section, option)
382 option_names = ("max_parallel_runs_per_node", "clis_per_node")
383 for option in option_names:
384 if file_settings.has_option(section, option):
385 value = file_settings.getint(section, option)
386 self.set_slurm_max_parallel_runs_per_node(value, state)
387 file_settings.remove_option(section, option)
389 section = "ablation"
390 option_names = ("racing", "ablation_racing")
391 for option in option_names:
392 if file_settings.has_option(section, option):
393 value = file_settings.getboolean(section, option)
394 self.set_ablation_racing_flag(value, state)
395 file_settings.remove_option(section, option)
397 section = "parallel_portfolio"
398 option_names = ("check_interval", )
399 for option in option_names:
400 if file_settings.has_option(section, option):
401 value = int(file_settings.get(section, option))
402 self.set_parallel_portfolio_check_interval(value, state)
403 file_settings.remove_option(section, option)
405 option_names = ("num_seeds_per_solver", )
406 for option in option_names:
407 if file_settings.has_option(section, option):
408 value = int(file_settings.get(section, option))
409 self.set_parallel_portfolio_number_of_seeds_per_solver(value, state)
410 file_settings.remove_option(section, option)
412 # TODO: Report on any unknown settings that were read
413 sections = file_settings.sections()
415 for section in sections:
416 for option in file_settings[section]:
417 # TODO: Should check the options are valid Slurm options
418 if section == "slurm":
419 value = file_settings.get(section, option)
420 self.add_slurm_extra_option(option, value, state)
421 else:
422 print(f'Unrecognised section - option combination: "{section} '
423 f'{option}" in file {file_path} ignored')
425 # Print error if unable to read the settings
426 else:
427 print(f"ERROR: Failed to read settings from {file_path} The file may have "
428 "been empty, located in a different path, or be in another format than"
429 " INI. Default Settings values be used.")
431 def write_used_settings(self: Settings) -> None:
432 """Write the used settings to the default locations."""
433 # Write to latest settings file
434 self.write_settings_ini(self.DEFAULT_settings_dir / "latest.ini")
436 def write_settings_ini(self: Settings, file_path: Path) -> None:
437 """Write the settings to an INI file."""
438 # Create needed directories if they don't exist
439 file_path.parent.mkdir(parents=True, exist_ok=True)
440 slurm_extra_section_options = None
441 if self.__settings.has_section("slurm_extra"):
442 # Slurm extra options are not written as a seperate section
443 slurm_extra_section_options = {}
444 for key in self.__settings["slurm_extra"]:
445 self.__settings["slurm"][key] = self.__settings["slurm_extra"][key]
446 slurm_extra_section_options[key] = self.__settings["slurm_extra"][key]
447 self.__settings.remove_section("slurm_extra")
448 # We do not write None values
449 removed = []
450 for section in self.__settings.sections():
451 for option in self.__settings[section]:
452 try:
453 if ast.literal_eval(str(self.__settings[section][option])) is None:
454 del self.__settings[section][option]
455 removed.append((section, option))
456 except Exception:
457 pass
458 # Write the settings to file
459 with file_path.open("w") as settings_file:
460 self.__settings.write(settings_file)
461 # Rebuild slurm extra if needed
462 if slurm_extra_section_options is not None:
463 self.__settings.add_section("slurm_extra")
464 for key in slurm_extra_section_options:
465 self.__settings["slurm_extra"][key] = slurm_extra_section_options[key]
466 # Rebuild None if needed
467 for section, option in removed:
468 self.__settings[section][option] = "None"
470 def __init_section(self: Settings, section: str) -> None:
471 if section not in self.__settings:
472 self.__settings[section] = {}
474 @staticmethod
475 def __check_setting_state(current_state: SettingState,
476 new_state: SettingState, name: str) -> bool:
477 change_setting_ok = True
479 if current_state == SettingState.FILE and new_state == SettingState.DEFAULT:
480 change_setting_ok = False
481 print(f"Warning: Attempting to overwrite setting for {name} with default "
482 "value; keeping the value read from file!")
483 elif (current_state == SettingState.CMD_LINE
484 and new_state == SettingState.DEFAULT):
485 change_setting_ok = False
486 print(f"Warning: Attempting to overwrite setting for {name} with default "
487 "value; keeping the value read from command line!")
488 elif current_state == SettingState.CMD_LINE and new_state == SettingState.FILE:
489 change_setting_ok = False
490 print(f"Warning: Attempting to overwrite setting for {name} with value from "
491 "file; keeping the value read from command line!")
493 return change_setting_ok
495 # General settings ###
496 def set_general_sparkle_objectives(
497 self: Settings,
498 value: list[SparkleObjective] = [DEFAULT_general_sparkle_objective, ],
499 origin: SettingState = SettingState.DEFAULT) -> None:
500 """Set the sparkle objective."""
501 section = "general"
502 name = "objective"
503 if value is not None and self.__check_setting_state(
504 self.__general_sparkle_objective_set, origin, name):
505 if isinstance(value, list):
506 value = ",".join([str(obj) for obj in value])
507 else:
508 value = str(value)
509 # Append standard Sparkle Objectives
510 if "status" not in value:
511 value += ",status"
512 if "cpu_time" not in value:
513 value += ",cpu_time"
514 if "wall_time" not in value:
515 value += ",wall_time"
516 if "memory" not in value:
517 value += ",memory"
518 self.__init_section(section)
519 self.__general_sparkle_objective_set = origin
520 self.__settings[section][name] = value
522 def get_general_sparkle_objectives(self: Settings) -> list[SparkleObjective]:
523 """Return the performance measure."""
524 if self.__general_sparkle_objective_set == SettingState.NOT_SET:
525 self.set_general_sparkle_objectives()
527 return [resolve_objective(obj)
528 for obj in self.__settings["general"]["objective"].split(",")]
530 def set_general_sparkle_configurator(
531 self: Settings,
532 value: str = DEFAULT_general_sparkle_configurator,
533 origin: SettingState = SettingState.DEFAULT) -> None:
534 """Set the Sparkle configurator."""
535 section = "general"
536 name = "configurator"
537 if value is not None and self.__check_setting_state(
538 self.__general_sparkle_configurator_set, origin, name):
539 self.__init_section(section)
540 self.__general_sparkle_configurator_set = origin
541 self.__settings[section][name] = value
543 def get_general_sparkle_configurator(self: Settings) -> Configurator:
544 """Return the configurator init method."""
545 if self.__general_sparkle_configurator_set == SettingState.NOT_SET:
546 self.set_general_sparkle_configurator()
547 configurator_var = self.__settings["general"]["configurator"]
548 if (self.__general_sparkle_configurator is None
549 or self.__general_sparkle_configurator.name != configurator_var):
550 configurator_subclass =\
551 cim.resolve_configurator(self.__settings["general"]["configurator"])
552 if configurator_subclass is not None:
553 self.__general_sparkle_configurator = configurator_subclass(
554 base_dir=Path(),
555 output_path=Settings.DEFAULT_configuration_output_raw)
556 else:
557 print("WARNING: Configurator class name not recognised: "
558 f'{self.__settings["general"]["configurator"]}. '
559 "Configurator not set.")
560 return self.__general_sparkle_configurator
562 def set_general_sparkle_selector(
563 self: Settings,
564 value: Path = DEFAULT_general_sparkle_selector,
565 origin: SettingState = SettingState.DEFAULT) -> None:
566 """Set the Sparkle selector."""
567 section = "general"
568 name = "selector"
569 if value is not None and self.__check_setting_state(
570 self.__general_sparkle_selector_set, origin, name):
571 self.__init_section(section)
572 self.__general_sparkle_selector_set = origin
573 self.__settings[section][name] = str(value)
575 def get_general_sparkle_selector(self: Settings) -> Selector:
576 """Return the selector init method."""
577 if self.__general_sparkle_selector_set == SettingState.NOT_SET:
578 self.set_general_sparkle_selector()
579 return Selector(Path(self.__settings["general"]["selector"]),
580 self.DEFAULT_selection_output_raw)
582 def set_general_solution_verifier(
583 self: Settings, value: str = DEFAULT_general_solution_verifier,
584 origin: SettingState = SettingState.DEFAULT) -> None:
585 """Set the solution verifier to use."""
586 section = "general"
587 name = "solution_verifier"
589 if value is not None and self.__check_setting_state(
590 self.__general_solution_verifier_set, origin, name):
591 self.__init_section(section)
592 self.__general_solution_verifier_set = origin
593 self.__settings[section][name] = value
595 def get_general_solution_verifier(self: Settings) -> object:
596 """Return the solution verifier to use."""
597 if self.__general_solution_verifier_set == SettingState.NOT_SET:
598 self.set_general_solution_verifier()
599 name = self.__settings["general"]["solution_verifier"].lower()
600 if name == str(SATVerifier()).lower():
601 return SATVerifier()
602 return None
604 def set_general_target_cutoff_time(
605 self: Settings, value: int = DEFAULT_general_target_cutoff_time,
606 origin: SettingState = SettingState.DEFAULT) -> None:
607 """Set the cutoff time in seconds for target algorithms."""
608 section = "general"
609 name = "target_cutoff_time"
611 if value is not None and self.__check_setting_state(
612 self.__general_target_cutoff_time_set, origin, name):
613 self.__init_section(section)
614 self.__general_target_cutoff_time_set = origin
615 self.__settings[section][name] = str(value)
617 def get_general_target_cutoff_time(self: Settings) -> int:
618 """Return the cutoff time in seconds for target algorithms."""
619 if self.__general_target_cutoff_time_set == SettingState.NOT_SET:
620 self.set_general_target_cutoff_time()
621 return int(self.__settings["general"]["target_cutoff_time"])
623 def set_general_extractor_cutoff_time(
624 self: Settings, value: int = DEFAULT_general_extractor_cutoff_time,
625 origin: SettingState = SettingState.DEFAULT) -> None:
626 """Set the cutoff time in seconds for feature extraction."""
627 section = "general"
628 name = "extractor_cutoff_time"
630 if value is not None and self.__check_setting_state(
631 self.__general_extractor_cutoff_time_set, origin, name):
632 self.__init_section(section)
633 self.__general_extractor_cutoff_time_set = origin
634 self.__settings[section][name] = str(value)
636 def get_general_extractor_cutoff_time(self: Settings) -> int:
637 """Return the cutoff time in seconds for feature extraction."""
638 if self.__general_extractor_cutoff_time_set == SettingState.NOT_SET:
639 self.set_general_extractor_cutoff_time()
640 return int(self.__settings["general"]["extractor_cutoff_time"])
642 def set_number_of_jobs_in_parallel(
643 self: Settings, value: int = DEFAULT_number_of_jobs_in_parallel,
644 origin: SettingState = SettingState.DEFAULT) -> None:
645 """Set the number of runs Sparkle can do in parallel."""
646 section = "slurm"
647 name = "number_of_jobs_in_parallel"
649 if value is not None and self.__check_setting_state(
650 self.__number_of_jobs_in_parallel_set, origin, name):
651 self.__init_section(section)
652 self.__number_of_jobs_in_parallel_set = origin
653 self.__settings[section][name] = str(value)
655 def get_number_of_jobs_in_parallel(self: Settings) -> int:
656 """Return the number of runs Sparkle can do in parallel."""
657 if self.__number_of_jobs_in_parallel_set == SettingState.NOT_SET:
658 self.set_number_of_jobs_in_parallel()
660 return int(self.__settings["slurm"]["number_of_jobs_in_parallel"])
662 def set_general_verbosity(
663 self: Settings, value: VerbosityLevel = DEFAULT_general_verbosity,
664 origin: SettingState = SettingState.DEFAULT) -> None:
665 """Set the general verbosity to use."""
666 section = "general"
667 name = "verbosity"
669 if value is not None and self.__check_setting_state(
670 self.__general_verbosity_set, origin, name):
671 self.__init_section(section)
672 self.__general_verbosity_set = origin
673 self.__settings[section][name] = value.name
675 def get_general_verbosity(self: Settings) -> VerbosityLevel:
676 """Return the general verbosity."""
677 if self.__general_verbosity_set == SettingState.NOT_SET:
678 self.set_general_verbosity()
680 return VerbosityLevel.from_string(
681 self.__settings["general"]["verbosity"])
683 def set_general_check_interval(
684 self: Settings,
685 value: int = DEFAULT_general_check_interval,
686 origin: SettingState = SettingState.DEFAULT) -> None:
687 """Set the general check interval."""
688 section = "general"
689 name = "check_interval"
691 if value is not None and self.__check_setting_state(
692 self.__general_check_interval_set, origin, name):
693 self.__init_section(section)
694 self.__general_check_interval_set = origin
695 self.__settings[section][name] = str(value)
697 def get_general_check_interval(self: Settings) -> int:
698 """Return the general check interval."""
699 if self.__general_check_interval_set == SettingState.NOT_SET:
700 self.set_general_check_interval()
702 return int(self.__settings["general"]["check_interval"])
704 # Configuration settings General ###
706 def get_configurator_settings(self: Settings,
707 configurator_name: str) -> dict[str, any]:
708 """Return the configurator settings."""
709 configurator_settings = {
710 "number_of_runs": self.get_configurator_number_of_runs(),
711 "solver_calls": self.get_configurator_solver_calls(),
712 "cutoff_time": self.get_general_target_cutoff_time(),
713 "max_iterations": self.get_configurator_max_iterations()
714 }
715 # In the settings below, we default to the configurator general settings if no
716 # specific configurator settings are given, by using the [None] or [Value]
717 if configurator_name == cim.SMAC2.__name__:
718 # Return all settings from the SMAC2 section
719 configurator_settings.update({
720 "cpu_time": self.get_smac2_cpu_time(),
721 "wallclock_time": self.get_smac2_wallclock_time(),
722 "target_cutoff_length": self.get_smac2_target_cutoff_length(),
723 "use_cpu_time_in_tunertime": self.get_smac2_use_cpu_time_in_tunertime(),
724 "max_iterations": self.get_smac2_max_iterations()
725 or configurator_settings["max_iterations"],
726 })
727 if configurator_name == cim.IRACE.__name__:
728 # Return all settings from the IRACE section
729 configurator_settings.update({
730 "solver_calls": self.get_irace_max_experiments(),
731 "max_time": self.get_irace_max_time(),
732 "first_test": self.get_irace_first_test(),
733 "mu": self.get_irace_mu(),
734 "max_iterations": self.get_irace_max_iterations()
735 or configurator_settings["max_iterations"],
736 })
737 if (configurator_settings["solver_calls"] == 0
738 and configurator_settings["max_time"] == 0): # Default to base
739 configurator_settings["solver_calls"] =\
740 self.get_configurator_solver_calls()
741 return configurator_settings
743 def set_configurator_solver_calls(
744 self: Settings, value: int = DEFAULT_configurator_solver_calls,
745 origin: SettingState = SettingState.DEFAULT) -> None:
746 """Set the number of solver calls."""
747 section = "configuration"
748 name = "solver_calls"
750 if value is not None and self.__check_setting_state(
751 self.__config_solver_calls_set, origin, name):
752 self.__init_section(section)
753 self.__config_solver_calls_set = origin
754 self.__settings[section][name] = str(value)
756 def get_configurator_solver_calls(self: Settings) -> int | None:
757 """Return the maximum number of solver calls the configurator can do."""
758 if self.__config_solver_calls_set == SettingState.NOT_SET:
759 self.set_configurator_solver_calls()
761 return int(self.__settings["configuration"]["solver_calls"])
763 def set_configurator_number_of_runs(
764 self: Settings, value: int = DEFAULT_configurator_number_of_runs,
765 origin: SettingState = SettingState.DEFAULT) -> None:
766 """Set the number of configuration runs."""
767 section = "configuration"
768 name = "number_of_runs"
770 if value is not None and self.__check_setting_state(
771 self.__config_number_of_runs_set, origin, name):
772 self.__init_section(section)
773 self.__config_number_of_runs_set = origin
774 self.__settings[section][name] = str(value)
776 def get_configurator_number_of_runs(self: Settings) -> int:
777 """Return the number of configuration runs."""
778 if self.__config_number_of_runs_set == SettingState.NOT_SET:
779 self.set_configurator_number_of_runs()
781 return int(self.__settings["configuration"]["number_of_runs"])
783 def set_configurator_max_iterations(
784 self: Settings, value: int = DEFAULT_configurator_maximum_iterations,
785 origin: SettingState = SettingState.DEFAULT) -> None:
786 """Set the number of configuration runs."""
787 section = "configuration"
788 name = "max_iterations"
790 if self.__check_setting_state(
791 self.__config_max_iterations_set, origin, name):
792 self.__init_section(section)
793 self.__config_max_iterations_set = origin
794 self.__settings[section][name] = str(value)
796 def get_configurator_max_iterations(self: Settings) -> int | None:
797 """Get the maximum number of configurator iterations."""
798 if self.__config_max_iterations_set == SettingState.NOT_SET:
799 self.set_configurator_max_iterations()
800 max_iterations = self.__settings["configuration"]["max_iterations"]
801 return int(max_iterations) if max_iterations.isdigit() else None
803 # Configuration: SMAC specific settings ###
805 def set_smac2_wallclock_time(
806 self: Settings, value: int = DEFAULT_smac2_wallclock_time,
807 origin: SettingState = SettingState.DEFAULT) -> None:
808 """Set the budget per configuration run in seconds (wallclock)."""
809 section = "smac2"
810 name = "wallclock_time"
812 if self.__check_setting_state(
813 self.__smac2_wallclock_time_set, origin, name):
814 self.__init_section(section)
815 self.__smac2_wallclock_time_set = origin
816 self.__settings[section][name] = str(value)
818 def get_smac2_wallclock_time(self: Settings) -> int | None:
819 """Return the budget per configuration run in seconds (wallclock)."""
820 if self.__smac2_wallclock_time_set == SettingState.NOT_SET:
821 self.set_smac2_wallclock_time()
822 wallclock_time = self.__settings["smac2"]["wallclock_time"]
823 return int(wallclock_time) if wallclock_time.isdigit() else None
825 def set_smac2_cpu_time(
826 self: Settings, value: int = DEFAULT_smac2_cpu_time,
827 origin: SettingState = SettingState.DEFAULT) -> None:
828 """Set the budget per configuration run in seconds (cpu)."""
829 section = "smac2"
830 name = "cpu_time"
832 if self.__check_setting_state(
833 self.__smac2_cpu_time_set, origin, name):
834 self.__init_section(section)
835 self.__smac2_cpu_time_set = origin
836 self.__settings[section][name] = str(value)
838 def get_smac2_cpu_time(self: Settings) -> int | None:
839 """Return the budget per configuration run in seconds (cpu)."""
840 if self.__smac2_cpu_time_set == SettingState.NOT_SET:
841 self.set_smac2_cpu_time()
842 cpu_time = self.__settings["smac2"]["cpu_time"]
843 return int(cpu_time) if cpu_time.isdigit() else None
845 def set_smac2_target_cutoff_length(
846 self: Settings, value: str = DEFAULT_smac2_target_cutoff_length,
847 origin: SettingState = SettingState.DEFAULT) -> None:
848 """Set the target algorithm cutoff length."""
849 section = "smac2"
850 name = "target_cutoff_length"
852 if value is not None and self.__check_setting_state(
853 self.__smac2_target_cutoff_length_set, origin, name):
854 self.__init_section(section)
855 self.__smac2_target_cutoff_length_set = origin
856 self.__settings[section][name] = str(value)
858 def get_smac2_target_cutoff_length(self: Settings) -> str:
859 """Return the target algorithm cutoff length.
861 'A domain specific measure of when the algorithm should consider itself done.'
863 Returns:
864 The target algorithm cutoff length.
865 """
866 if self.__smac2_target_cutoff_length_set == SettingState.NOT_SET:
867 self.set_smac2_target_cutoff_length()
868 return self.__settings["smac2"]["target_cutoff_length"]
870 def set_smac2_use_cpu_time_in_tunertime(
871 self: Settings, value: bool = DEFAULT_smac2_use_cpu_time_in_tunertime,
872 origin: SettingState = SettingState.DEFAULT) -> None:
873 """Set whether to use CPU time in tunertime."""
874 section = "smac2"
875 name = "use_cpu_time_in_tunertime"
877 if self.__check_setting_state(
878 self.__smac2_use_cpu_time_in_tunertime_set, origin, name):
879 self.__init_section(section)
880 self.__smac2_use_cpu_time_in_tunertime_set = origin
881 self.__settings[section][name] = str(value)
883 def get_smac2_use_cpu_time_in_tunertime(self: Settings) -> bool:
884 """Return whether to use CPU time in tunertime."""
885 if self.__smac2_use_cpu_time_in_tunertime_set == SettingState.NOT_SET:
886 self.set_smac2_use_cpu_time_in_tunertime()
887 return ast.literal_eval(self.__settings["smac2"]["use_cpu_time_in_tunertime"])
889 def set_smac2_max_iterations(
890 self: Settings, value: int = DEFAULT_smac2_max_iterations,
891 origin: SettingState = SettingState.DEFAULT) -> None:
892 """Set the maximum number of SMAC2 iterations."""
893 section = "smac2"
894 name = "max_iterations"
896 if self.__check_setting_state(
897 self.__smac2_max_iterations_set, origin, name):
898 self.__init_section(section)
899 self.__smac2_max_iterations_set = origin
900 self.__settings[section][name] = str(value)
902 def get_smac2_max_iterations(self: Settings) -> int | None:
903 """Get the maximum number of SMAC2 iterations."""
904 if self.__smac2_max_iterations_set == SettingState.NOT_SET:
905 self.set_smac2_max_iterations()
906 max_iterations = self.__settings["smac2"]["max_iterations"]
907 return int(max_iterations) if max_iterations.isdigit() else None
909 # Configuration: IRACE specific settings ###
911 def get_irace_max_time(self: Settings) -> int:
912 """Return the max time in seconds for IRACE."""
913 if self.__irace_max_time_set == SettingState.NOT_SET:
914 self.set_irace_max_time()
915 return int(self.__settings["irace"]["max_time"])
917 def set_irace_max_time(
918 self: Settings, value: int = DEFAULT_irace_max_time,
919 origin: SettingState = SettingState.DEFAULT) -> None:
920 """Set the max time in seconds for IRACE."""
921 section = "irace"
922 name = "max_time"
924 if value is not None and self.__check_setting_state(
925 self.__irace_max_time_set, origin, name):
926 self.__init_section(section)
927 self.__irace_max_time_set = origin
928 self.__settings[section][name] = str(value)
930 def get_irace_max_experiments(self: Settings) -> int:
931 """Return the max number of experiments for IRACE."""
932 if self.__irace_max_experiments_set == SettingState.NOT_SET:
933 self.set_irace_max_experiments()
934 return int(self.__settings["irace"]["max_experiments"])
936 def set_irace_max_experiments(
937 self: Settings, value: int = DEFAULT_irace_max_experiments,
938 origin: SettingState = SettingState.DEFAULT) -> None:
939 """Set the max number of experiments for IRACE."""
940 section = "irace"
941 name = "max_experiments"
943 if value is not None and self.__check_setting_state(
944 self.__irace_max_experiments_set, origin, name):
945 self.__init_section(section)
946 self.__irace_max_experiments_set = origin
947 self.__settings[section][name] = str(value)
949 def get_irace_first_test(self: Settings) -> int | None:
950 """Return the first test for IRACE.
952 Specifies how many instances are evaluated before the first
953 elimination test. IRACE Default: 5. [firstTest]
954 """
955 if self.__irace_first_test_set == SettingState.NOT_SET:
956 self.set_irace_first_test()
957 first_test = self.__settings["irace"]["first_test"]
958 return int(first_test) if first_test.isdigit() else None
960 def set_irace_first_test(
961 self: Settings, value: int = DEFAULT_irace_first_test,
962 origin: SettingState = SettingState.DEFAULT) -> None:
963 """Set the first test for IRACE."""
964 section = "irace"
965 name = "first_test"
967 if self.__check_setting_state(
968 self.__irace_first_test_set, origin, name):
969 self.__init_section(section)
970 self.__irace_first_test_set = origin
971 self.__settings[section][name] = str(value)
973 def get_irace_mu(self: Settings) -> int | None:
974 """Return the mu for IRACE.
976 Parameter used to define the number of configurations sampled and
977 evaluated at each iteration. IRACE Default: 5. [mu]
978 """
979 if self.__irace_mu_set == SettingState.NOT_SET:
980 self.set_irace_mu()
981 mu = self.__settings["irace"]["mu"]
982 return int(mu) if mu.isdigit() else None
984 def set_irace_mu(
985 self: Settings, value: int = DEFAULT_irace_mu,
986 origin: SettingState = SettingState.DEFAULT) -> None:
987 """Set the mu for IRACE."""
988 section = "irace"
989 name = "mu"
991 if self.__check_setting_state(
992 self.__irace_mu_set, origin, name):
993 self.__init_section(section)
994 self.__irace_mu_set = origin
995 self.__settings[section][name] = str(value)
997 def get_irace_max_iterations(self: Settings) -> int:
998 """Return the number of iterations for IRACE."""
999 if self.__irace_max_iterations_set == SettingState.NOT_SET:
1000 self.set_irace_max_iterations()
1001 max_iterations = self.__settings["irace"]["max_iterations"]
1002 return int(max_iterations) if max_iterations.isdigit() else None
1004 def set_irace_max_iterations(
1005 self: Settings, value: int = DEFAULT_irace_max_iterations,
1006 origin: SettingState = SettingState.DEFAULT) -> None:
1007 """Set the number of iterations for IRACE.
1009 Maximum number of iterations to be executed. Each iteration involves the
1010 generation of new configurations and the use of racing to select the best
1011 configurations. By default (with 0), irace calculates a minimum number of
1012 iterations as N^iter = ⌊2 + log2 N param⌋, where N^param is the number of
1013 non-fixed parameters to be tuned.
1014 Setting this parameter may make irace stop sooner than it should without using
1015 all the available budget. IRACE recommends to use the default value (Empty).
1016 """
1017 section = "irace"
1018 name = "max_iterations"
1020 if self.__check_setting_state(
1021 self.__irace_max_iterations_set, origin, name):
1022 self.__init_section(section)
1023 self.__irace_max_iterations_set = origin
1024 self.__settings[section][name] = str(value)
1026 # Slurm settings ###
1028 def set_slurm_max_parallel_runs_per_node(
1029 self: Settings,
1030 value: int = DEFAULT_slurm_max_parallel_runs_per_node,
1031 origin: SettingState = SettingState.DEFAULT) -> None:
1032 """Set the number of algorithms Slurm can run in parallel per node."""
1033 section = "slurm"
1034 name = "max_parallel_runs_per_node"
1036 if value is not None and self.__check_setting_state(
1037 self.__slurm_max_parallel_runs_per_node_set, origin, name):
1038 self.__init_section(section)
1039 self.__slurm_max_parallel_runs_per_node_set = origin
1040 self.__settings[section][name] = str(value)
1042 def get_slurm_max_parallel_runs_per_node(self: Settings) -> int:
1043 """Return the number of algorithms Slurm can run in parallel per node."""
1044 if self.__slurm_max_parallel_runs_per_node_set == SettingState.NOT_SET:
1045 self.set_slurm_max_parallel_runs_per_node()
1047 return int(self.__settings["slurm"]["max_parallel_runs_per_node"])
1049 # SLURM extra options
1051 def add_slurm_extra_option(self: Settings, name: str, value: str,
1052 origin: SettingState = SettingState.DEFAULT) -> None:
1053 """Add additional Slurm options."""
1054 section = "slurm_extra"
1056 current_state = (self.__slurm_extra_options_set[name]
1057 if name in self.__slurm_extra_options_set
1058 else SettingState.NOT_SET)
1060 if value is not None and self.__check_setting_state(current_state, origin, name):
1061 self.__init_section(section)
1062 self.__slurm_extra_options_set[name] = origin
1063 self.__settings[section][name] = str(value)
1065 def get_slurm_extra_options(self: Settings,
1066 as_args: bool = False) -> dict | list:
1067 """Return a dict with additional Slurm options."""
1068 section = "slurm_extra"
1069 options = dict()
1071 if "slurm_extra" in self.__settings.sections():
1072 for option in self.__settings["slurm_extra"]:
1073 options[option] = self.__settings.get(section, option)
1074 if as_args:
1075 return [f"--{key}={options[key]}" for key in options.keys()]
1076 return options
1078 # Ablation settings ###
1080 def set_ablation_racing_flag(self: Settings, value: bool = DEFAULT_ablation_racing,
1081 origin: SettingState = SettingState.DEFAULT) -> None:
1082 """Set a flag indicating whether racing should be used for ablation."""
1083 section = "ablation"
1084 name = "racing"
1086 if value is not None and self.__check_setting_state(
1087 self.__ablation_racing_flag_set, origin, name):
1088 self.__init_section(section)
1089 self.__ablation_racing_flag_set = origin
1090 self.__settings[section][name] = str(value)
1092 def get_ablation_racing_flag(self: Settings) -> bool:
1093 """Return a bool indicating whether the racing flag is set for ablation."""
1094 if self.__ablation_racing_flag_set == SettingState.NOT_SET:
1095 self.set_ablation_racing_flag()
1097 return bool(self.__settings["ablation"]["racing"])
1099 # Parallel Portfolio settings
1101 def set_parallel_portfolio_check_interval(
1102 self: Settings,
1103 value: int = DEFAULT_parallel_portfolio_check_interval,
1104 origin: SettingState = SettingState.DEFAULT) -> None:
1105 """Set the parallel portfolio check interval."""
1106 section = "parallel_portfolio"
1107 name = "check_interval"
1109 if value is not None and self.__check_setting_state(
1110 self.__parallel_portfolio_check_interval_set, origin, name):
1111 self.__init_section(section)
1112 self.__parallel_portfolio_check_interval_set = origin
1113 self.__settings[section][name] = str(value)
1115 def get_parallel_portfolio_check_interval(self: Settings) -> int:
1116 """Return the parallel portfolio check interval."""
1117 if self.__parallel_portfolio_check_interval_set == SettingState.NOT_SET:
1118 self.set_parallel_portfolio_check_interval()
1120 return int(
1121 self.__settings["parallel_portfolio"]["check_interval"])
1123 def set_parallel_portfolio_number_of_seeds_per_solver(
1124 self: Settings,
1125 value: int = DEFAULT_parallel_portfolio_num_seeds_per_solver,
1126 origin: SettingState = SettingState.DEFAULT) -> None:
1127 """Set the parallel portfolio seeds per solver to start."""
1128 section = "parallel_portfolio"
1129 name = "num_seeds_per_solver"
1131 if value is not None and self.__check_setting_state(
1132 self.__parallel_portfolio_num_seeds_per_solver_set, origin, name):
1133 self.__init_section(section)
1134 self.__parallel_portfolio_num_seeds_per_solver_set = origin
1135 self.__settings[section][name] = str(value)
1137 def get_parallel_portfolio_number_of_seeds_per_solver(self: Settings) -> int:
1138 """Return the parallel portfolio seeds per solver to start."""
1139 if self.__parallel_portfolio_num_seeds_per_solver_set == SettingState.NOT_SET:
1140 self.set_parallel_portfolio_number_of_seeds_per_solver()
1142 return int(
1143 self.__settings["parallel_portfolio"]["num_seeds_per_solver"])
1145 def set_run_on(self: Settings, value: Runner = DEFAULT_general_run_on,
1146 origin: SettingState = SettingState.DEFAULT) -> None:
1147 """Set the compute on which to run."""
1148 section = "general"
1149 name = "run_on"
1151 if value is not None and self.__check_setting_state(
1152 self.__run_on_set, origin, name):
1153 self.__init_section(section)
1154 self.__run_on_set = origin
1155 self.__settings[section][name] = value
1157 def get_run_on(self: Settings) -> Runner:
1158 """Return the compute on which to run."""
1159 if self.__run_on_set == SettingState.NOT_SET:
1160 self.set_run_on()
1162 return Runner(self.__settings["general"]["run_on"])
1164 @staticmethod
1165 def check_settings_changes(cur_settings: Settings, prev_settings: Settings) -> bool:
1166 """Check if there are changes between the previous and the current settings.
1168 Prints any section changes, printing None if no setting was found.
1170 Args:
1171 cur_settings: The current settings
1172 prev_settings: The previous settings
1174 Returns:
1175 True iff there are no changes.
1176 """
1177 cur_dict = cur_settings.__settings._sections
1178 prev_dict = prev_settings.__settings._sections
1180 cur_sections_set = set(cur_dict.keys())
1181 prev_sections_set = set(prev_dict.keys())
1182 sections_removed = prev_sections_set - cur_sections_set
1183 if sections_removed:
1184 print("Warning: the following sections have been removed:")
1185 for section in sections_removed:
1186 print(f" - Section '{section}'")
1188 sections_added = cur_sections_set - prev_sections_set
1189 if sections_added:
1190 print("Warning: the following sections have been added:")
1191 for section in sections_added:
1192 print(f" - Section '{section}'")
1194 sections_remained = cur_sections_set & prev_sections_set
1195 option_changed = False
1196 for section in sections_remained:
1197 printed_section = False
1198 names = set(cur_dict[section].keys()) | set(prev_dict[section].keys())
1199 for name in names:
1200 # if name is not present in one of the two dicts, get None as placeholder
1201 cur_val = cur_dict[section].get(name, None)
1202 prev_val = prev_dict[section].get(name, None)
1204 # If cur val is None, it is default
1205 if cur_val is not None and cur_val != prev_val:
1206 # Have we printed the initial warning?
1207 if not option_changed:
1208 print("Warning: The following attributes/options have changed:")
1209 option_changed = True
1211 # do we have yet to print the section?
1212 if not printed_section:
1213 print(f" - In the section '{section}':")
1214 printed_section = True
1216 # print actual change
1217 print(f" · '{name}' changed from '{prev_val}' to '{cur_val}'")
1219 return not (sections_removed or sections_added or option_changed)