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