Coverage for sparkle/platform/settings_objects.py: 97%
577 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 10:17 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-09-29 10:17 +0000
1"""Classes and Enums to control settings."""
3from __future__ import annotations
4import configparser
5import argparse
6from enum import Enum
7from pathlib import Path
8from typing import Any, NamedTuple, Optional
10from runrunner import Runner
12from sparkle.types import SparkleObjective, resolve_objective
13from sparkle.configurator.configurator import Configurator
14from sparkle.configurator import implementations as cim
15from sparkle.platform.cli_types import VerbosityLevel
18class Option(NamedTuple):
19 """Class to define an option in the Settings."""
21 name: str
22 section: str
23 type: Any
24 default_value: Any
25 alternatives: tuple[str, ...]
26 help: str = ""
27 cli_kwargs: dict[str, Any] = {}
29 def __str__(self: Option) -> str:
30 """Return the option name."""
31 return self.name
33 def __eq__(self: Option, other: Any) -> bool:
34 """Check if two options are equal."""
35 if isinstance(other, Option):
36 return (
37 self.name == other.name
38 and self.section == other.section
39 and self.type == other.type
40 and self.default_value == other.default_value
41 and self.alternatives == other.alternatives
42 )
43 if isinstance(other, str):
44 return self.name == other or other in self.alternatives
45 return False
47 @property
48 def args(self: Option) -> list[str]:
49 """Return the option names as a command line arguments."""
50 return [
51 f"--{name.replace('_', '-')}"
52 for name in [self.name] + list(self.alternatives)
53 ]
55 @property
56 def kwargs(self: Option) -> dict[str, Any]:
57 """Return the option attributes as kwargs."""
58 kw = {"help": self.help, **self.cli_kwargs}
60 # If this option uses a boolean flag action, argparse must NOT receive 'type'
61 action = kw.get("action")
62 if action in ("store_true", "store_false"):
63 return kw
65 # Otherwise include the base 'type'
66 return {"type": self.type, **kw}
69class Settings:
70 """Class to read, write, set, and get settings."""
72 # CWD Prefix
73 cwd_prefix = Path() # Empty for now
75 # Library prefix
76 lib_prefix = Path(__file__).parent.parent.resolve()
78 # Default directory names
79 rawdata_dir = Path("Raw_Data")
80 analysis_dir = Path("Analysis")
81 DEFAULT_settings_dir = Path("Settings")
82 __settings_file = Path("sparkle_settings.ini")
83 __latest_settings_file = Path("latest.ini")
85 # Default settings path
86 DEFAULT_settings_path = Path(cwd_prefix / DEFAULT_settings_dir / __settings_file)
87 DEFAULT_previous_settings_path = Path(
88 cwd_prefix / DEFAULT_settings_dir / __latest_settings_file
89 )
90 DEFAULT_reference_dir = DEFAULT_settings_dir / "Reference_Lists"
92 # Default library pathing
93 DEFAULT_components = lib_prefix / "Components"
95 # Report Component: Bilbiography
96 bibliography_path = DEFAULT_components / "latex_source" / "report.bib"
98 # Example settings path
99 DEFAULT_example_settings_path = Path(DEFAULT_components / "sparkle_settings.ini")
101 # Runsolver component
102 DEFAULT_runsolver_dir = DEFAULT_components / "runsolver" / "src"
103 DEFAULT_runsolver_exec = DEFAULT_runsolver_dir / "runsolver"
105 # Ablation component
106 DEFAULT_ablation_dir = DEFAULT_components / "ablationAnalysis-0.9.4"
107 DEFAULT_ablation_exec = DEFAULT_ablation_dir / "ablationAnalysis"
108 DEFAULT_ablation_validation_exec = DEFAULT_ablation_dir / "ablationValidation"
110 # Default input directory pathing
111 DEFAULT_solver_dir = cwd_prefix / "Solvers"
112 DEFAULT_instance_dir = cwd_prefix / "Instances"
113 DEFAULT_extractor_dir = cwd_prefix / "Extractors"
114 DEFAULT_snapshot_dir = cwd_prefix / "Snapshots"
116 # Default output directory pathing
117 DEFAULT_tmp_output = cwd_prefix / "Tmp"
118 DEFAULT_output = cwd_prefix / "Output"
119 DEFAULT_configuration_output = DEFAULT_output / "Configuration"
120 DEFAULT_selection_output = DEFAULT_output / "Selection"
121 DEFAULT_parallel_portfolio_output = DEFAULT_output / "Parallel_Portfolio"
122 DEFAULT_ablation_output = DEFAULT_output / "Ablation"
123 DEFAULT_log_output = DEFAULT_output / "Log"
125 # Default output subdirs
126 DEFAULT_output_analysis = DEFAULT_output / analysis_dir
128 # Old default output dirs which should be part of something else
129 DEFAULT_feature_data = DEFAULT_output / "Feature_Data"
130 DEFAULT_performance_data = DEFAULT_output / "Performance_Data"
132 # Collection of all working dirs for platform
133 DEFAULT_working_dirs = [
134 DEFAULT_solver_dir,
135 DEFAULT_instance_dir,
136 DEFAULT_extractor_dir,
137 DEFAULT_output,
138 DEFAULT_configuration_output,
139 DEFAULT_selection_output,
140 DEFAULT_output_analysis,
141 DEFAULT_tmp_output,
142 DEFAULT_log_output,
143 DEFAULT_feature_data,
144 DEFAULT_performance_data,
145 DEFAULT_settings_dir,
146 DEFAULT_reference_dir,
147 ]
149 # Old default file paths from GV which should be turned into variables
150 DEFAULT_feature_data_path = DEFAULT_feature_data / "feature_data.csv"
151 DEFAULT_performance_data_path = DEFAULT_performance_data / "performance_data.csv"
153 # Define sections and options
154 # GENERAL Options
155 SECTION_general: str = "general"
156 OPTION_objectives = Option(
157 "objectives",
158 SECTION_general,
159 str,
160 None,
161 ("sparkle_objectives",),
162 "A list of Sparkle objectives.",
163 cli_kwargs={"nargs": "+"},
164 )
165 OPTION_configurator = Option(
166 "configurator",
167 SECTION_general,
168 str,
169 None,
170 tuple(),
171 "Name of the configurator to use.",
172 )
173 OPTION_solver_cutoff_time = Option(
174 "solver_cutoff_time",
175 SECTION_general,
176 int,
177 None,
178 ("target_cutoff_time", "cutoff_time_each_solver_call"),
179 "Solver cutoff time in seconds.",
180 )
181 OPTION_extractor_cutoff_time = Option(
182 "extractor_cutoff_time",
183 SECTION_general,
184 int,
185 None,
186 tuple("cutoff_time_each_feature_computation"),
187 "Extractor cutoff time in seconds.",
188 )
189 OPTION_run_on = Option(
190 "run_on",
191 SECTION_general,
192 Runner,
193 None,
194 tuple(),
195 "On which compute resource to execute.",
196 cli_kwargs={"choices": [Runner.LOCAL, Runner.SLURM]},
197 )
198 OPTION_verbosity = Option(
199 "verbosity",
200 SECTION_general,
201 VerbosityLevel,
202 VerbosityLevel.STANDARD,
203 ("verbosity_level",),
204 "Verbosity level.",
205 )
206 OPTION_seed = Option(
207 "seed",
208 SECTION_general,
209 int,
210 None,
211 tuple(),
212 "Seed to use for pseudo-random number generators.",
213 )
214 OPTION_appendices = Option(
215 "appendices",
216 SECTION_general,
217 bool,
218 False,
219 tuple(),
220 "Include the appendix section in the generated report.",
221 cli_kwargs={
222 "action": "store_true",
223 "default": None,
224 },
225 )
227 # CONFIGURATION Options
228 SECTION_configuration = "configuration"
229 OPTION_configurator_number_of_runs = Option(
230 "number_of_runs",
231 SECTION_configuration,
232 int,
233 None,
234 tuple(),
235 "The number of independent configurator jobs/runs.",
236 )
237 OPTION_configurator_solver_call_budget = Option(
238 "solver_calls",
239 SECTION_configuration,
240 int,
241 None,
242 tuple(),
243 "The maximum number of calls (evaluations) a configurator can do in a single "
244 "run of the solver.",
245 )
246 OPTION_configurator_max_iterations = Option(
247 "max_iterations",
248 SECTION_configuration,
249 int,
250 None,
251 ("maximum_iterations",),
252 "The maximum number of iterations a configurator can do in a single job.",
253 )
255 # ABLATION Options
256 SECTION_ablation = "ablation"
257 OPTION_ablation_racing = Option(
258 "racing",
259 SECTION_ablation,
260 bool,
261 False,
262 ("ablation_racing",),
263 "Set a flag indicating whether racing should be used for ablation.",
264 )
265 OPTION_ablation_clis_per_node = Option(
266 "clis_per_node",
267 SECTION_ablation,
268 int,
269 None,
270 (
271 "max_parallel_runs_per_node",
272 "maximum_parallel_runs_per_node",
273 ),
274 "The maximum number of ablation analysis jobs to run in parallel on a single "
275 "node.",
276 )
278 # SELECTION Options
279 SECTION_selection = "selection"
280 OPTION_selection_class = Option(
281 "selector_class",
282 SECTION_selection,
283 str,
284 None,
285 ("class",),
286 "Can contain any of the class names as defined in asf.selectors.",
287 )
288 OPTION_selection_model = Option(
289 "selector_model",
290 SECTION_selection,
291 str,
292 None,
293 ("model",),
294 "Can be any of the sklearn.ensemble models.",
295 )
296 OPTION_minimum_marginal_contribution = Option(
297 "minimum_marginal_contribution",
298 SECTION_selection,
299 float,
300 0.01,
301 (
302 "minimum_marginal_contribution",
303 "minimum_contribution",
304 "contribution_threshold",
305 ),
306 "The minimum marginal contribution a solver (configuration) must have to be used for the selector.",
307 )
309 # SMAC2 Options
310 SECTION_smac2 = "smac2"
311 OPTION_smac2_wallclock_time_budget = Option(
312 "wallclock_time_budget",
313 SECTION_smac2,
314 int,
315 None,
316 ("wallclock_time",),
317 "The wallclock time budget in seconds for each SMAC2 run.",
318 )
319 OPTION_smac2_cpu_time_budget = Option(
320 "cpu_time_budget",
321 SECTION_smac2,
322 int,
323 None,
324 ("cpu_time",),
325 "The cpu time budget in seconds for each SMAC2 run.",
326 )
327 OPTION_smac2_target_cutoff_length = Option(
328 "target_cutoff_length",
329 SECTION_smac2,
330 str,
331 None,
332 ("cutoff_length", "solver_cutoff_length"),
333 "The target cutoff length for SMAC2 solver call.",
334 )
335 OPTION_smac2_count_tuner_time = Option(
336 "use_cpu_time_in_tunertime",
337 SECTION_smac2,
338 bool,
339 None,
340 ("countSMACTimeAsTunerTime",),
341 "Whether to count and deducted SMAC2 CPU time from the CPU time budget.",
342 )
343 OPTION_smac2_cli_cores = Option(
344 "cli_cores",
345 SECTION_smac2,
346 int,
347 None,
348 tuple(),
349 "Number of cores to use to execute SMAC2 runs.",
350 )
351 OPTION_smac2_max_iterations = Option(
352 "max_iterations",
353 SECTION_smac2,
354 int,
355 None,
356 (
357 "iteration_limit",
358 "numIterations",
359 "numberOfIterations",
360 ),
361 "The maximum number of iterations SMAC2 configurator can do in a single job.",
362 )
364 # SMAC3 Options
365 SECTION_smac3 = "smac3"
366 OPTION_smac3_number_of_trials = Option(
367 "n_trials",
368 SECTION_smac3,
369 int,
370 None,
371 ("n_trials", "number_of_trials", "solver_calls"),
372 "Maximum calls SMAC3 is allowed to make to the Solver in a single run/job.",
373 )
374 OPTION_smac3_facade = Option(
375 "facade",
376 SECTION_smac3,
377 str,
378 "AlgorithmConfigurationFacade",
379 ("facade", "smac_facade", "smac3_facade"),
380 "The SMAC3 Facade to use. See the SMAC3 documentation for more options.",
381 )
382 OPTION_smac3_facade_max_ratio = Option(
383 "facade_max_ratio",
384 SECTION_smac3,
385 float,
386 None,
387 ("facade_max_ratio", "smac3_facade_max_ratio", "smac3_facade_max_ratio"),
388 "The SMAC3 Facade max ratio. See the SMAC3 documentation for more options.",
389 )
390 OPTION_smac3_crash_cost = Option(
391 "crash_cost",
392 SECTION_smac3,
393 float,
394 None,
395 tuple(),
396 "Defines the cost for a failed trial, defaults in SMAC3 to np.inf.",
397 )
398 OPTION_smac3_termination_cost_threshold = Option(
399 "termination_cost_threshold",
400 SECTION_smac3,
401 float,
402 None,
403 tuple(),
404 "Defines a cost threshold when the SMAC3 optimization should stop.",
405 )
406 OPTION_smac3_wallclock_time_budget = Option(
407 "walltime_limit",
408 SECTION_smac3,
409 float,
410 None,
411 ("wallclock_time", "wallclock_budget", "wallclock_time_budget"),
412 "The maximum time in seconds that SMAC3 is allowed to run per job.",
413 )
414 OPTION_smac3_cpu_time_budget = Option(
415 "cputime_limit",
416 SECTION_smac3,
417 float,
418 None,
419 ("cpu_time", "cpu_budget", "cpu_time_budget"),
420 "The maximum CPU time in seconds that SMAC3 is allowed to run per job.",
421 )
422 OPTION_smac3_use_default_config = Option(
423 "use_default_config",
424 SECTION_smac3,
425 bool,
426 None,
427 tuple(),
428 "If True, the configspace's default configuration is evaluated in the initial "
429 "design. For historic benchmark reasons, this is False by default. Notice, that "
430 "this will result in n_configs + 1 for the initial design. Respecting n_trials, "
431 "this will result in one fewer evaluated configuration in the optimization.",
432 )
433 OPTION_smac3_min_budget = Option(
434 "min_budget",
435 SECTION_smac3,
436 float,
437 None,
438 ("minimum_budget",),
439 "The minimum budget (epochs, subset size, number of instances, ...) that is used"
440 " for the optimization. Use this argument if you use multi-fidelity or instance "
441 "optimization.",
442 )
443 OPTION_smac3_max_budget = Option(
444 "max_budget",
445 SECTION_smac3,
446 float,
447 None,
448 ("maximum_budget",),
449 "The maximum budget (epochs, subset size, number of instances, ...) that is used"
450 " for the optimization. Use this argument if you use multi-fidelity or instance "
451 "optimization.",
452 )
454 # IRACE Options
455 SECTION_irace = "irace"
456 OPTION_irace_max_time = Option(
457 "max_time",
458 SECTION_irace,
459 int,
460 0,
461 ("maximum_time",),
462 "The maximum time in seconds for each IRACE run/job.",
463 )
464 OPTION_irace_max_experiments = Option(
465 "max_experiments",
466 SECTION_irace,
467 int,
468 0,
469 ("maximum_experiments",),
470 "The maximum number of experiments for each IRACE run/job.",
471 )
472 OPTION_irace_first_test = Option(
473 "first_test",
474 SECTION_irace,
475 int,
476 None,
477 tuple(),
478 "Specifies how many instances are evaluated before the first elimination test. "
479 "IRACE Default: 5.",
480 )
481 OPTION_irace_mu = Option(
482 "mu",
483 SECTION_irace,
484 int,
485 None,
486 tuple(),
487 "Parameter used to define the number of configurations sampled and evaluated at "
488 "each iteration. IRACE Default: 5.",
489 )
490 OPTION_irace_max_iterations = Option(
491 "max_iterations",
492 SECTION_irace,
493 int,
494 None,
495 ("nb_iterations", "iterations", "max_iterations"),
496 "Maximum number of iterations to be executed. Each iteration involves the "
497 "generation of new configurations and the use of racing to select the best "
498 "configurations. By default (with 0), irace calculates a minimum number of "
499 "iterations as N^iter = ⌊2 + log2 N param⌋, where N^param is the number of "
500 "non-fixed parameters to be tuned. Setting this parameter may make irace stop "
501 "sooner than it should without using all the available budget. IRACE recommends"
502 " to use the default value (Empty).",
503 )
505 # ParamILS Options
506 SECTION_paramils = "paramils"
507 OPTION_paramils_min_runs = Option(
508 "min_runs",
509 SECTION_paramils,
510 int,
511 None,
512 ("minimum_runs",),
513 "Set the minimum number of runs for ParamILS for each run/job.",
514 )
515 OPTION_paramils_max_runs = Option(
516 "max_runs",
517 SECTION_paramils,
518 int,
519 None,
520 ("maximum_runs",),
521 "Set the maximum number of runs for ParamILS for each run/job.",
522 )
523 OPTION_paramils_cpu_time_budget = Option(
524 "cputime_budget",
525 SECTION_paramils,
526 int,
527 None,
528 (
529 "cputime_limit",
530 "cputime_limit",
531 "tunertime_limit",
532 "tuner_timeout",
533 "tunerTimeout",
534 ),
535 "The maximum CPU time for each ParamILS run/job.",
536 )
537 OPTION_paramils_random_restart = Option(
538 "random_restart",
539 SECTION_paramils,
540 float,
541 None,
542 tuple(),
543 "Set the random restart chance for ParamILS.",
544 )
545 OPTION_paramils_focused = Option(
546 "focused_approach",
547 SECTION_paramils,
548 bool,
549 False,
550 ("focused",),
551 "Set the focused approach for ParamILS.",
552 )
553 OPTION_paramils_count_tuner_time = Option(
554 "use_cpu_time_in_tunertime",
555 SECTION_paramils,
556 bool,
557 None,
558 tuple(),
559 "Whether to count and deducted ParamILS CPU time from the CPU time budget.",
560 )
561 OPTION_paramils_cli_cores = Option(
562 "cli_cores",
563 SECTION_paramils,
564 int,
565 None,
566 tuple(),
567 "Number of cores to use for ParamILS runs.",
568 )
569 OPTION_paramils_max_iterations = Option(
570 "max_iterations",
571 SECTION_paramils,
572 int,
573 None,
574 (
575 "iteration_limit",
576 "numIterations",
577 "numberOfIterations",
578 "maximum_iterations",
579 ),
580 "The maximum number of ParamILS iterations per run/job.",
581 )
582 OPTION_paramils_number_initial_configurations = Option(
583 "initial_configurations",
584 SECTION_paramils,
585 int,
586 None,
587 "The number of initial configurations ParamILS should evaluate.",
588 )
590 SECTION_parallel_portfolio = "parallel_portfolio"
591 OPTION_parallel_portfolio_check_interval = Option(
592 "check_interval",
593 SECTION_parallel_portfolio,
594 int,
595 None,
596 tuple(),
597 "The interval time in seconds when Solvers are checked for their status.",
598 )
599 OPTION_parallel_portfolio_number_of_seeds_per_solver = Option(
600 "num_seeds_per_solver",
601 SECTION_parallel_portfolio,
602 int,
603 None,
604 ("solver_seeds",),
605 "The number of seeds per solver.",
606 )
608 SECTION_slurm = "slurm"
609 OPTION_slurm_parallel_jobs = Option(
610 "number_of_jobs_in_parallel",
611 SECTION_slurm,
612 int,
613 None,
614 ("num_job_in_parallel",),
615 "The number of jobs to run in parallel.",
616 )
617 OPTION_slurm_prepend_script = Option(
618 "prepend_script",
619 SECTION_slurm,
620 str,
621 None,
622 ("job_prepend", "prepend"),
623 "Slurm script to prepend to the sbatch.",
624 )
626 sections_options: dict[str, list[Option]] = {
627 SECTION_general: [
628 OPTION_objectives,
629 OPTION_configurator,
630 OPTION_solver_cutoff_time,
631 OPTION_extractor_cutoff_time,
632 OPTION_run_on,
633 OPTION_appendices,
634 OPTION_verbosity,
635 OPTION_seed,
636 ],
637 SECTION_configuration: [
638 OPTION_configurator_number_of_runs,
639 OPTION_configurator_solver_call_budget,
640 OPTION_configurator_max_iterations,
641 ],
642 SECTION_ablation: [
643 OPTION_ablation_racing,
644 OPTION_ablation_clis_per_node,
645 ],
646 SECTION_selection: [
647 OPTION_selection_class,
648 OPTION_selection_model,
649 OPTION_minimum_marginal_contribution,
650 ],
651 SECTION_smac2: [
652 OPTION_smac2_wallclock_time_budget,
653 OPTION_smac2_cpu_time_budget,
654 OPTION_smac2_target_cutoff_length,
655 OPTION_smac2_count_tuner_time,
656 OPTION_smac2_cli_cores,
657 OPTION_smac2_max_iterations,
658 ],
659 SECTION_smac3: [
660 OPTION_smac3_number_of_trials,
661 OPTION_smac3_facade,
662 OPTION_smac3_facade_max_ratio,
663 OPTION_smac3_crash_cost,
664 OPTION_smac3_termination_cost_threshold,
665 OPTION_smac3_wallclock_time_budget,
666 OPTION_smac3_cpu_time_budget,
667 OPTION_smac3_use_default_config,
668 OPTION_smac3_min_budget,
669 OPTION_smac3_max_budget,
670 ],
671 SECTION_irace: [
672 OPTION_irace_max_time,
673 OPTION_irace_max_experiments,
674 OPTION_irace_first_test,
675 OPTION_irace_mu,
676 OPTION_irace_max_iterations,
677 ],
678 SECTION_paramils: [
679 OPTION_paramils_min_runs,
680 OPTION_paramils_max_runs,
681 OPTION_paramils_cpu_time_budget,
682 OPTION_paramils_random_restart,
683 OPTION_paramils_focused,
684 OPTION_paramils_count_tuner_time,
685 OPTION_paramils_cli_cores,
686 OPTION_paramils_max_iterations,
687 OPTION_paramils_number_initial_configurations,
688 ],
689 SECTION_parallel_portfolio: [
690 OPTION_parallel_portfolio_check_interval,
691 OPTION_parallel_portfolio_number_of_seeds_per_solver,
692 ],
693 SECTION_slurm: [OPTION_slurm_parallel_jobs, OPTION_slurm_prepend_script],
694 }
696 def __init__(
697 self: Settings, file_path: Path, argsv: argparse.Namespace = None
698 ) -> None:
699 """Initialise a settings object.
701 Args:
702 file_path (Path): Path to the settings file.
703 argsv: The CLI arguments to process.
704 """
705 # Settings 'dictionary' in configparser format
706 self.__settings = configparser.ConfigParser()
707 for section in self.sections_options.keys():
708 self.__settings.add_section(section)
709 self.__settings[section] = {}
711 # General attributes
712 self.__sparkle_objectives: list[SparkleObjective] = None
713 self.__general_sparkle_configurator: Configurator = None
714 self.__solver_cutoff_time: int = None
715 self.__extractor_cutoff_time: int = None
716 self.__run_on: Runner = None
717 self.__appendices: bool = False
718 self.__verbosity_level: VerbosityLevel = None
719 self.__seed: Optional[int] = None
721 # Configuration attributes
722 self.__configurator_solver_call_budget: int = None
723 self.__configurator_number_of_runs: int = None
724 self.__configurator_max_iterations: int = None
726 # Ablation attributes
727 self.__ablation_racing_flag: bool = None
728 self.__ablation_max_parallel_runs_per_node: int = None
730 # Selection attributes
731 self.__selection_model: str = None
732 self.__selection_class: str = None
733 self.__minimum_marginal_contribution: float = None
735 # SMAC2 attributes
736 self.__smac2_wallclock_time_budget: int = None
737 self.__smac2_cpu_time_budget: int = None
738 self.__smac2_target_cutoff_length: str = None
739 self.__smac2_use_tunertime_in_cpu_time_budget: bool = None
740 self.__smac2_cli_cores: int = None
741 self.__smac2_max_iterations: int = None
743 # SMAC3 attributes
744 self.__smac3_number_of_trials: int = None
745 self.__smac3_facade: str = None
746 self.__smac3_facade_max_ratio: float = None
747 self.__smac3_crash_cost: float = None
748 self.__smac3_termination_cost_threshold: float = None
749 self.__smac3_wallclock_time_limit: int = None
750 self.__smac3_cputime_limit: int = None
751 self.__smac3_use_default_config: bool = None
752 self.__smac3_min_budget: float = None
753 self.__smac3_max_budget: float = None
755 # IRACE attributes
756 self.__irace_max_time: int = None
757 self.__irace_max_experiments: int = None
758 self.__irace_first_test: int = None
759 self.__irace_mu: int = None
760 self.__irace_max_iterations: int = None
762 # ParamILS attributes
763 self.__paramils_cpu_time_budget: int = None
764 self.__paramils_min_runs: int = None
765 self.__paramils_max_runs: int = None
766 self.__paramils_random_restart: float = None
767 self.__paramils_focused_approach: bool = None
768 self.__paramils_use_cpu_time_in_tunertime: bool = None
769 self.__paramils_cli_cores: int = None
770 self.__paramils_max_iterations: int = None
771 self.__paramils_number_initial_configurations: int = None
773 # Parallel portfolio attributes
774 self.__parallel_portfolio_check_interval: int = None
775 self.__parallel_portfolio_num_seeds_per_solver: int = None
777 # Slurm attributes
778 self.__slurm_jobs_in_parallel: int = None
779 self.__slurm_job_prepend: str = None
781 # The seed that has been used to set the random state
782 self.random_state: Optional[int] = None
784 if file_path and file_path.exists():
785 self.read_settings_ini(file_path)
787 if argsv:
788 self.apply_arguments(argsv)
790 def read_settings_ini(self: Settings, file_path: Path) -> None:
791 """Read the settings from an INI file."""
792 if not file_path.exists():
793 raise ValueError(f"Settings file {file_path} does not exist.")
794 # Read file
795 file_settings = configparser.ConfigParser()
796 file_settings.read(file_path)
798 # Set internal settings based on data read from FILE if they were read
799 # successfully
800 if file_settings.sections() == []:
801 # Print error if unable to read the settings
802 print(
803 f"ERROR: Failed to read settings from {file_path} The file may "
804 "have been empty or be in another format than INI."
805 )
806 return
808 for section in file_settings.sections():
809 if section not in self.__settings.sections():
810 print(f'Unrecognised section: "{section}" in file {file_path} ignored')
811 continue
812 for option_name in file_settings.options(section):
813 if option_name not in self.sections_options[section]:
814 if section == Settings.SECTION_slurm: # Flexible section
815 self.__settings.set(
816 section,
817 option_name,
818 file_settings.get(section, option_name),
819 )
820 else:
821 print(
822 f'Unrecognised section - option combination: "{section} '
823 f'{option_name}" in file {file_path} ignored'
824 )
825 continue
826 option_index = self.sections_options[section].index(option_name)
827 option = self.sections_options[section][option_index]
828 self.__settings.set(
829 section, option.name, file_settings.get(section, option_name)
830 )
831 del file_settings
833 def write_settings_ini(self: Settings, file_path: Path) -> None:
834 """Write the settings to an INI file."""
835 # Create needed directories if they don't exist
836 file_path.parent.mkdir(parents=True, exist_ok=True)
837 # We don't write empty sections
838 for s in self.__settings.sections():
839 if not self.__settings[s]:
840 self.__settings.remove_section(s)
841 with file_path.open("w") as fout:
842 self.__settings.write(fout)
843 for s in self.sections_options.keys():
844 if s not in self.__settings.sections():
845 self.__settings.add_section(s)
847 def write_used_settings(self: Settings) -> None:
848 """Write the used settings to the default locations."""
849 # Write to latest settings file
850 self.write_settings_ini(self.DEFAULT_previous_settings_path)
852 def apply_arguments(self: Settings, argsv: argparse.Namespace) -> None:
853 """Apply the arguments to the settings."""
854 # Read a possible second file, that overwrites the first, where applicable
855 # e.g. settings are not deleted, but overwritten where applicable
856 if hasattr(argsv, "settings_file") and argsv.settings_file:
857 self.read_settings_ini(argsv.settings_file)
858 # Match all possible arguments to the settings
859 for argument in argsv.__dict__.keys():
860 value = argsv.__dict__[argument]
861 if value is None:
862 continue # Skip None
863 value = value.name if isinstance(value, Enum) else str(value)
864 for section in self.sections_options.keys():
865 if argument in self.sections_options[section]:
866 index = self.sections_options[section].index(argument)
867 option = self.sections_options[section][index]
868 self.__settings.set(option.section, option.name, value)
869 break
871 def _abstract_getter(self: Settings, option: Option) -> Any:
872 """Abstract getter method."""
873 if self.__settings.has_option(option.section, option.name):
874 if option.type is bool:
875 return self.__settings.getboolean(option.section, option.name)
876 value = self.__settings.get(option.section, option.name)
877 if not isinstance(value, option.type):
878 if issubclass(option.type, Enum):
879 return option.type[value.upper()]
880 return option.type(value) # Attempt to resolve str to type
881 return value
882 return option.default_value
884 # General settings ###
885 @property
886 def objectives(self: Settings) -> list[SparkleObjective]:
887 """Get the objectives for Sparkle."""
888 if self.__sparkle_objectives is None and self.__settings.has_option(
889 Settings.SECTION_general, "objectives"
890 ):
891 objectives = self.__settings[Settings.SECTION_general]["objectives"]
892 if "status" not in objectives:
893 objectives += ",status:metric"
894 if "cpu_time" not in objectives:
895 objectives += ",cpu_time:metric"
896 if "wall_time" not in objectives:
897 objectives += ",wall_time:metric"
898 if "memory" not in objectives:
899 objectives += ",memory:metric"
900 self.__sparkle_objectives = [
901 resolve_objective(obj) for obj in objectives.split(",")
902 ]
903 return self.__sparkle_objectives
905 @property
906 def configurator(self: Settings) -> Configurator:
907 """Get the configurator class (instance)."""
908 if self.__general_sparkle_configurator is None and self.__settings.has_option(
909 Settings.OPTION_configurator.section, Settings.OPTION_configurator.name
910 ):
911 self.__general_sparkle_configurator = cim.resolve_configurator(
912 self.__settings.get(
913 Settings.OPTION_configurator.section,
914 Settings.OPTION_configurator.name,
915 )
916 )()
917 return self.__general_sparkle_configurator
919 @property
920 def solver_cutoff_time(self: Settings) -> int:
921 """Solver cutoff time in seconds."""
922 if self.__solver_cutoff_time is None:
923 self.__solver_cutoff_time = self._abstract_getter(
924 Settings.OPTION_solver_cutoff_time
925 )
926 return self.__solver_cutoff_time
928 @property
929 def extractor_cutoff_time(self: Settings) -> int:
930 """Extractor cutoff time in seconds."""
931 if self.__extractor_cutoff_time is None:
932 self.__extractor_cutoff_time = self._abstract_getter(
933 Settings.OPTION_extractor_cutoff_time
934 )
935 return self.__extractor_cutoff_time
937 @property
938 def run_on(self: Settings) -> Runner:
939 """On which compute to run (Local or Slurm)."""
940 if self.__run_on is None:
941 self.__run_on = self._abstract_getter(Settings.OPTION_run_on)
942 return self.__run_on
944 @property
945 def appendices(self: Settings) -> bool:
946 """Whether to include appendices in the report."""
947 return self._abstract_getter(Settings.OPTION_appendices)
949 @property
950 def verbosity_level(self: Settings) -> VerbosityLevel:
951 """Verbosity level to use in CLI commands."""
952 if self.__verbosity_level is None:
953 if self.__settings.has_option(
954 Settings.OPTION_verbosity.section, Settings.OPTION_verbosity.name
955 ):
956 self.__verbosity_level = VerbosityLevel[
957 self.__settings.get(
958 Settings.OPTION_verbosity.section,
959 Settings.OPTION_verbosity.name,
960 )
961 ]
962 else:
963 self.__verbosity_level = Settings.OPTION_verbosity.default_value
964 return self.__verbosity_level
966 @property
967 def seed(self: Settings) -> int:
968 """Seed to use in CLI commands."""
969 if self.__seed is not None:
970 return self.__seed
972 section, name = Settings.OPTION_seed.section, Settings.OPTION_seed.name
973 if self.__settings.has_option(section, name):
974 value = self.__settings.get(section, name)
975 self.__seed = int(value)
976 else:
977 self.__seed = Settings.OPTION_seed.default_value
979 return self.__seed
981 @seed.setter
982 def seed(self: Settings, value: int) -> None:
983 """Set the seed value (overwrites settings)."""
984 self.__seed = value
985 self.__settings.set(
986 Settings.OPTION_seed.section, Settings.OPTION_seed.name, str(self.__seed)
987 )
989 # Configuration settings ###
990 @property
991 def configurator_solver_call_budget(self: Settings) -> int:
992 """The amount of calls a configurator can do to the solver."""
993 if self.__configurator_solver_call_budget is None:
994 self.__configurator_solver_call_budget = self._abstract_getter(
995 Settings.OPTION_configurator_solver_call_budget
996 )
997 return self.__configurator_solver_call_budget
999 @property
1000 def configurator_number_of_runs(self: Settings) -> int:
1001 """Get the amount of configurator runs to do."""
1002 if self.__configurator_number_of_runs is None:
1003 self.__configurator_number_of_runs = self._abstract_getter(
1004 Settings.OPTION_configurator_number_of_runs
1005 )
1006 return self.__configurator_number_of_runs
1008 @property
1009 def configurator_max_iterations(self: Settings) -> int:
1010 """Get the amount of configurator iterations to do."""
1011 if self.__configurator_max_iterations is None:
1012 self.__configurator_max_iterations = self._abstract_getter(
1013 Settings.OPTION_configurator_max_iterations
1014 )
1015 return self.__configurator_max_iterations
1017 # Ablation settings ###
1018 @property
1019 def ablation_racing_flag(self: Settings) -> bool:
1020 """Get the ablation racing flag."""
1021 if self.__ablation_racing_flag is None:
1022 self.__ablation_racing_flag = self._abstract_getter(
1023 Settings.OPTION_ablation_racing
1024 )
1025 return self.__ablation_racing_flag
1027 @property
1028 def ablation_max_parallel_runs_per_node(self: Settings) -> int:
1029 """Get the ablation max parallel runs per node."""
1030 if self.__ablation_max_parallel_runs_per_node is None:
1031 self.__ablation_max_parallel_runs_per_node = self._abstract_getter(
1032 Settings.OPTION_ablation_clis_per_node
1033 )
1034 return self.__ablation_max_parallel_runs_per_node
1036 # Selection settings ###
1037 @property
1038 def selection_model(self: Settings) -> str:
1039 """Get the selection model."""
1040 if self.__selection_model is None:
1041 self.__selection_model = self._abstract_getter(
1042 Settings.OPTION_selection_model
1043 )
1044 return self.__selection_model
1046 @property
1047 def selection_class(self: Settings) -> str:
1048 """Get the selection class."""
1049 if self.__selection_class is None:
1050 self.__selection_class = self._abstract_getter(
1051 Settings.OPTION_selection_class
1052 )
1053 return self.__selection_class
1055 @property
1056 def minimum_marginal_contribution(self: Settings) -> float:
1057 """Get the minimum marginal contribution."""
1058 if self.__minimum_marginal_contribution is None:
1059 self.__minimum_marginal_contribution = self._abstract_getter(
1060 Settings.OPTION_minimum_marginal_contribution
1061 )
1062 return self.__minimum_marginal_contribution
1064 # Configuration: SMAC2 specific settings ###
1065 @property
1066 def smac2_wallclock_time_budget(self: Settings) -> int:
1067 """Return the SMAC2 wallclock budget per configuration run in seconds."""
1068 if self.__smac2_wallclock_time_budget is None:
1069 self.__smac2_wallclock_time_budget = self._abstract_getter(
1070 Settings.OPTION_smac2_wallclock_time_budget
1071 )
1072 return self.__smac2_wallclock_time_budget
1074 @property
1075 def smac2_cpu_time_budget(self: Settings) -> int:
1076 """Return the SMAC2 CPU budget per configuration run in seconds."""
1077 if self.__smac2_cpu_time_budget is None:
1078 self.__smac2_cpu_time_budget = self._abstract_getter(
1079 Settings.OPTION_smac2_cpu_time_budget
1080 )
1081 return self.__smac2_cpu_time_budget
1083 @property
1084 def smac2_target_cutoff_length(self: Settings) -> str:
1085 """Return the SMAC2 target cutoff length."""
1086 if self.__smac2_target_cutoff_length is None:
1087 self.__smac2_target_cutoff_length = self._abstract_getter(
1088 Settings.OPTION_smac2_target_cutoff_length
1089 )
1090 return self.__smac2_target_cutoff_length
1092 @property
1093 def smac2_use_tunertime_in_cpu_time_budget(self: Settings) -> bool:
1094 """Return whether SMAC2 time should be used in CPU time budget."""
1095 if self.__smac2_use_tunertime_in_cpu_time_budget is None:
1096 self.__smac2_use_tunertime_in_cpu_time_budget = self._abstract_getter(
1097 Settings.OPTION_smac2_count_tuner_time
1098 )
1099 return self.__smac2_use_tunertime_in_cpu_time_budget
1101 @property
1102 def smac2_cli_cores(self: Settings) -> int:
1103 """Return the SMAC2 CLI cores."""
1104 if self.__smac2_cli_cores is None:
1105 self.__smac2_cli_cores = self._abstract_getter(
1106 Settings.OPTION_smac2_cli_cores
1107 )
1108 return self.__smac2_cli_cores
1110 @property
1111 def smac2_max_iterations(self: Settings) -> int:
1112 """Return the SMAC2 max iterations."""
1113 if self.__smac2_max_iterations is None:
1114 self.__smac2_max_iterations = self._abstract_getter(
1115 Settings.OPTION_smac2_max_iterations
1116 )
1117 return self.__smac2_max_iterations
1119 # SMAC3 attributes ###
1120 @property
1121 def smac3_number_of_trials(self: Settings) -> int:
1122 """Return the SMAC3 number of trials."""
1123 if self.__smac3_number_of_trials is None:
1124 self.__smac3_number_of_trials = self._abstract_getter(
1125 Settings.OPTION_smac3_number_of_trials
1126 )
1127 return self.__smac3_number_of_trials
1129 @property
1130 def smac3_facade(self: Settings) -> str:
1131 """Return the SMAC3 facade."""
1132 if self.__smac3_facade is None:
1133 self.__smac3_facade = self._abstract_getter(Settings.OPTION_smac3_facade)
1134 return self.__smac3_facade
1136 @property
1137 def smac3_facade_max_ratio(self: Settings) -> float:
1138 """Return the SMAC3 facade max ratio."""
1139 if self.__smac3_facade_max_ratio is None:
1140 self.__smac3_facade_max_ratio = self._abstract_getter(
1141 Settings.OPTION_smac3_facade_max_ratio
1142 )
1143 return self.__smac3_facade_max_ratio
1145 @property
1146 def smac3_crash_cost(self: Settings) -> float:
1147 """Return the SMAC3 crash cost."""
1148 if self.__smac3_crash_cost is None:
1149 self.__smac3_crash_cost = self._abstract_getter(
1150 Settings.OPTION_smac3_crash_cost
1151 )
1152 return self.__smac3_crash_cost
1154 @property
1155 def smac3_termination_cost_threshold(self: Settings) -> float:
1156 """Return the SMAC3 termination cost threshold."""
1157 if self.__smac3_termination_cost_threshold is None:
1158 self.__smac3_termination_cost_threshold = self._abstract_getter(
1159 Settings.OPTION_smac3_termination_cost_threshold
1160 )
1161 return self.__smac3_termination_cost_threshold
1163 @property
1164 def smac3_wallclock_time_budget(self: Settings) -> int:
1165 """Return the SMAC3 walltime budget in seconds."""
1166 if self.__smac3_wallclock_time_limit is None:
1167 self.__smac3_wallclock_time_limit = self._abstract_getter(
1168 Settings.OPTION_smac3_wallclock_time_budget
1169 )
1170 return self.__smac3_wallclock_time_limit
1172 @property
1173 def smac3_cpu_time_budget(self: Settings) -> int:
1174 """Return the SMAC3 cputime budget in seconds."""
1175 if self.__smac3_cputime_limit is None:
1176 self.__smac3_cputime_limit = self._abstract_getter(
1177 Settings.OPTION_smac3_cpu_time_budget
1178 )
1179 return self.__smac3_cputime_limit
1181 @property
1182 def smac3_use_default_config(self: Settings) -> bool:
1183 """Return whether SMAC3 should use the default config."""
1184 if self.__smac3_use_default_config is None:
1185 self.__smac3_use_default_config = self._abstract_getter(
1186 Settings.OPTION_smac3_use_default_config
1187 )
1188 return self.__smac3_use_default_config
1190 @property
1191 def smac3_min_budget(self: Settings) -> int:
1192 """Return the SMAC3 min budget."""
1193 if self.__smac3_min_budget is None:
1194 self.__smac3_min_budget = self._abstract_getter(
1195 Settings.OPTION_smac3_min_budget
1196 )
1197 return self.__smac3_min_budget
1199 @property
1200 def smac3_max_budget(self: Settings) -> int:
1201 """Return the SMAC3 max budget."""
1202 if self.__smac3_max_budget is None:
1203 self.__smac3_max_budget = self._abstract_getter(
1204 Settings.OPTION_smac3_max_budget
1205 )
1206 return self.__smac3_max_budget
1208 # IRACE settings ###
1209 @property
1210 def irace_max_time(self: Settings) -> int:
1211 """Return the max time in seconds for IRACE."""
1212 if self.__irace_max_time is None:
1213 self.__irace_max_time = self._abstract_getter(Settings.OPTION_irace_max_time)
1214 return self.__irace_max_time
1216 @property
1217 def irace_max_experiments(self: Settings) -> int:
1218 """Return the max experiments for IRACE."""
1219 if self.__irace_max_experiments is None:
1220 self.__irace_max_experiments = self._abstract_getter(
1221 Settings.OPTION_irace_max_experiments
1222 )
1223 return self.__irace_max_experiments
1225 @property
1226 def irace_first_test(self: Settings) -> int:
1227 """Return the first test for IRACE."""
1228 if self.__irace_first_test is None:
1229 self.__irace_first_test = self._abstract_getter(
1230 Settings.OPTION_irace_first_test
1231 )
1232 return self.__irace_first_test
1234 @property
1235 def irace_mu(self: Settings) -> int:
1236 """Return the mu for IRACE."""
1237 if self.__irace_mu is None:
1238 self.__irace_mu = self._abstract_getter(Settings.OPTION_irace_mu)
1239 return self.__irace_mu
1241 @property
1242 def irace_max_iterations(self: Settings) -> int:
1243 """Return the max iterations for IRACE."""
1244 if self.__irace_max_iterations is None:
1245 self.__irace_max_iterations = self._abstract_getter(
1246 Settings.OPTION_irace_max_iterations
1247 )
1248 return self.__irace_max_iterations
1250 # ParamILS settings ###
1251 @property
1252 def paramils_cpu_time_budget(self: Settings) -> int:
1253 """Return the CPU time budget for ParamILS."""
1254 if self.__paramils_cpu_time_budget is None:
1255 self.__paramils_cpu_time_budget = self._abstract_getter(
1256 Settings.OPTION_paramils_cpu_time_budget
1257 )
1258 return self.__paramils_cpu_time_budget
1260 @property
1261 def paramils_min_runs(self: Settings) -> int:
1262 """Return the min runs for ParamILS."""
1263 if self.__paramils_min_runs is None:
1264 self.__paramils_min_runs = self._abstract_getter(
1265 Settings.OPTION_paramils_min_runs
1266 )
1267 return self.__paramils_min_runs
1269 @property
1270 def paramils_max_runs(self: Settings) -> int:
1271 """Return the max runs for ParamILS."""
1272 if self.__paramils_max_runs is None:
1273 self.__paramils_max_runs = self._abstract_getter(
1274 Settings.OPTION_paramils_max_runs
1275 )
1276 return self.__paramils_max_runs
1278 @property
1279 def paramils_random_restart(self: Settings) -> float:
1280 """Return the random restart for ParamILS."""
1281 if self.__paramils_random_restart is None:
1282 self.__paramils_random_restart = self._abstract_getter(
1283 Settings.OPTION_paramils_random_restart
1284 )
1285 return self.__paramils_random_restart
1287 @property
1288 def paramils_focused_approach(self: Settings) -> bool:
1289 """Return the focused approach for ParamILS."""
1290 if self.__paramils_focused_approach is None:
1291 self.__paramils_focused_approach = self._abstract_getter(
1292 Settings.OPTION_paramils_focused
1293 )
1294 return self.__paramils_focused_approach
1296 @property
1297 def paramils_use_cpu_time_in_tunertime(self: Settings) -> bool:
1298 """Return the use cpu time for ParamILS."""
1299 if self.__paramils_use_cpu_time_in_tunertime is None:
1300 self.__paramils_use_cpu_time_in_tunertime = self._abstract_getter(
1301 Settings.OPTION_paramils_count_tuner_time
1302 )
1303 return self.__paramils_use_cpu_time_in_tunertime
1305 @property
1306 def paramils_cli_cores(self: Settings) -> int:
1307 """The number of CPU cores to use for ParamILS."""
1308 if self.__paramils_cli_cores is None:
1309 self.__paramils_cli_cores = self._abstract_getter(
1310 Settings.OPTION_paramils_cli_cores
1311 )
1312 return self.__paramils_cli_cores
1314 @property
1315 def paramils_max_iterations(self: Settings) -> int:
1316 """Return the max iterations for ParamILS."""
1317 if self.__paramils_max_iterations is None:
1318 self.__paramils_max_iterations = self._abstract_getter(
1319 Settings.OPTION_paramils_max_iterations
1320 )
1321 return self.__paramils_max_iterations
1323 @property
1324 def paramils_number_initial_configurations(self: Settings) -> int:
1325 """Return the number of initial configurations for ParamILS."""
1326 if self.__paramils_number_initial_configurations is None:
1327 self.__paramils_number_initial_configurations = self._abstract_getter(
1328 Settings.OPTION_paramils_number_initial_configurations
1329 )
1330 return self.__paramils_number_initial_configurations
1332 # Parallel Portfolio settings ###
1333 @property
1334 def parallel_portfolio_check_interval(self: Settings) -> int:
1335 """Return the check interval for the parallel portfolio."""
1336 if self.__parallel_portfolio_check_interval is None:
1337 self.__parallel_portfolio_check_interval = self._abstract_getter(
1338 Settings.OPTION_parallel_portfolio_check_interval
1339 )
1340 return self.__parallel_portfolio_check_interval
1342 @property
1343 def parallel_portfolio_num_seeds_per_solver(self: Settings) -> int:
1344 """Return the number of seeds per solver for the parallel portfolio."""
1345 if self.__parallel_portfolio_num_seeds_per_solver is None:
1346 self.__parallel_portfolio_num_seeds_per_solver = self._abstract_getter(
1347 Settings.OPTION_parallel_portfolio_number_of_seeds_per_solver
1348 )
1349 return self.__parallel_portfolio_num_seeds_per_solver
1351 # Slurm settings ###
1352 @property
1353 def slurm_jobs_in_parallel(self: Settings) -> int:
1354 """Return the (maximum) number of jobs to run in parallel."""
1355 if self.__slurm_jobs_in_parallel is None:
1356 self.__slurm_jobs_in_parallel = self._abstract_getter(
1357 Settings.OPTION_slurm_parallel_jobs
1358 )
1359 return self.__slurm_jobs_in_parallel
1361 @property
1362 def slurm_job_prepend(self: Settings) -> str:
1363 """Return the slurm job prepend."""
1364 if self.__slurm_job_prepend is None and self.__settings.has_option(
1365 Settings.OPTION_slurm_prepend_script.section,
1366 Settings.OPTION_slurm_prepend_script.name,
1367 ):
1368 value = self.__settings[Settings.OPTION_slurm_prepend_script.section][
1369 Settings.OPTION_slurm_prepend_script.name
1370 ]
1371 try:
1372 path = Path(value)
1373 if path.is_file():
1374 with path.open() as f:
1375 value = f.read()
1376 f.close()
1377 self.__slurm_job_prepend = str(value)
1378 except TypeError:
1379 pass
1380 return self.__slurm_job_prepend
1382 @property
1383 def sbatch_settings(self: Settings) -> list[str]:
1384 """Return the sbatch settings."""
1385 sbatch_options = self.__settings[Settings.SECTION_slurm]
1386 # Return all non-predefined keys
1387 return [
1388 f"--{key}={sbatch_options[key]}"
1389 for key in sbatch_options.keys()
1390 if key not in Settings.sections_options[Settings.SECTION_slurm]
1391 ]
1393 # General functionalities ###
1395 def get_configurator_output_path(self: Settings, configurator: Configurator) -> Path:
1396 """Return the configurator output path."""
1397 return self.DEFAULT_configuration_output / configurator.name
1399 def get_configurator_settings(
1400 self: Settings, configurator_name: str
1401 ) -> dict[str, any]:
1402 """Return the settings of a specific configurator."""
1403 configurator_settings = {
1404 "solver_calls": self.configurator_solver_call_budget,
1405 "solver_cutoff_time": self.solver_cutoff_time,
1406 "max_iterations": self.configurator_max_iterations,
1407 }
1408 # In the settings below, we default to the configurator general settings if no
1409 # specific configurator settings are given, by using the [None] or [Value]
1410 if configurator_name == cim.SMAC2.__name__:
1411 # Return all settings from the SMAC2 section
1412 configurator_settings.update(
1413 {
1414 "cpu_time": self.smac2_cpu_time_budget,
1415 "wallclock_time": self.smac2_wallclock_time_budget,
1416 "target_cutoff_length": self.smac2_target_cutoff_length,
1417 "use_cpu_time_in_tunertime": self.smac2_use_tunertime_in_cpu_time_budget,
1418 "cli_cores": self.smac2_cli_cores,
1419 "max_iterations": self.smac2_max_iterations
1420 or configurator_settings["max_iterations"],
1421 }
1422 )
1423 elif configurator_name == cim.SMAC3.__name__:
1424 # Return all settings from the SMAC3 section
1425 del configurator_settings["max_iterations"] # SMAC3 does not have this?
1426 configurator_settings.update(
1427 {
1428 "smac_facade": self.smac3_facade,
1429 "max_ratio": self.smac3_facade_max_ratio,
1430 "crash_cost": self.smac3_crash_cost,
1431 "termination_cost_threshold": self.smac3_termination_cost_threshold,
1432 "walltime_limit": self.smac3_wallclock_time_budget,
1433 "cputime_limit": self.smac3_cpu_time_budget,
1434 "use_default_config": self.smac3_use_default_config,
1435 "min_budget": self.smac3_min_budget,
1436 "max_budget": self.smac3_max_budget,
1437 "solver_calls": self.smac3_number_of_trials
1438 or configurator_settings["solver_calls"],
1439 }
1440 )
1441 # Do not pass None values to SMAC3, its Scenario resolves default settings
1442 configurator_settings = {
1443 key: value
1444 for key, value in configurator_settings.items()
1445 if value is not None
1446 }
1447 elif configurator_name == cim.IRACE.__name__:
1448 # Return all settings from the IRACE section
1449 configurator_settings.update(
1450 {
1451 "solver_calls": self.irace_max_experiments,
1452 "max_time": self.irace_max_time,
1453 "first_test": self.irace_first_test,
1454 "mu": self.irace_mu,
1455 "max_iterations": self.irace_max_iterations
1456 or configurator_settings["max_iterations"],
1457 }
1458 )
1459 if (
1460 configurator_settings["solver_calls"] == 0
1461 and configurator_settings["max_time"] == 0
1462 ): # Default to base
1463 configurator_settings["solver_calls"] = (
1464 self.configurator_solver_call_budget
1465 )
1466 elif configurator_name == cim.ParamILS.__name__:
1467 configurator_settings.update(
1468 {
1469 "tuner_timeout": self.paramils_cpu_time_budget,
1470 "min_runs": self.paramils_min_runs,
1471 "max_runs": self.paramils_max_runs,
1472 "focused_ils": self.paramils_focused_approach,
1473 "initial_configurations": self.paramils_number_initial_configurations,
1474 "random_restart": self.paramils_random_restart,
1475 "cli_cores": self.paramils_cli_cores,
1476 "use_cpu_time_in_tunertime": self.paramils_use_cpu_time_in_tunertime,
1477 "max_iterations": self.paramils_max_iterations
1478 or configurator_settings["max_iterations"],
1479 }
1480 )
1481 return configurator_settings
1483 @staticmethod
1484 def check_settings_changes(
1485 cur_settings: Settings, prev_settings: Settings, verbose: bool = True
1486 ) -> bool:
1487 """Check if there are changes between the previous and the current settings.
1489 Prints any section changes, printing None if no setting was found.
1491 Args:
1492 cur_settings: The current settings
1493 prev_settings: The previous settings
1494 verbose: Verbosity of the function
1496 Returns:
1497 True iff there are changes.
1498 """
1499 cur_dict = cur_settings.__settings._sections
1500 prev_dict = prev_settings.__settings._sections
1502 cur_sections_set = set(cur_dict.keys())
1503 prev_sections_set = set(prev_dict.keys())
1505 sections_remained = cur_sections_set & prev_sections_set
1506 option_changed = False
1507 for section in sections_remained:
1508 printed_section = False
1509 names = set(cur_dict[section].keys()) | set(prev_dict[section].keys())
1510 if (
1511 section == "general" and "seed" in names
1512 ): # Do not report on the seed, is supposed to change
1513 names.remove("seed")
1514 for name in names:
1515 # if name is not present in one of the two dicts, get None as placeholder
1516 cur_val = cur_dict[section].get(name, None)
1517 prev_val = prev_dict[section].get(name, None)
1519 # If cur val is None, it is default
1520 if cur_val is not None and cur_val != prev_val:
1521 if not option_changed and verbose: # Print the initial
1522 print("[INFO] The following attributes/options have changed:")
1523 option_changed = True
1525 # do we have yet to print the section?
1526 if not printed_section and verbose:
1527 print(f" - In the section '{section}':")
1528 printed_section = True
1530 # print actual change
1531 if verbose:
1532 print(f" · '{name}' changed from '{prev_val}' to '{cur_val}'")
1534 return option_changed