Coverage for src/sparkle/platform/settings_objects.py: 96%
589 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-08 12:00 +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", argparse.BooleanOptionalAction):
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_groupwise_computation = Option(
198 "groupwise_computation",
199 SECTION_general,
200 bool,
201 True,
202 tuple(),
203 "Run extractors per feature group (use --no-groupwise-computation to disable).",
204 cli_kwargs={
205 "action": argparse.BooleanOptionalAction,
206 "default": None,
207 },
208 )
209 OPTION_run_on = Option(
210 "run_on",
211 SECTION_general,
212 Runner,
213 None,
214 tuple(),
215 "On which compute resource to execute.",
216 cli_kwargs={"choices": [Runner.LOCAL, Runner.SLURM]},
217 )
218 OPTION_verbosity = Option(
219 "verbosity",
220 SECTION_general,
221 VerbosityLevel,
222 VerbosityLevel.STANDARD,
223 ("verbosity_level",),
224 "Verbosity level.",
225 )
226 OPTION_seed = Option(
227 "seed",
228 SECTION_general,
229 int,
230 None,
231 tuple(),
232 "Seed to use for pseudo-random number generators.",
233 )
234 OPTION_appendices = Option(
235 "appendices",
236 SECTION_general,
237 bool,
238 False,
239 tuple(),
240 "Include the appendix section in the generated report.",
241 cli_kwargs={
242 "action": "store_true",
243 "default": None,
244 },
245 )
247 # CONFIGURATION Options
248 SECTION_configuration = "configuration"
249 OPTION_configurator_number_of_runs = Option(
250 "number_of_runs",
251 SECTION_configuration,
252 int,
253 None,
254 tuple(),
255 "The number of independent configurator jobs/runs.",
256 )
257 OPTION_configurator_solver_call_budget = Option(
258 "solver_calls",
259 SECTION_configuration,
260 int,
261 None,
262 tuple(),
263 "The maximum number of calls (evaluations) a configurator can do in a single "
264 "run of the solver.",
265 )
266 OPTION_configurator_max_iterations = Option(
267 "max_iterations",
268 SECTION_configuration,
269 int,
270 None,
271 ("maximum_iterations",),
272 "The maximum number of iterations a configurator can do in a single job.",
273 )
275 # ABLATION Options
276 SECTION_ablation = "ablation"
277 OPTION_ablation_racing = Option(
278 "racing",
279 SECTION_ablation,
280 bool,
281 False,
282 ("ablation_racing",),
283 "Set a flag indicating whether racing should be used for ablation.",
284 )
285 OPTION_ablation_clis_per_node = Option(
286 "clis_per_node",
287 SECTION_ablation,
288 int,
289 None,
290 (
291 "max_parallel_runs_per_node",
292 "maximum_parallel_runs_per_node",
293 ),
294 "The maximum number of ablation analysis jobs to run in parallel on a single "
295 "node.",
296 )
298 # SELECTION Options
299 SECTION_selection = "selection"
300 OPTION_selection_class = Option(
301 "selector_class",
302 SECTION_selection,
303 str,
304 None,
305 ("class",),
306 "Can contain any of the class names as defined in asf.selectors.",
307 )
308 OPTION_selection_model = Option(
309 "selector_model",
310 SECTION_selection,
311 str,
312 None,
313 ("model",),
314 "Can be any of the sklearn.ensemble models.",
315 )
316 OPTION_minimum_marginal_contribution = Option(
317 "minimum_marginal_contribution",
318 SECTION_selection,
319 float,
320 0.01,
321 (
322 "minimum_marginal_contribution",
323 "minimum_contribution",
324 "contribution_threshold",
325 ),
326 "The minimum marginal contribution a solver (configuration) must have to be used for the selector.",
327 )
329 # SMAC2 Options
330 SECTION_smac2 = "smac2"
331 OPTION_smac2_wallclock_time_budget = Option(
332 "wallclock_time_budget",
333 SECTION_smac2,
334 int,
335 None,
336 ("wallclock_time",),
337 "The wallclock time budget in seconds for each SMAC2 run.",
338 )
339 OPTION_smac2_cpu_time_budget = Option(
340 "cpu_time_budget",
341 SECTION_smac2,
342 int,
343 None,
344 ("cpu_time",),
345 "The cpu time budget in seconds for each SMAC2 run.",
346 )
347 OPTION_smac2_target_cutoff_length = Option(
348 "target_cutoff_length",
349 SECTION_smac2,
350 str,
351 None,
352 ("cutoff_length", "solver_cutoff_length"),
353 "The target cutoff length for SMAC2 solver call.",
354 )
355 OPTION_smac2_count_tuner_time = Option(
356 "use_cpu_time_in_tunertime",
357 SECTION_smac2,
358 bool,
359 None,
360 ("countSMACTimeAsTunerTime",),
361 "Whether to count and deducted SMAC2 CPU time from the CPU time budget.",
362 )
363 OPTION_smac2_cli_cores = Option(
364 "cli_cores",
365 SECTION_smac2,
366 int,
367 None,
368 tuple(),
369 "Number of cores to use to execute SMAC2 runs.",
370 )
371 OPTION_smac2_max_iterations = Option(
372 "max_iterations",
373 SECTION_smac2,
374 int,
375 None,
376 (
377 "iteration_limit",
378 "numIterations",
379 "numberOfIterations",
380 ),
381 "The maximum number of iterations SMAC2 configurator can do in a single job.",
382 )
384 # SMAC3 Options
385 SECTION_smac3 = "smac3"
386 OPTION_smac3_number_of_trials = Option(
387 "n_trials",
388 SECTION_smac3,
389 int,
390 None,
391 ("n_trials", "number_of_trials", "solver_calls"),
392 "Maximum calls SMAC3 is allowed to make to the Solver in a single run/job.",
393 )
394 OPTION_smac3_facade = Option(
395 "facade",
396 SECTION_smac3,
397 str,
398 "AlgorithmConfigurationFacade",
399 ("facade", "smac_facade", "smac3_facade"),
400 "The SMAC3 Facade to use. See the SMAC3 documentation for more options.",
401 )
402 OPTION_smac3_facade_max_ratio = Option(
403 "facade_max_ratio",
404 SECTION_smac3,
405 float,
406 None,
407 ("facade_max_ratio", "smac3_facade_max_ratio", "smac3_facade_max_ratio"),
408 "The SMAC3 Facade max ratio. See the SMAC3 documentation for more options.",
409 )
410 OPTION_smac3_crash_cost = Option(
411 "crash_cost",
412 SECTION_smac3,
413 float,
414 None,
415 tuple(),
416 "Defines the cost for a failed trial, defaults in SMAC3 to np.inf.",
417 )
418 OPTION_smac3_termination_cost_threshold = Option(
419 "termination_cost_threshold",
420 SECTION_smac3,
421 float,
422 None,
423 tuple(),
424 "Defines a cost threshold when the SMAC3 optimization should stop.",
425 )
426 OPTION_smac3_wallclock_time_budget = Option(
427 "walltime_limit",
428 SECTION_smac3,
429 float,
430 None,
431 ("wallclock_time", "wallclock_budget", "wallclock_time_budget"),
432 "The maximum time in seconds that SMAC3 is allowed to run per job.",
433 )
434 OPTION_smac3_cpu_time_budget = Option(
435 "cputime_limit",
436 SECTION_smac3,
437 float,
438 None,
439 ("cpu_time", "cpu_budget", "cpu_time_budget"),
440 "The maximum CPU time in seconds that SMAC3 is allowed to run per job.",
441 )
442 OPTION_smac3_use_default_config = Option(
443 "use_default_config",
444 SECTION_smac3,
445 bool,
446 None,
447 tuple(),
448 "If True, the configspace's default configuration is evaluated in the initial "
449 "design. For historic benchmark reasons, this is False by default. Notice, that "
450 "this will result in n_configs + 1 for the initial design. Respecting n_trials, "
451 "this will result in one fewer evaluated configuration in the optimization.",
452 )
453 OPTION_smac3_min_budget = Option(
454 "min_budget",
455 SECTION_smac3,
456 float,
457 None,
458 ("minimum_budget",),
459 "The minimum budget (epochs, subset size, number of instances, ...) that is used"
460 " for the optimization. Use this argument if you use multi-fidelity or instance "
461 "optimization.",
462 )
463 OPTION_smac3_max_budget = Option(
464 "max_budget",
465 SECTION_smac3,
466 float,
467 None,
468 ("maximum_budget",),
469 "The maximum budget (epochs, subset size, number of instances, ...) that is used"
470 " for the optimization. Use this argument if you use multi-fidelity or instance "
471 "optimization.",
472 )
474 # IRACE Options
475 SECTION_irace = "irace"
476 OPTION_irace_max_time = Option(
477 "max_time",
478 SECTION_irace,
479 int,
480 0,
481 ("maximum_time",),
482 "The maximum time in seconds for each IRACE run/job.",
483 )
484 OPTION_irace_max_experiments = Option(
485 "max_experiments",
486 SECTION_irace,
487 int,
488 0,
489 ("maximum_experiments",),
490 "The maximum number of experiments for each IRACE run/job.",
491 )
492 OPTION_irace_first_test = Option(
493 "first_test",
494 SECTION_irace,
495 int,
496 None,
497 tuple(),
498 "Specifies how many instances are evaluated before the first elimination test. "
499 "IRACE Default: 5.",
500 )
501 OPTION_irace_mu = Option(
502 "mu",
503 SECTION_irace,
504 int,
505 None,
506 tuple(),
507 "Parameter used to define the number of configurations sampled and evaluated at "
508 "each iteration. IRACE Default: 5.",
509 )
510 OPTION_irace_max_iterations = Option(
511 "max_iterations",
512 SECTION_irace,
513 int,
514 None,
515 ("nb_iterations", "iterations", "max_iterations"),
516 "Maximum number of iterations to be executed. Each iteration involves the "
517 "generation of new configurations and the use of racing to select the best "
518 "configurations. By default (with 0), irace calculates a minimum number of "
519 "iterations as N^iter = ⌊2 + log2 N param⌋, where N^param is the number of "
520 "non-fixed parameters to be tuned. Setting this parameter may make irace stop "
521 "sooner than it should without using all the available budget. IRACE recommends"
522 " to use the default value (Empty).",
523 )
525 # ParamILS Options
526 SECTION_paramils = "paramils"
527 OPTION_paramils_min_runs = Option(
528 "min_runs",
529 SECTION_paramils,
530 int,
531 None,
532 ("minimum_runs",),
533 "Set the minimum number of runs for ParamILS for each run/job.",
534 )
535 OPTION_paramils_max_runs = Option(
536 "max_runs",
537 SECTION_paramils,
538 int,
539 None,
540 ("maximum_runs",),
541 "Set the maximum number of runs for ParamILS for each run/job.",
542 )
543 OPTION_paramils_cpu_time_budget = Option(
544 "cputime_budget",
545 SECTION_paramils,
546 int,
547 None,
548 (
549 "cputime_limit",
550 "cputime_limit",
551 "tunertime_limit",
552 "tuner_timeout",
553 "tunerTimeout",
554 ),
555 "The maximum CPU time for each ParamILS run/job.",
556 )
557 OPTION_paramils_random_restart = Option(
558 "random_restart",
559 SECTION_paramils,
560 float,
561 None,
562 tuple(),
563 "Set the random restart chance for ParamILS.",
564 )
565 OPTION_paramils_focused = Option(
566 "focused_approach",
567 SECTION_paramils,
568 bool,
569 False,
570 ("focused",),
571 "Set the focused approach for ParamILS.",
572 )
573 OPTION_paramils_count_tuner_time = Option(
574 "use_cpu_time_in_tunertime",
575 SECTION_paramils,
576 bool,
577 None,
578 tuple(),
579 "Whether to count and deducted ParamILS CPU time from the CPU time budget.",
580 )
581 OPTION_paramils_cli_cores = Option(
582 "cli_cores",
583 SECTION_paramils,
584 int,
585 None,
586 tuple(),
587 "Number of cores to use for ParamILS runs.",
588 )
589 OPTION_paramils_max_iterations = Option(
590 "max_iterations",
591 SECTION_paramils,
592 int,
593 None,
594 (
595 "iteration_limit",
596 "numIterations",
597 "numberOfIterations",
598 "maximum_iterations",
599 ),
600 "The maximum number of ParamILS iterations per run/job.",
601 )
602 OPTION_paramils_number_initial_configurations = Option(
603 "initial_configurations",
604 SECTION_paramils,
605 int,
606 None,
607 "The number of initial configurations ParamILS should evaluate.",
608 )
610 SECTION_parallel_portfolio = "parallel_portfolio"
611 OPTION_parallel_portfolio_check_interval = Option(
612 "check_interval",
613 SECTION_parallel_portfolio,
614 int,
615 None,
616 tuple(),
617 "The interval time in seconds when Solvers are checked for their status.",
618 )
619 OPTION_parallel_portfolio_number_of_seeds_per_solver = Option(
620 "num_seeds_per_solver",
621 SECTION_parallel_portfolio,
622 int,
623 None,
624 ("solver_seeds",),
625 "The number of seeds per solver.",
626 )
628 SECTION_slurm = "slurm"
629 OPTION_slurm_parallel_jobs = Option(
630 "number_of_jobs_in_parallel",
631 SECTION_slurm,
632 int,
633 None,
634 ("num_job_in_parallel",),
635 "The number of jobs to run in parallel.",
636 )
637 OPTION_slurm_prepend_script = Option(
638 "prepend_script",
639 SECTION_slurm,
640 str,
641 None,
642 ("job_prepend", "prepend"),
643 "Slurm script to prepend to the sbatch.",
644 )
646 sections_options: dict[str, list[Option]] = {
647 SECTION_general: [
648 OPTION_objectives,
649 OPTION_configurator,
650 OPTION_solver_cutoff_time,
651 OPTION_extractor_cutoff_time,
652 OPTION_groupwise_computation,
653 OPTION_run_on,
654 OPTION_appendices,
655 OPTION_verbosity,
656 OPTION_seed,
657 ],
658 SECTION_configuration: [
659 OPTION_configurator_number_of_runs,
660 OPTION_configurator_solver_call_budget,
661 OPTION_configurator_max_iterations,
662 ],
663 SECTION_ablation: [
664 OPTION_ablation_racing,
665 OPTION_ablation_clis_per_node,
666 ],
667 SECTION_selection: [
668 OPTION_selection_class,
669 OPTION_selection_model,
670 OPTION_minimum_marginal_contribution,
671 ],
672 SECTION_smac2: [
673 OPTION_smac2_wallclock_time_budget,
674 OPTION_smac2_cpu_time_budget,
675 OPTION_smac2_target_cutoff_length,
676 OPTION_smac2_count_tuner_time,
677 OPTION_smac2_cli_cores,
678 OPTION_smac2_max_iterations,
679 ],
680 SECTION_smac3: [
681 OPTION_smac3_number_of_trials,
682 OPTION_smac3_facade,
683 OPTION_smac3_facade_max_ratio,
684 OPTION_smac3_crash_cost,
685 OPTION_smac3_termination_cost_threshold,
686 OPTION_smac3_wallclock_time_budget,
687 OPTION_smac3_cpu_time_budget,
688 OPTION_smac3_use_default_config,
689 OPTION_smac3_min_budget,
690 OPTION_smac3_max_budget,
691 ],
692 SECTION_irace: [
693 OPTION_irace_max_time,
694 OPTION_irace_max_experiments,
695 OPTION_irace_first_test,
696 OPTION_irace_mu,
697 OPTION_irace_max_iterations,
698 ],
699 SECTION_paramils: [
700 OPTION_paramils_min_runs,
701 OPTION_paramils_max_runs,
702 OPTION_paramils_cpu_time_budget,
703 OPTION_paramils_random_restart,
704 OPTION_paramils_focused,
705 OPTION_paramils_count_tuner_time,
706 OPTION_paramils_cli_cores,
707 OPTION_paramils_max_iterations,
708 OPTION_paramils_number_initial_configurations,
709 ],
710 SECTION_parallel_portfolio: [
711 OPTION_parallel_portfolio_check_interval,
712 OPTION_parallel_portfolio_number_of_seeds_per_solver,
713 ],
714 SECTION_slurm: [OPTION_slurm_parallel_jobs, OPTION_slurm_prepend_script],
715 }
717 def __init__(
718 self: Settings, file_path: Path, argsv: argparse.Namespace = None
719 ) -> None:
720 """Initialise a settings object.
722 Args:
723 file_path (Path): Path to the settings file.
724 argsv: The CLI arguments to process.
725 """
726 # Settings 'dictionary' in configparser format
727 self.__settings = configparser.ConfigParser()
728 for section in self.sections_options.keys():
729 self.__settings.add_section(section)
730 self.__settings[section] = {}
732 # General attributes
733 self.__sparkle_objectives: list[SparkleObjective] = None
734 self.__general_sparkle_configurator: Configurator = None
735 self.__solver_cutoff_time: int = None
736 self.__extractor_cutoff_time: int = None
737 self.__groupwise_computation: bool = None
738 self.__run_on: Runner = None
739 self.__appendices: bool = False
740 self.__verbosity_level: VerbosityLevel = None
741 self.__seed: Optional[int] = None
743 # Configuration attributes
744 self.__configurator_solver_call_budget: int = None
745 self.__configurator_number_of_runs: int = None
746 self.__configurator_max_iterations: int = None
748 # Ablation attributes
749 self.__ablation_racing_flag: bool = None
750 self.__ablation_max_parallel_runs_per_node: int = None
752 # Selection attributes
753 self.__selection_model: str = None
754 self.__selection_class: str = None
755 self.__minimum_marginal_contribution: float = None
757 # SMAC2 attributes
758 self.__smac2_wallclock_time_budget: int = None
759 self.__smac2_cpu_time_budget: int = None
760 self.__smac2_target_cutoff_length: str = None
761 self.__smac2_use_tunertime_in_cpu_time_budget: bool = None
762 self.__smac2_cli_cores: int = None
763 self.__smac2_max_iterations: int = None
765 # SMAC3 attributes
766 self.__smac3_number_of_trials: int = None
767 self.__smac3_facade: str = None
768 self.__smac3_facade_max_ratio: float = None
769 self.__smac3_crash_cost: float = None
770 self.__smac3_termination_cost_threshold: float = None
771 self.__smac3_wallclock_time_limit: int = None
772 self.__smac3_cputime_limit: int = None
773 self.__smac3_use_default_config: bool = None
774 self.__smac3_min_budget: float = None
775 self.__smac3_max_budget: float = None
777 # IRACE attributes
778 self.__irace_max_time: int = None
779 self.__irace_max_experiments: int = None
780 self.__irace_first_test: int = None
781 self.__irace_mu: int = None
782 self.__irace_max_iterations: int = None
784 # ParamILS attributes
785 self.__paramils_cpu_time_budget: int = None
786 self.__paramils_min_runs: int = None
787 self.__paramils_max_runs: int = None
788 self.__paramils_random_restart: float = None
789 self.__paramils_focused_approach: bool = None
790 self.__paramils_use_cpu_time_in_tunertime: bool = None
791 self.__paramils_cli_cores: int = None
792 self.__paramils_max_iterations: int = None
793 self.__paramils_number_initial_configurations: int = None
795 # Parallel portfolio attributes
796 self.__parallel_portfolio_check_interval: int = None
797 self.__parallel_portfolio_num_seeds_per_solver: int = None
799 # Slurm attributes
800 self.__slurm_jobs_in_parallel: int = None
801 self.__slurm_job_prepend: str = None
803 # The seed that has been used to set the random state
804 self.random_state: Optional[int] = None
806 if file_path and file_path.exists():
807 self.read_settings_ini(file_path)
809 if argsv:
810 self.apply_arguments(argsv)
812 def read_settings_ini(self: Settings, file_path: Path) -> None:
813 """Read the settings from an INI file."""
814 if not file_path.exists():
815 raise ValueError(f"Settings file {file_path} does not exist.")
816 # Read file
817 file_settings = configparser.ConfigParser()
818 file_settings.read(file_path)
820 # Set internal settings based on data read from FILE if they were read
821 # successfully
822 if file_settings.sections() == []:
823 # Print error if unable to read the settings
824 print(
825 f"ERROR: Failed to read settings from {file_path} The file may "
826 "have been empty or be in another format than INI."
827 )
828 return
830 for section in file_settings.sections():
831 if section not in self.__settings.sections():
832 print(f'Unrecognised section: "{section}" in file {file_path} ignored')
833 continue
834 for option_name in file_settings.options(section):
835 if option_name not in self.sections_options[section]:
836 if section == Settings.SECTION_slurm: # Flexible section
837 self.__settings.set(
838 section,
839 option_name,
840 file_settings.get(section, option_name),
841 )
842 else:
843 print(
844 f'Unrecognised section - option combination: "{section} '
845 f'{option_name}" in file {file_path} ignored'
846 )
847 continue
848 option_index = self.sections_options[section].index(option_name)
849 option = self.sections_options[section][option_index]
850 self.__settings.set(
851 section, option.name, file_settings.get(section, option_name)
852 )
853 del file_settings
855 def write_settings_ini(self: Settings, file_path: Path) -> None:
856 """Write the settings to an INI file."""
857 # Create needed directories if they don't exist
858 file_path.parent.mkdir(parents=True, exist_ok=True)
859 # We don't write empty sections
860 for section in self.__settings.sections():
861 if not self.__settings[section]:
862 self.__settings.remove_section(section)
863 with file_path.open("w") as fout:
864 self.__settings.write(fout)
865 for section in self.sections_options.keys():
866 if section not in self.__settings.sections():
867 self.__settings.add_section(section)
869 def write_used_settings(self: Settings) -> None:
870 """Write the used settings to the default locations."""
871 # Write to latest settings file
872 self.write_settings_ini(self.DEFAULT_previous_settings_path)
874 def apply_arguments(self: Settings, argsv: argparse.Namespace) -> None:
875 """Apply the arguments to the settings."""
876 # Read a possible second file, that overwrites the first, where applicable
877 # e.g. settings are not deleted, but overwritten where applicable
878 if hasattr(argsv, "settings_file") and argsv.settings_file:
879 self.read_settings_ini(argsv.settings_file)
880 # Match all possible arguments to the settings
881 for argument in argsv.__dict__.keys():
882 value = argsv.__dict__[argument]
883 if value is None:
884 continue # Skip None
885 if isinstance(value, Enum):
886 value = value.name
887 elif isinstance(value, list):
888 value = ",".join([str(s) for s in value])
889 else:
890 value = str(value)
891 for section in self.sections_options.keys():
892 if argument in self.sections_options[section]:
893 index = self.sections_options[section].index(argument)
894 option = self.sections_options[section][index]
895 self.__settings.set(option.section, option.name, value)
896 break
898 def _abstract_getter(self: Settings, option: Option) -> Any:
899 """Abstract getter method."""
900 if self.__settings.has_option(option.section, option.name):
901 if option.type is bool:
902 return self.__settings.getboolean(option.section, option.name)
903 value = self.__settings.get(option.section, option.name)
904 if not isinstance(value, option.type):
905 if issubclass(option.type, Enum):
906 return option.type[value.upper()]
907 return option.type(value) # Attempt to resolve str to type
908 return value
909 return option.default_value
911 # General settings ###
912 @property
913 def objectives(self: Settings) -> list[SparkleObjective]:
914 """Get the objectives for Sparkle."""
915 if self.__sparkle_objectives is None and self.__settings.has_option(
916 Settings.SECTION_general, "objectives"
917 ):
918 objectives = self.__settings[Settings.SECTION_general]["objectives"]
919 if "status:metric" not in objectives:
920 objectives += ",status:metric"
921 if "cpu_time:metric" not in objectives:
922 objectives += ",cpu_time:metric"
923 if "wall_time:metric" not in objectives:
924 objectives += ",wall_time:metric"
925 if "memory:metric" not in objectives:
926 objectives += ",memory:metric"
927 self.__sparkle_objectives = [
928 resolve_objective(obj) for obj in objectives.split(",")
929 ]
930 return self.__sparkle_objectives
932 @property
933 def configurator(self: Settings) -> Configurator:
934 """Get the configurator class (instance)."""
935 if self.__general_sparkle_configurator is None and self.__settings.has_option(
936 Settings.OPTION_configurator.section, Settings.OPTION_configurator.name
937 ):
938 # NOTE: Import here for speed up if not using configurator
939 from sparkle.configurator.implementations import resolve_configurator
941 self.__general_sparkle_configurator = resolve_configurator(
942 self.__settings.get(
943 Settings.OPTION_configurator.section,
944 Settings.OPTION_configurator.name,
945 )
946 )()
947 return self.__general_sparkle_configurator
949 @property
950 def solver_cutoff_time(self: Settings) -> int:
951 """Solver cutoff time in seconds."""
952 if self.__solver_cutoff_time is None:
953 self.__solver_cutoff_time = self._abstract_getter(
954 Settings.OPTION_solver_cutoff_time
955 )
956 return self.__solver_cutoff_time
958 @property
959 def extractor_cutoff_time(self: Settings) -> int:
960 """Extractor cutoff time in seconds."""
961 if self.__extractor_cutoff_time is None:
962 self.__extractor_cutoff_time = self._abstract_getter(
963 Settings.OPTION_extractor_cutoff_time
964 )
965 return self.__extractor_cutoff_time
967 @property
968 def groupwise_computation(self: Settings) -> bool:
969 """Whether to run extractors per feature group."""
970 if self.__groupwise_computation is None:
971 self.__groupwise_computation = self._abstract_getter(
972 Settings.OPTION_groupwise_computation
973 )
974 return self.__groupwise_computation
976 @property
977 def run_on(self: Settings) -> Runner:
978 """On which compute to run (Local or Slurm)."""
979 if self.__run_on is None:
980 self.__run_on = self._abstract_getter(Settings.OPTION_run_on)
981 return self.__run_on
983 @property
984 def appendices(self: Settings) -> bool:
985 """Whether to include appendices in the report."""
986 return self._abstract_getter(Settings.OPTION_appendices)
988 @property
989 def verbosity_level(self: Settings) -> VerbosityLevel:
990 """Verbosity level to use in CLI commands."""
991 if self.__verbosity_level is None:
992 if self.__settings.has_option(
993 Settings.OPTION_verbosity.section, Settings.OPTION_verbosity.name
994 ):
995 self.__verbosity_level = VerbosityLevel[
996 self.__settings.get(
997 Settings.OPTION_verbosity.section,
998 Settings.OPTION_verbosity.name,
999 )
1000 ]
1001 else:
1002 self.__verbosity_level = Settings.OPTION_verbosity.default_value
1003 return self.__verbosity_level
1005 @property
1006 def seed(self: Settings) -> int:
1007 """Seed to use in CLI commands."""
1008 if self.__seed is not None:
1009 return self.__seed
1011 section, name = Settings.OPTION_seed.section, Settings.OPTION_seed.name
1012 if self.__settings.has_option(section, name):
1013 value = self.__settings.get(section, name)
1014 self.__seed = int(value)
1015 else:
1016 self.__seed = Settings.OPTION_seed.default_value
1018 return self.__seed
1020 @seed.setter
1021 def seed(self: Settings, value: int) -> None:
1022 """Set the seed value (overwrites settings)."""
1023 self.__seed = value
1024 self.__settings.set(
1025 Settings.OPTION_seed.section, Settings.OPTION_seed.name, str(self.__seed)
1026 )
1028 # Configuration settings ###
1029 @property
1030 def configurator_solver_call_budget(self: Settings) -> int:
1031 """The amount of calls a configurator can do to the solver."""
1032 if self.__configurator_solver_call_budget is None:
1033 self.__configurator_solver_call_budget = self._abstract_getter(
1034 Settings.OPTION_configurator_solver_call_budget
1035 )
1036 return self.__configurator_solver_call_budget
1038 @property
1039 def configurator_number_of_runs(self: Settings) -> int:
1040 """Get the amount of configurator runs to do."""
1041 if self.__configurator_number_of_runs is None:
1042 self.__configurator_number_of_runs = self._abstract_getter(
1043 Settings.OPTION_configurator_number_of_runs
1044 )
1045 return self.__configurator_number_of_runs
1047 @property
1048 def configurator_max_iterations(self: Settings) -> int:
1049 """Get the amount of configurator iterations to do."""
1050 if self.__configurator_max_iterations is None:
1051 self.__configurator_max_iterations = self._abstract_getter(
1052 Settings.OPTION_configurator_max_iterations
1053 )
1054 return self.__configurator_max_iterations
1056 # Ablation settings ###
1057 @property
1058 def ablation_racing_flag(self: Settings) -> bool:
1059 """Get the ablation racing flag."""
1060 if self.__ablation_racing_flag is None:
1061 self.__ablation_racing_flag = self._abstract_getter(
1062 Settings.OPTION_ablation_racing
1063 )
1064 return self.__ablation_racing_flag
1066 @property
1067 def ablation_max_parallel_runs_per_node(self: Settings) -> int:
1068 """Get the ablation max parallel runs per node."""
1069 if self.__ablation_max_parallel_runs_per_node is None:
1070 self.__ablation_max_parallel_runs_per_node = self._abstract_getter(
1071 Settings.OPTION_ablation_clis_per_node
1072 )
1073 return self.__ablation_max_parallel_runs_per_node
1075 # Selection settings ###
1076 @property
1077 def selection_model(self: Settings) -> str:
1078 """Get the selection model."""
1079 if self.__selection_model is None:
1080 self.__selection_model = self._abstract_getter(
1081 Settings.OPTION_selection_model
1082 )
1083 return self.__selection_model
1085 @property
1086 def selection_class(self: Settings) -> str:
1087 """Get the selection class."""
1088 if self.__selection_class is None:
1089 self.__selection_class = self._abstract_getter(
1090 Settings.OPTION_selection_class
1091 )
1092 return self.__selection_class
1094 @property
1095 def minimum_marginal_contribution(self: Settings) -> float:
1096 """Get the minimum marginal contribution."""
1097 if self.__minimum_marginal_contribution is None:
1098 self.__minimum_marginal_contribution = self._abstract_getter(
1099 Settings.OPTION_minimum_marginal_contribution
1100 )
1101 return self.__minimum_marginal_contribution
1103 # Configuration: SMAC2 specific settings ###
1104 @property
1105 def smac2_wallclock_time_budget(self: Settings) -> int:
1106 """Return the SMAC2 wallclock budget per configuration run in seconds."""
1107 if self.__smac2_wallclock_time_budget is None:
1108 self.__smac2_wallclock_time_budget = self._abstract_getter(
1109 Settings.OPTION_smac2_wallclock_time_budget
1110 )
1111 return self.__smac2_wallclock_time_budget
1113 @property
1114 def smac2_cpu_time_budget(self: Settings) -> int:
1115 """Return the SMAC2 CPU budget per configuration run in seconds."""
1116 if self.__smac2_cpu_time_budget is None:
1117 self.__smac2_cpu_time_budget = self._abstract_getter(
1118 Settings.OPTION_smac2_cpu_time_budget
1119 )
1120 return self.__smac2_cpu_time_budget
1122 @property
1123 def smac2_target_cutoff_length(self: Settings) -> str:
1124 """Return the SMAC2 target cutoff length."""
1125 if self.__smac2_target_cutoff_length is None:
1126 self.__smac2_target_cutoff_length = self._abstract_getter(
1127 Settings.OPTION_smac2_target_cutoff_length
1128 )
1129 return self.__smac2_target_cutoff_length
1131 @property
1132 def smac2_use_tunertime_in_cpu_time_budget(self: Settings) -> bool:
1133 """Return whether SMAC2 time should be used in CPU time budget."""
1134 if self.__smac2_use_tunertime_in_cpu_time_budget is None:
1135 self.__smac2_use_tunertime_in_cpu_time_budget = self._abstract_getter(
1136 Settings.OPTION_smac2_count_tuner_time
1137 )
1138 return self.__smac2_use_tunertime_in_cpu_time_budget
1140 @property
1141 def smac2_cli_cores(self: Settings) -> int:
1142 """Return the SMAC2 CLI cores."""
1143 if self.__smac2_cli_cores is None:
1144 self.__smac2_cli_cores = self._abstract_getter(
1145 Settings.OPTION_smac2_cli_cores
1146 )
1147 return self.__smac2_cli_cores
1149 @property
1150 def smac2_max_iterations(self: Settings) -> int:
1151 """Return the SMAC2 max iterations."""
1152 if self.__smac2_max_iterations is None:
1153 self.__smac2_max_iterations = self._abstract_getter(
1154 Settings.OPTION_smac2_max_iterations
1155 )
1156 return self.__smac2_max_iterations
1158 # SMAC3 attributes ###
1159 @property
1160 def smac3_number_of_trials(self: Settings) -> int:
1161 """Return the SMAC3 number of trials."""
1162 if self.__smac3_number_of_trials is None:
1163 self.__smac3_number_of_trials = self._abstract_getter(
1164 Settings.OPTION_smac3_number_of_trials
1165 )
1166 return self.__smac3_number_of_trials
1168 @property
1169 def smac3_facade(self: Settings) -> str:
1170 """Return the SMAC3 facade."""
1171 if self.__smac3_facade is None:
1172 self.__smac3_facade = self._abstract_getter(Settings.OPTION_smac3_facade)
1173 return self.__smac3_facade
1175 @property
1176 def smac3_facade_max_ratio(self: Settings) -> float:
1177 """Return the SMAC3 facade max ratio."""
1178 if self.__smac3_facade_max_ratio is None:
1179 self.__smac3_facade_max_ratio = self._abstract_getter(
1180 Settings.OPTION_smac3_facade_max_ratio
1181 )
1182 return self.__smac3_facade_max_ratio
1184 @property
1185 def smac3_crash_cost(self: Settings) -> float:
1186 """Return the SMAC3 crash cost."""
1187 if self.__smac3_crash_cost is None:
1188 self.__smac3_crash_cost = self._abstract_getter(
1189 Settings.OPTION_smac3_crash_cost
1190 )
1191 return self.__smac3_crash_cost
1193 @property
1194 def smac3_termination_cost_threshold(self: Settings) -> float:
1195 """Return the SMAC3 termination cost threshold."""
1196 if self.__smac3_termination_cost_threshold is None:
1197 self.__smac3_termination_cost_threshold = self._abstract_getter(
1198 Settings.OPTION_smac3_termination_cost_threshold
1199 )
1200 return self.__smac3_termination_cost_threshold
1202 @property
1203 def smac3_wallclock_time_budget(self: Settings) -> int:
1204 """Return the SMAC3 walltime budget in seconds."""
1205 if self.__smac3_wallclock_time_limit is None:
1206 self.__smac3_wallclock_time_limit = self._abstract_getter(
1207 Settings.OPTION_smac3_wallclock_time_budget
1208 )
1209 return self.__smac3_wallclock_time_limit
1211 @property
1212 def smac3_cpu_time_budget(self: Settings) -> int:
1213 """Return the SMAC3 cputime budget in seconds."""
1214 if self.__smac3_cputime_limit is None:
1215 self.__smac3_cputime_limit = self._abstract_getter(
1216 Settings.OPTION_smac3_cpu_time_budget
1217 )
1218 return self.__smac3_cputime_limit
1220 @property
1221 def smac3_use_default_config(self: Settings) -> bool:
1222 """Return whether SMAC3 should use the default config."""
1223 if self.__smac3_use_default_config is None:
1224 self.__smac3_use_default_config = self._abstract_getter(
1225 Settings.OPTION_smac3_use_default_config
1226 )
1227 return self.__smac3_use_default_config
1229 @property
1230 def smac3_min_budget(self: Settings) -> int:
1231 """Return the SMAC3 min budget."""
1232 if self.__smac3_min_budget is None:
1233 self.__smac3_min_budget = self._abstract_getter(
1234 Settings.OPTION_smac3_min_budget
1235 )
1236 return self.__smac3_min_budget
1238 @property
1239 def smac3_max_budget(self: Settings) -> int:
1240 """Return the SMAC3 max budget."""
1241 if self.__smac3_max_budget is None:
1242 self.__smac3_max_budget = self._abstract_getter(
1243 Settings.OPTION_smac3_max_budget
1244 )
1245 return self.__smac3_max_budget
1247 # IRACE settings ###
1248 @property
1249 def irace_max_time(self: Settings) -> int:
1250 """Return the max time in seconds for IRACE."""
1251 if self.__irace_max_time is None:
1252 self.__irace_max_time = self._abstract_getter(Settings.OPTION_irace_max_time)
1253 return self.__irace_max_time
1255 @property
1256 def irace_max_experiments(self: Settings) -> int:
1257 """Return the max experiments for IRACE."""
1258 if self.__irace_max_experiments is None:
1259 self.__irace_max_experiments = self._abstract_getter(
1260 Settings.OPTION_irace_max_experiments
1261 )
1262 return self.__irace_max_experiments
1264 @property
1265 def irace_first_test(self: Settings) -> int:
1266 """Return the first test for IRACE."""
1267 if self.__irace_first_test is None:
1268 self.__irace_first_test = self._abstract_getter(
1269 Settings.OPTION_irace_first_test
1270 )
1271 return self.__irace_first_test
1273 @property
1274 def irace_mu(self: Settings) -> int:
1275 """Return the mu for IRACE."""
1276 if self.__irace_mu is None:
1277 self.__irace_mu = self._abstract_getter(Settings.OPTION_irace_mu)
1278 return self.__irace_mu
1280 @property
1281 def irace_max_iterations(self: Settings) -> int:
1282 """Return the max iterations for IRACE."""
1283 if self.__irace_max_iterations is None:
1284 self.__irace_max_iterations = self._abstract_getter(
1285 Settings.OPTION_irace_max_iterations
1286 )
1287 return self.__irace_max_iterations
1289 # ParamILS settings ###
1290 @property
1291 def paramils_cpu_time_budget(self: Settings) -> int:
1292 """Return the CPU time budget for ParamILS."""
1293 if self.__paramils_cpu_time_budget is None:
1294 self.__paramils_cpu_time_budget = self._abstract_getter(
1295 Settings.OPTION_paramils_cpu_time_budget
1296 )
1297 return self.__paramils_cpu_time_budget
1299 @property
1300 def paramils_min_runs(self: Settings) -> int:
1301 """Return the min runs for ParamILS."""
1302 if self.__paramils_min_runs is None:
1303 self.__paramils_min_runs = self._abstract_getter(
1304 Settings.OPTION_paramils_min_runs
1305 )
1306 return self.__paramils_min_runs
1308 @property
1309 def paramils_max_runs(self: Settings) -> int:
1310 """Return the max runs for ParamILS."""
1311 if self.__paramils_max_runs is None:
1312 self.__paramils_max_runs = self._abstract_getter(
1313 Settings.OPTION_paramils_max_runs
1314 )
1315 return self.__paramils_max_runs
1317 @property
1318 def paramils_random_restart(self: Settings) -> float:
1319 """Return the random restart for ParamILS."""
1320 if self.__paramils_random_restart is None:
1321 self.__paramils_random_restart = self._abstract_getter(
1322 Settings.OPTION_paramils_random_restart
1323 )
1324 return self.__paramils_random_restart
1326 @property
1327 def paramils_focused_approach(self: Settings) -> bool:
1328 """Return the focused approach for ParamILS."""
1329 if self.__paramils_focused_approach is None:
1330 self.__paramils_focused_approach = self._abstract_getter(
1331 Settings.OPTION_paramils_focused
1332 )
1333 return self.__paramils_focused_approach
1335 @property
1336 def paramils_use_cpu_time_in_tunertime(self: Settings) -> bool:
1337 """Return the use cpu time for ParamILS."""
1338 if self.__paramils_use_cpu_time_in_tunertime is None:
1339 self.__paramils_use_cpu_time_in_tunertime = self._abstract_getter(
1340 Settings.OPTION_paramils_count_tuner_time
1341 )
1342 return self.__paramils_use_cpu_time_in_tunertime
1344 @property
1345 def paramils_cli_cores(self: Settings) -> int:
1346 """The number of CPU cores to use for ParamILS."""
1347 if self.__paramils_cli_cores is None:
1348 self.__paramils_cli_cores = self._abstract_getter(
1349 Settings.OPTION_paramils_cli_cores
1350 )
1351 return self.__paramils_cli_cores
1353 @property
1354 def paramils_max_iterations(self: Settings) -> int:
1355 """Return the max iterations for ParamILS."""
1356 if self.__paramils_max_iterations is None:
1357 self.__paramils_max_iterations = self._abstract_getter(
1358 Settings.OPTION_paramils_max_iterations
1359 )
1360 return self.__paramils_max_iterations
1362 @property
1363 def paramils_number_initial_configurations(self: Settings) -> int:
1364 """Return the number of initial configurations for ParamILS."""
1365 if self.__paramils_number_initial_configurations is None:
1366 self.__paramils_number_initial_configurations = self._abstract_getter(
1367 Settings.OPTION_paramils_number_initial_configurations
1368 )
1369 return self.__paramils_number_initial_configurations
1371 # Parallel Portfolio settings ###
1372 @property
1373 def parallel_portfolio_check_interval(self: Settings) -> int:
1374 """Return the check interval for the parallel portfolio."""
1375 if self.__parallel_portfolio_check_interval is None:
1376 self.__parallel_portfolio_check_interval = self._abstract_getter(
1377 Settings.OPTION_parallel_portfolio_check_interval
1378 )
1379 return self.__parallel_portfolio_check_interval
1381 @property
1382 def parallel_portfolio_num_seeds_per_solver(self: Settings) -> int:
1383 """Return the number of seeds per solver for the parallel portfolio."""
1384 if self.__parallel_portfolio_num_seeds_per_solver is None:
1385 self.__parallel_portfolio_num_seeds_per_solver = self._abstract_getter(
1386 Settings.OPTION_parallel_portfolio_number_of_seeds_per_solver
1387 )
1388 return self.__parallel_portfolio_num_seeds_per_solver
1390 # Slurm settings ###
1391 @property
1392 def slurm_jobs_in_parallel(self: Settings) -> int:
1393 """Return the (maximum) number of jobs to run in parallel."""
1394 if self.__slurm_jobs_in_parallel is None:
1395 self.__slurm_jobs_in_parallel = self._abstract_getter(
1396 Settings.OPTION_slurm_parallel_jobs
1397 )
1398 return self.__slurm_jobs_in_parallel
1400 @property
1401 def slurm_job_prepend(self: Settings) -> str:
1402 """Return the slurm job prepend."""
1403 if self.__slurm_job_prepend is None and self.__settings.has_option(
1404 Settings.OPTION_slurm_prepend_script.section,
1405 Settings.OPTION_slurm_prepend_script.name,
1406 ):
1407 value = self.__settings[Settings.OPTION_slurm_prepend_script.section][
1408 Settings.OPTION_slurm_prepend_script.name
1409 ]
1410 try:
1411 path = Path(value)
1412 if path.is_file():
1413 with path.open() as f:
1414 value = f.read()
1415 f.close()
1416 self.__slurm_job_prepend = str(value)
1417 except TypeError:
1418 pass
1419 return self.__slurm_job_prepend
1421 @property
1422 def sbatch_settings(self: Settings) -> list[str]:
1423 """Return the sbatch settings."""
1424 sbatch_options = self.__settings[Settings.SECTION_slurm]
1425 # Return all non-predefined keys
1426 return [
1427 f"--{key}={sbatch_options[key]}"
1428 for key in sbatch_options.keys()
1429 if key not in Settings.sections_options[Settings.SECTION_slurm]
1430 ]
1432 # General functionalities ###
1434 def get_configurator_output_path(self: Settings, configurator: Configurator) -> Path:
1435 """Return the configurator output path."""
1436 return self.DEFAULT_configuration_output / configurator.name
1438 def get_configurator_settings(
1439 self: Settings, configurator_name: str
1440 ) -> dict[str, any]:
1441 """Return the settings of a specific configurator."""
1442 configurator_settings = {
1443 "solver_calls": self.configurator_solver_call_budget,
1444 "solver_cutoff_time": self.solver_cutoff_time,
1445 "max_iterations": self.configurator_max_iterations,
1446 }
1447 # In the settings below, we default to the configurator general settings if no
1448 # specific configurator settings are given, by using the [None] or [Value]
1449 if (
1450 configurator_name == "SMAC2"
1451 ): # NOTE: This is hardcoded, but doing it through imports slows done the ENTIRETY of the Sparkle substantially
1452 # Return all settings from the SMAC2 section
1453 configurator_settings.update(
1454 {
1455 "cpu_time": self.smac2_cpu_time_budget,
1456 "wallclock_time": self.smac2_wallclock_time_budget,
1457 "target_cutoff_length": self.smac2_target_cutoff_length,
1458 "use_cpu_time_in_tunertime": self.smac2_use_tunertime_in_cpu_time_budget,
1459 "cli_cores": self.smac2_cli_cores,
1460 "max_iterations": self.smac2_max_iterations
1461 or configurator_settings["max_iterations"],
1462 }
1463 )
1464 elif (
1465 configurator_name == "SMAC3"
1466 ): # NOTE: This is hardcoded, but doing it through imports slows done the ENTIRETY of the Sparkle substantially
1467 # Return all settings from the SMAC3 section
1468 del configurator_settings["max_iterations"] # SMAC3 does not have this?
1469 configurator_settings.update(
1470 {
1471 "smac_facade": self.smac3_facade,
1472 "max_ratio": self.smac3_facade_max_ratio,
1473 "crash_cost": self.smac3_crash_cost,
1474 "termination_cost_threshold": self.smac3_termination_cost_threshold,
1475 "walltime_limit": self.smac3_wallclock_time_budget,
1476 "cputime_limit": self.smac3_cpu_time_budget,
1477 "use_default_config": self.smac3_use_default_config,
1478 "min_budget": self.smac3_min_budget,
1479 "max_budget": self.smac3_max_budget,
1480 "solver_calls": self.smac3_number_of_trials
1481 or configurator_settings["solver_calls"],
1482 }
1483 )
1484 # Do not pass None values to SMAC3, its Scenario resolves default settings
1485 configurator_settings = {
1486 key: value
1487 for key, value in configurator_settings.items()
1488 if value is not None
1489 }
1490 elif (
1491 configurator_name == "IRACE"
1492 ): # NOTE: This is hardcoded, but doing it through imports slows done the ENTIRETY of the Sparkle substantially
1493 # Return all settings from the IRACE section
1494 configurator_settings.update(
1495 {
1496 "solver_calls": self.irace_max_experiments,
1497 "max_time": self.irace_max_time,
1498 "first_test": self.irace_first_test,
1499 "mu": self.irace_mu,
1500 "max_iterations": self.irace_max_iterations
1501 or configurator_settings["max_iterations"],
1502 }
1503 )
1504 if (
1505 configurator_settings["solver_calls"] == 0
1506 and configurator_settings["max_time"] == 0
1507 ): # Default to base
1508 configurator_settings["solver_calls"] = (
1509 self.configurator_solver_call_budget
1510 )
1511 elif (
1512 configurator_name == "ParamILS"
1513 ): # NOTE: This is hardcoded, but doing it through imports slows done the ENTIRETY of the Sparkle substantially
1514 configurator_settings.update(
1515 {
1516 "tuner_timeout": self.paramils_cpu_time_budget,
1517 "min_runs": self.paramils_min_runs,
1518 "max_runs": self.paramils_max_runs,
1519 "focused_ils": self.paramils_focused_approach,
1520 "initial_configurations": self.paramils_number_initial_configurations,
1521 "random_restart": self.paramils_random_restart,
1522 "cli_cores": self.paramils_cli_cores,
1523 "use_cpu_time_in_tunertime": self.paramils_use_cpu_time_in_tunertime,
1524 "max_iterations": self.paramils_max_iterations
1525 or configurator_settings["max_iterations"],
1526 }
1527 )
1528 return configurator_settings
1530 @staticmethod
1531 def check_settings_changes(
1532 cur_settings: Settings, prev_settings: Settings, verbose: bool = True
1533 ) -> bool:
1534 """Check if there are changes between the previous and the current settings.
1536 Prints any section changes, printing None if no setting was found.
1538 Args:
1539 cur_settings: The current settings
1540 prev_settings: The previous settings
1541 verbose: Verbosity of the function
1543 Returns:
1544 True iff there are changes.
1545 """
1546 cur_dict = cur_settings.__settings._sections
1547 prev_dict = prev_settings.__settings._sections
1549 cur_sections_set = set(cur_dict.keys())
1550 prev_sections_set = set(prev_dict.keys())
1552 sections_remained = cur_sections_set & prev_sections_set
1553 option_changed = False
1554 for section in sections_remained:
1555 printed_section = False
1556 names = set(cur_dict[section].keys()) | set(prev_dict[section].keys())
1557 if (
1558 section == "general" and "seed" in names
1559 ): # Do not report on the seed, is supposed to change
1560 names.remove("seed")
1561 for name in names:
1562 # if name is not present in one of the two dicts, get None as placeholder
1563 cur_val = cur_dict[section].get(name, None)
1564 prev_val = prev_dict[section].get(name, None)
1566 # If cur val is None, it is default
1567 if cur_val is not None and cur_val != prev_val:
1568 if not option_changed and verbose: # Print the initial
1569 print("[INFO] The following attributes/options have changed:")
1570 option_changed = True
1572 # do we have yet to print the section?
1573 if not printed_section and verbose:
1574 print(f" - In the section '{section}':")
1575 printed_section = True
1577 # print actual change
1578 if verbose:
1579 print(f" · '{name}' changed from '{prev_val}' to '{cur_val}'")
1581 return option_changed