Coverage for sparkle/platform/settings_objects.py: 81%

929 statements  

« prev     ^ index     » next       coverage.py v7.6.10, created at 2025-01-07 15:22 +0000

1"""Classes and Enums to control settings.""" 

2from __future__ import annotations 

3import configparser 

4from enum import Enum 

5import ast 

6from pathlib import Path 

7from pathlib import PurePath 

8 

9from sparkle.types import SparkleObjective, resolve_objective 

10from sparkle.types.objective import PAR 

11from sparkle.solver import Selector 

12from sparkle.configurator.configurator import Configurator 

13from sparkle.configurator import implementations as cim 

14 

15from runrunner import Runner 

16from sparkle.platform.cli_types import VerbosityLevel 

17 

18 

19class SettingState(Enum): 

20 """Enum of possible setting states.""" 

21 

22 NOT_SET = 0 

23 DEFAULT = 1 

24 FILE = 2 

25 CMD_LINE = 3 

26 

27 

28class Settings: 

29 """Class to read, write, set, and get settings.""" 

30 # CWD Prefix 

31 cwd_prefix = Path() # Empty for now 

32 

33 # Library prefix 

34 lib_prefix = Path(__file__).parent.parent.resolve() 

35 

36 # Default directory names 

37 rawdata_dir = Path("Raw_Data") 

38 analysis_dir = Path("Analysis") 

39 DEFAULT_settings_dir = Path("Settings") 

40 __settings_file = Path("sparkle_settings.ini") 

41 

42 # Default settings path 

43 DEFAULT_settings_path = PurePath(cwd_prefix / DEFAULT_settings_dir / __settings_file) 

44 DEFAULT_reference_dir = DEFAULT_settings_dir / "Reference_Lists" 

45 

46 # Default library pathing 

47 DEFAULT_components = lib_prefix / "Components" 

48 

49 # Example settings path 

50 DEFAULT_example_settings_path = PurePath(DEFAULT_components / "sparkle_settings.ini") 

51 

52 # Runsolver component 

53 DEFAULT_runsolver_dir = DEFAULT_components / "runsolver" / "src" 

54 DEFAULT_runsolver_exec = DEFAULT_runsolver_dir / "runsolver" 

55 

56 # Ablation component 

57 DEFAULT_ablation_dir = DEFAULT_components / "ablationAnalysis-0.9.4" 

58 DEFAULT_ablation_exec = DEFAULT_ablation_dir / "ablationAnalysis" 

59 DEFAULT_ablation_validation_exec = DEFAULT_ablation_dir / "ablationValidation" 

60 

61 # Autofolio component 

62 DEFAULT_general_sparkle_selector = DEFAULT_components / "AutoFolio/scripts/autofolio" 

63 

64 # Report component 

65 DEFAULT_latex_source = DEFAULT_components / "Sparkle-latex-source" 

66 DEFAULT_latex_bib = DEFAULT_latex_source / "SparkleReport.bib" 

67 

68 # Default input directory pathing 

69 DEFAULT_solver_dir = cwd_prefix / "Solvers" 

70 DEFAULT_instance_dir = cwd_prefix / "Instances" 

71 DEFAULT_extractor_dir = cwd_prefix / "Extractors" 

72 DEFAULT_snapshot_dir = cwd_prefix / "Snapshots" 

73 

74 # Default output directory pathing 

75 DEFAULT_tmp_output = cwd_prefix / "Tmp" 

76 DEFAULT_output = cwd_prefix / "Output" 

77 DEFAULT_configuration_output = DEFAULT_output / "Configuration" 

78 DEFAULT_selection_output = DEFAULT_output / "Selection" 

79 DEFAULT_parallel_portfolio_output = DEFAULT_output / "Parallel_Portfolio" 

80 DEFAULT_ablation_output = DEFAULT_output / "Ablation" 

81 DEFAULT_log_output = DEFAULT_output / "Log" 

82 

83 # Default output subdirs 

84 DEFAULT_configuration_output_raw = DEFAULT_configuration_output / rawdata_dir 

85 DEFAULT_configuration_output_analysis = DEFAULT_configuration_output / analysis_dir 

86 DEFAULT_selection_output_raw = DEFAULT_selection_output / rawdata_dir 

87 DEFAULT_selection_output_analysis = DEFAULT_selection_output / analysis_dir 

88 DEFAULT_parallel_portfolio_output_raw =\ 

89 DEFAULT_parallel_portfolio_output / rawdata_dir 

90 DEFAULT_parallel_portfolio_output_analysis =\ 

91 DEFAULT_parallel_portfolio_output / analysis_dir 

92 

93 # Old default output dirs which should be part of something else 

94 DEFAULT_feature_data = DEFAULT_output / "Feature_Data" 

95 DEFAULT_performance_data = DEFAULT_output / "Performance_Data" 

96 

97 # Collection of all working dirs for platform 

98 DEFAULT_working_dirs = [ 

99 DEFAULT_solver_dir, DEFAULT_instance_dir, DEFAULT_extractor_dir, 

100 DEFAULT_output, DEFAULT_configuration_output, 

101 DEFAULT_selection_output, 

102 DEFAULT_tmp_output, DEFAULT_log_output, 

103 DEFAULT_feature_data, DEFAULT_performance_data, 

104 DEFAULT_settings_dir, DEFAULT_reference_dir, 

105 ] 

106 

107 # Old default file paths from GV which should be turned into variables 

108 DEFAULT_feature_data_path =\ 

109 DEFAULT_feature_data / "feature_data.csv" 

110 DEFAULT_performance_data_path =\ 

111 DEFAULT_performance_data / "performance_data.csv" 

112 

113 # Constant default values 

114 DEFAULT_general_sparkle_objective = PAR(10) 

115 DEFAULT_general_sparkle_configurator = cim.SMAC2.__name__ 

116 DEFAULT_general_target_cutoff_time = 60 

117 DEFAULT_general_extractor_cutoff_time = 60 

118 DEFAULT_number_of_jobs_in_parallel = 25 

119 DEFAULT_general_verbosity = VerbosityLevel.STANDARD 

120 DEFAULT_general_check_interval = 10 

121 DEFAULT_general_run_on = "local" 

122 

123 DEFAULT_configurator_number_of_runs = 25 

124 DEFAULT_configurator_solver_calls = 100 

125 DEFAULT_configurator_maximum_iterations = None 

126 

127 # Default SMAC2 settings 

128 DEFAULT_smac2_wallclock_time = None 

129 DEFAULT_smac2_cpu_time = None 

130 DEFAULT_smac2_target_cutoff_length = "max" 

131 DEFAULT_smac2_use_cpu_time_in_tunertime = None 

132 DEFAULT_smac2_cli_cores = None 

133 DEFAULT_smac2_max_iterations = None 

134 

135 # Default SMAC3 settings 

136 DEFAULT_smac3_number_of_runs = None 

137 DEFAULT_smac3_facade = "AlgorithmConfigurationFacade" 

138 DEFAULT_smac3_facade_max_ratio = None 

139 DEFAULT_smac3_crash_cost = None 

140 DEFAULT_smac3_termination_cost_threshold = None 

141 DEFAULT_smac3_walltime_limit = None 

142 DEFAULT_smac3_cputime_limit = None 

143 DEFAULT_smac3_use_default_config = None 

144 DEFAULT_smac3_min_budget = None 

145 DEFAULT_smac3_max_budget = None 

146 

147 # Default IRACE settings 

148 DEFAULT_irace_max_time = 0 # IRACE equivalent of None in this case 

149 DEFAULT_irace_max_experiments = 0 

150 DEFAULT_irace_first_test = None 

151 DEFAULT_irace_mu = None 

152 DEFAULT_irace_max_iterations = None 

153 

154 DEFAULT_portfolio_construction_timeout = None 

155 

156 DEFAULT_slurm_max_parallel_runs_per_node = 8 

157 

158 DEFAULT_ablation_racing = False 

159 

160 DEFAULT_parallel_portfolio_check_interval = 4 

161 DEFAULT_parallel_portfolio_num_seeds_per_solver = 1 

162 

163 # Default IRACE settings 

164 DEFAULT_irace_max_time = 0 # IRACE equivalent of None in this case 

165 DEFAULT_irace_max_experiments = 0 

166 DEFAULT_irace_first_test = None 

167 DEFAULT_irace_mu = None 

168 DEFAULT_irace_max_iterations = None 

169 

170 def __init__(self: Settings, file_path: PurePath = None) -> None: 

171 """Initialise a settings object.""" 

172 # Settings 'dictionary' in configparser format 

173 self.__settings = configparser.ConfigParser() 

174 

175 # Setting flags 

176 self.__general_sparkle_objective_set = SettingState.NOT_SET 

177 self.__general_sparkle_configurator_set = SettingState.NOT_SET 

178 self.__general_sparkle_selector_set = SettingState.NOT_SET 

179 self.__general_target_cutoff_time_set = SettingState.NOT_SET 

180 self.__general_extractor_cutoff_time_set = SettingState.NOT_SET 

181 self.__general_verbosity_set = SettingState.NOT_SET 

182 self.__general_check_interval_set = SettingState.NOT_SET 

183 

184 self.__config_solver_calls_set = SettingState.NOT_SET 

185 self.__config_number_of_runs_set = SettingState.NOT_SET 

186 self.__config_max_iterations_set = SettingState.NOT_SET 

187 

188 self.__smac2_wallclock_time_set = SettingState.NOT_SET 

189 self.__smac2_cpu_time_set = SettingState.NOT_SET 

190 self.__smac2_use_cpu_time_in_tunertime_set = SettingState.NOT_SET 

191 self.__smac2_cli_cores_set = SettingState.NOT_SET 

192 self.__smac2_max_iterations_set = SettingState.NOT_SET 

193 self.__smac2_target_cutoff_length_set = SettingState.NOT_SET 

194 

195 self.__smac3_number_of_trials_set = SettingState.NOT_SET 

196 self.__smac3_smac_facade_set = SettingState.NOT_SET 

197 self.__smac3_facade_max_ratio_set = SettingState.NOT_SET 

198 self.__smac3_crash_cost_set = SettingState.NOT_SET 

199 self.__smac3_termination_cost_threshold_set = SettingState.NOT_SET 

200 self.__smac3_walltime_limit_set = SettingState.NOT_SET 

201 self.__smac3_cputime_limit_set = SettingState.NOT_SET 

202 self.__smac3_use_default_config_set = SettingState.NOT_SET 

203 self.__smac3_min_budget_set = SettingState.NOT_SET 

204 self.__smac3_max_budget_set = SettingState.NOT_SET 

205 

206 self.__run_on_set = SettingState.NOT_SET 

207 self.__number_of_jobs_in_parallel_set = SettingState.NOT_SET 

208 self.__slurm_max_parallel_runs_per_node_set = SettingState.NOT_SET 

209 self.__ablation_racing_flag_set = SettingState.NOT_SET 

210 

211 self.__parallel_portfolio_check_interval_set = SettingState.NOT_SET 

212 self.__parallel_portfolio_num_seeds_per_solver_set = SettingState.NOT_SET 

213 

214 self.__irace_max_time_set = SettingState.NOT_SET 

215 self.__irace_max_experiments_set = SettingState.NOT_SET 

216 self.__irace_first_test_set = SettingState.NOT_SET 

217 self.__irace_mu_set = SettingState.NOT_SET 

218 self.__irace_max_iterations_set = SettingState.NOT_SET 

219 

220 self.__general_sparkle_configurator = None 

221 

222 self.__slurm_extra_options_set = dict() 

223 

224 if file_path is None: 

225 # Initialise settings from default file path 

226 self.read_settings_ini() 

227 else: 

228 # Initialise settings from a given file path 

229 self.read_settings_ini(file_path) 

230 

231 def read_settings_ini(self: Settings, file_path: PurePath = DEFAULT_settings_path, 

232 state: SettingState = SettingState.FILE) -> None: 

233 """Read the settings from an INI file.""" 

234 # Read file 

235 file_settings = configparser.ConfigParser() 

236 file_settings.read(file_path) 

237 

238 # Set internal settings based on data read from FILE if they were read 

239 # successfully 

240 if file_settings.sections() != []: 

241 section = "general" 

242 option_names = ("objectives", ) 

243 for option in option_names: 

244 if file_settings.has_option(section, option): 

245 value = [resolve_objective(obj) for obj in 

246 file_settings.get(section, option).split(",")] 

247 self.set_general_sparkle_objectives(value, state) 

248 file_settings.remove_option(section, option) 

249 

250 # Comma so python understands it's a tuple... 

251 option_names = ("configurator", ) 

252 for option in option_names: 

253 if file_settings.has_option(section, option): 

254 value = file_settings.get(section, option) 

255 self.set_general_sparkle_configurator(value, state) 

256 file_settings.remove_option(section, option) 

257 

258 option_names = ("selector", ) 

259 for option in option_names: 

260 if file_settings.has_option(section, option): 

261 value = file_settings.get(section, option) 

262 self.set_general_sparkle_selector(value, state) 

263 file_settings.remove_option(section, option) 

264 

265 option_names = ("target_cutoff_time", 

266 "cutoff_time_each_solver_call") 

267 for option in option_names: 

268 if file_settings.has_option(section, option): 

269 value = file_settings.getint(section, option) 

270 self.set_general_target_cutoff_time(value, state) 

271 file_settings.remove_option(section, option) 

272 

273 option_names = ("extractor_cutoff_time", 

274 "cutoff_time_each_feature_computation") 

275 for option in option_names: 

276 if file_settings.has_option(section, option): 

277 value = file_settings.getint(section, option) 

278 self.set_general_extractor_cutoff_time(value, state) 

279 file_settings.remove_option(section, option) 

280 

281 option_names = ("run_on", ) 

282 for option in option_names: 

283 if file_settings.has_option(section, option): 

284 value = file_settings.get(section, option) 

285 self.set_run_on(value, state) 

286 file_settings.remove_option(section, option) 

287 

288 option_names = ("verbosity", ) 

289 for option in option_names: 

290 if file_settings.has_option(section, option): 

291 value = VerbosityLevel.from_string( 

292 file_settings.get(section, option)) 

293 self.set_general_verbosity(value, state) 

294 file_settings.remove_option(section, option) 

295 

296 option_names = ("check_interval", ) 

297 for option in option_names: 

298 if file_settings.has_option(section, option): 

299 value = int(file_settings.get(section, option)) 

300 self.set_general_check_interval(value, state) 

301 file_settings.remove_option(section, option) 

302 

303 section = "configuration" 

304 option_names = ("solver_calls", ) 

305 for option in option_names: 

306 if file_settings.has_option(section, option): 

307 value = file_settings.getint(section, option) 

308 self.set_configurator_solver_calls(value, state) 

309 file_settings.remove_option(section, option) 

310 

311 option_names = ("number_of_runs", ) 

312 for option in option_names: 

313 if file_settings.has_option(section, option): 

314 value = file_settings.getint(section, option) 

315 self.set_configurator_number_of_runs(value, state) 

316 file_settings.remove_option(section, option) 

317 

318 option_name = "max_iterations" 

319 if file_settings.has_option(section, option_name): 

320 value = file_settings.getint(section, option_name) 

321 self.set_configurator_max_iterations(value, state) 

322 file_settings.remove_option(section, option_name) 

323 

324 section = "smac2" 

325 option_names = ("wallclock_time", ) 

326 for option in option_names: 

327 if file_settings.has_option(section, option): 

328 value = file_settings.getint(section, option) 

329 self.set_smac2_wallclock_time(value, state) 

330 file_settings.remove_option(section, option) 

331 

332 option_names = ("cpu_time", ) 

333 for option in option_names: 

334 if file_settings.has_option(section, option): 

335 value = file_settings.getint(section, option) 

336 self.set_smac2_cpu_time(value, state) 

337 file_settings.remove_option(section, option) 

338 

339 option_names = ("target_cutoff_length", "each_run_cutoff_length") 

340 for option in option_names: 

341 if file_settings.has_option(section, option): 

342 value = file_settings.get(section, option) 

343 self.set_smac2_target_cutoff_length(value, state) 

344 file_settings.remove_option(section, option) 

345 

346 option_names = ("use_cpu_time_in_tunertime", "countSMACTimeAsTunerTime") 

347 for option in option_names: 

348 if file_settings.has_option(section, option): 

349 value = file_settings.getboolean(section, option) 

350 self.set_smac2_use_cpu_time_in_tunertime(value, state) 

351 file_settings.remove_option(section, option) 

352 

353 option_names = ("cli_cores", ) 

354 for option in option_names: 

355 if file_settings.has_option(section, option): 

356 value = file_settings.getint(section, option) 

357 self.set_smac2_cli_cores(value, state) 

358 file_settings.remove_option(section, option) 

359 

360 options_names = ("iteration_limit", "numIterations", "numberOfIterations", 

361 "max_iterations") 

362 for option in options_names: 

363 if file_settings.has_option(section, option): 

364 value = file_settings.getint(section, option) 

365 self.set_smac2_max_iterations(value, state) 

366 file_settings.remove_option(section, option) 

367 

368 section = "smac3" 

369 

370 option_names = ("n_trials", "number_of_trials", "solver_calls") 

371 for option in option_names: 

372 if file_settings.has_option(section, option): 

373 value = file_settings.getint(section, option) 

374 self.set_smac3_number_of_trials(value, state) 

375 file_settings.remove_option(section, option) 

376 

377 options_names = ("facade", "smac_facade", "smac3_facade") 

378 for option in options_names: 

379 if file_settings.has_option(section, option): 

380 value = file_settings.get(section, option) 

381 self.set_smac3_smac_facade(value, state) 

382 file_settings.remove_option(section, option) 

383 

384 option_names = ("max_ratio", "facade_max_ratio", "initial_trials_max_ratio") 

385 for option in option_names: 

386 if file_settings.has_option(section, option): 

387 value = file_settings.getfloat(section, option) 

388 self.set_smac3_facade_max_ratio(value, state) 

389 file_settings.remove_option(section, option) 

390 

391 options_names = ("crash_cost", ) 

392 for option in options_names: 

393 if file_settings.has_option(section, option): 

394 value = file_settings.get(section, option) 

395 self.set_smac3_crash_cost(value, state) 

396 file_settings.remove_option(section, option) 

397 

398 options_names = ("termination_cost_threshold", ) 

399 for option in options_names: 

400 if file_settings.has_option(section, option): 

401 value = file_settings.get(section, option) 

402 self.set_smac3_termination_cost_threshold(value, state) 

403 file_settings.remove_option(section, option) 

404 

405 options_names = ("walltime_limit", "wallclock_time") 

406 for option in options_names: 

407 if file_settings.has_option(section, option): 

408 value = file_settings.getfloat(section, option) 

409 self.set_smac3_walltime_limit(value, state) 

410 file_settings.remove_option(section, option) 

411 

412 options_names = ("cputime_limit", ) 

413 for option in options_names: 

414 if file_settings.has_option(section, option): 

415 value = file_settings.getfloat(section, option) 

416 self.set_smac3_cputime_limit(value, state) 

417 file_settings.remove_option(section, option) 

418 

419 options_names = ("use_default_config", ) 

420 for option in options_names: 

421 if file_settings.has_option(section, option): 

422 value = file_settings.getboolean(section, option) 

423 self.set_smac3_use_default_config(value, state) 

424 file_settings.remove_option(section, option) 

425 

426 options_names = ("min_budget", ) 

427 for option in options_names: 

428 if file_settings.has_option(section, option): 

429 value = file_settings.getfloat(section, option) 

430 self.set_smac3_min_budget(value, state) 

431 file_settings.remove_option(section, option) 

432 

433 options_names = ("max_budget", ) 

434 for option in options_names: 

435 if file_settings.has_option(section, option): 

436 value = file_settings.getfloat(section, option) 

437 self.set_smac3_max_budget(value, state) 

438 file_settings.remove_option(section, option) 

439 

440 section = "irace" 

441 option_names = ("max_time", ) 

442 for option in option_names: 

443 if file_settings.has_option(section, option): 

444 value = file_settings.getint(section, option) 

445 self.set_irace_max_time(value, state) 

446 file_settings.remove_option(section, option) 

447 

448 option_names = ("max_experiments", ) 

449 for option in option_names: 

450 if file_settings.has_option(section, option): 

451 value = file_settings.getint(section, option) 

452 self.set_irace_max_experiments(value, state) 

453 file_settings.remove_option(section, option) 

454 

455 option_names = ("first_test", ) 

456 for option in option_names: 

457 if file_settings.has_option(section, option): 

458 value = file_settings.getint(section, option) 

459 self.set_irace_first_test(value, state) 

460 file_settings.remove_option(section, option) 

461 

462 option_names = ("mu", ) 

463 for option in option_names: 

464 if file_settings.has_option(section, option): 

465 value = file_settings.getint(section, option) 

466 self.set_irace_mu(value, state) 

467 file_settings.remove_option(section, option) 

468 

469 option_names = ("nb_iterations", "iterations", "max_iterations") 

470 for option in option_names: 

471 if file_settings.has_option(section, option): 

472 value = file_settings.getint(section, option) 

473 self.set_irace_max_iterations(value, state) 

474 file_settings.remove_option(section, option) 

475 

476 section = "slurm" 

477 option_names = ("number_of_jobs_in_parallel", "num_job_in_parallel") 

478 for option in option_names: 

479 if file_settings.has_option(section, option): 

480 value = file_settings.getint(section, option) 

481 self.set_number_of_jobs_in_parallel(value, state) 

482 file_settings.remove_option(section, option) 

483 

484 option_names = ("max_parallel_runs_per_node", "clis_per_node") 

485 for option in option_names: 

486 if file_settings.has_option(section, option): 

487 value = file_settings.getint(section, option) 

488 self.set_slurm_max_parallel_runs_per_node(value, state) 

489 file_settings.remove_option(section, option) 

490 

491 section = "ablation" 

492 option_names = ("racing", "ablation_racing") 

493 for option in option_names: 

494 if file_settings.has_option(section, option): 

495 value = file_settings.getboolean(section, option) 

496 self.set_ablation_racing_flag(value, state) 

497 file_settings.remove_option(section, option) 

498 

499 section = "parallel_portfolio" 

500 option_names = ("check_interval", ) 

501 for option in option_names: 

502 if file_settings.has_option(section, option): 

503 value = int(file_settings.get(section, option)) 

504 self.set_parallel_portfolio_check_interval(value, state) 

505 file_settings.remove_option(section, option) 

506 

507 option_names = ("num_seeds_per_solver", ) 

508 for option in option_names: 

509 if file_settings.has_option(section, option): 

510 value = int(file_settings.get(section, option)) 

511 self.set_parallel_portfolio_number_of_seeds_per_solver(value, state) 

512 file_settings.remove_option(section, option) 

513 

514 # TODO: Report on any unknown settings that were read 

515 sections = file_settings.sections() 

516 

517 for section in sections: 

518 for option in file_settings[section]: 

519 # TODO: Should check the options are valid Slurm options 

520 if section == "slurm": 

521 value = file_settings.get(section, option) 

522 self.add_slurm_extra_option(option, value, state) 

523 else: 

524 print(f'Unrecognised section - option combination: "{section} ' 

525 f'{option}" in file {file_path} ignored') 

526 

527 # Print error if unable to read the settings 

528 elif Path(file_path).exists(): 

529 print(f"ERROR: Failed to read settings from {file_path} The file may have " 

530 "been empty or be in another format than INI. Default Setting values " 

531 "will be used.") 

532 

533 def write_used_settings(self: Settings) -> None: 

534 """Write the used settings to the default locations.""" 

535 # Write to latest settings file 

536 self.write_settings_ini(self.DEFAULT_settings_dir / "latest.ini") 

537 

538 def write_settings_ini(self: Settings, file_path: Path) -> None: 

539 """Write the settings to an INI file.""" 

540 # Create needed directories if they don't exist 

541 file_path.parent.mkdir(parents=True, exist_ok=True) 

542 slurm_extra_section_options = None 

543 if self.__settings.has_section("slurm_extra"): 

544 # Slurm extra options are not written as a seperate section 

545 slurm_extra_section_options = {} 

546 for key in self.__settings["slurm_extra"]: 

547 self.__settings["slurm"][key] = self.__settings["slurm_extra"][key] 

548 slurm_extra_section_options[key] = self.__settings["slurm_extra"][key] 

549 self.__settings.remove_section("slurm_extra") 

550 # We do not write None values 

551 removed = [] 

552 for section in self.__settings.sections(): 

553 for option in self.__settings[section]: 

554 try: 

555 if ast.literal_eval(str(self.__settings[section][option])) is None: 

556 del self.__settings[section][option] 

557 removed.append((section, option)) 

558 except Exception: 

559 pass 

560 # Write the settings to file 

561 with file_path.open("w") as settings_file: 

562 self.__settings.write(settings_file) 

563 # Rebuild slurm extra if needed 

564 if slurm_extra_section_options is not None: 

565 self.__settings.add_section("slurm_extra") 

566 for key in slurm_extra_section_options: 

567 self.__settings["slurm_extra"][key] = slurm_extra_section_options[key] 

568 # Rebuild None if needed 

569 for section, option in removed: 

570 self.__settings[section][option] = "None" 

571 

572 def __init_section(self: Settings, section: str) -> None: 

573 if section not in self.__settings: 

574 self.__settings[section] = {} 

575 

576 @staticmethod 

577 def __check_setting_state(current_state: SettingState, 

578 new_state: SettingState, name: str) -> bool: 

579 change_setting_ok = True 

580 

581 if current_state == SettingState.FILE and new_state == SettingState.DEFAULT: 

582 change_setting_ok = False 

583 print(f"Warning: Attempting to overwrite setting for {name} with default " 

584 "value; keeping the value read from file!") 

585 elif (current_state == SettingState.CMD_LINE 

586 and new_state == SettingState.DEFAULT): 

587 change_setting_ok = False 

588 print(f"Warning: Attempting to overwrite setting for {name} with default " 

589 "value; keeping the value read from command line!") 

590 elif current_state == SettingState.CMD_LINE and new_state == SettingState.FILE: 

591 change_setting_ok = False 

592 print(f"Warning: Attempting to overwrite setting for {name} with value from " 

593 "file; keeping the value read from command line!") 

594 

595 return change_setting_ok 

596 

597 # General settings ### 

598 def set_general_sparkle_objectives( 

599 self: Settings, 

600 value: list[SparkleObjective] = [DEFAULT_general_sparkle_objective, ], 

601 origin: SettingState = SettingState.DEFAULT) -> None: 

602 """Set the sparkle objective.""" 

603 section = "general" 

604 name = "objectives" 

605 

606 if value is not None and self.__check_setting_state( 

607 self.__general_sparkle_objective_set, origin, name): 

608 if isinstance(value, list): 

609 value = ",".join([str(obj) for obj in value]) 

610 else: 

611 value = str(value) 

612 

613 # Append standard Sparkle Objectives 

614 if "status" not in value: 

615 value += ",status:metric" 

616 if "cpu_time" not in value: 

617 value += ",cpu_time:metric" 

618 if "wall_time" not in value: 

619 value += ",wall_time:metric" 

620 if "memory" not in value: 

621 value += ",memory:metric" 

622 

623 self.__init_section(section) 

624 self.__general_sparkle_objective_set = origin 

625 self.__settings[section][name] = value 

626 

627 def get_general_sparkle_objectives( 

628 self: Settings, 

629 filter_metric: bool = False) -> list[SparkleObjective]: 

630 """Return the Sparkle objectives.""" 

631 if self.__general_sparkle_objective_set == SettingState.NOT_SET: 

632 self.set_general_sparkle_objectives() 

633 

634 objectives = [resolve_objective(obj) 

635 for obj in self.__settings["general"]["objectives"].split(",")] 

636 

637 if filter_metric: 

638 return [obj for obj in objectives if not obj.metric] 

639 

640 return objectives 

641 

642 def set_general_sparkle_configurator( 

643 self: Settings, 

644 value: str = DEFAULT_general_sparkle_configurator, 

645 origin: SettingState = SettingState.DEFAULT) -> None: 

646 """Set the Sparkle configurator.""" 

647 section = "general" 

648 name = "configurator" 

649 if value is not None and self.__check_setting_state( 

650 self.__general_sparkle_configurator_set, origin, name): 

651 self.__init_section(section) 

652 self.__general_sparkle_configurator_set = origin 

653 self.__settings[section][name] = value 

654 

655 def get_general_sparkle_configurator(self: Settings) -> Configurator: 

656 """Return the configurator init method.""" 

657 if self.__general_sparkle_configurator_set == SettingState.NOT_SET: 

658 self.set_general_sparkle_configurator() 

659 configurator_var = self.__settings["general"]["configurator"] 

660 if (self.__general_sparkle_configurator is None 

661 or self.__general_sparkle_configurator.name != configurator_var): 

662 configurator_subclass =\ 

663 cim.resolve_configurator(self.__settings["general"]["configurator"]) 

664 if configurator_subclass is not None: 

665 self.__general_sparkle_configurator = configurator_subclass( 

666 base_dir=Path(), 

667 output_path=Settings.DEFAULT_configuration_output_raw) 

668 else: 

669 print("WARNING: Configurator class name not recognised: " 

670 f'{self.__settings["general"]["configurator"]}. ' 

671 "Configurator not set.") 

672 return self.__general_sparkle_configurator 

673 

674 def set_general_sparkle_selector( 

675 self: Settings, 

676 value: Path = DEFAULT_general_sparkle_selector, 

677 origin: SettingState = SettingState.DEFAULT) -> None: 

678 """Set the Sparkle selector.""" 

679 section = "general" 

680 name = "selector" 

681 if value is not None and self.__check_setting_state( 

682 self.__general_sparkle_selector_set, origin, name): 

683 self.__init_section(section) 

684 self.__general_sparkle_selector_set = origin 

685 self.__settings[section][name] = str(value) 

686 

687 def get_general_sparkle_selector(self: Settings) -> Selector: 

688 """Return the selector init method.""" 

689 if self.__general_sparkle_selector_set == SettingState.NOT_SET: 

690 self.set_general_sparkle_selector() 

691 return Selector(Path(self.__settings["general"]["selector"]), 

692 self.DEFAULT_selection_output_raw) 

693 

694 def set_general_target_cutoff_time( 

695 self: Settings, value: int = DEFAULT_general_target_cutoff_time, 

696 origin: SettingState = SettingState.DEFAULT) -> None: 

697 """Set the cutoff time in seconds for target algorithms.""" 

698 section = "general" 

699 name = "target_cutoff_time" 

700 

701 if value is not None and self.__check_setting_state( 

702 self.__general_target_cutoff_time_set, origin, name): 

703 self.__init_section(section) 

704 self.__general_target_cutoff_time_set = origin 

705 self.__settings[section][name] = str(value) 

706 

707 def get_general_target_cutoff_time(self: Settings) -> int: 

708 """Return the cutoff time in seconds for target algorithms.""" 

709 if self.__general_target_cutoff_time_set == SettingState.NOT_SET: 

710 self.set_general_target_cutoff_time() 

711 return int(self.__settings["general"]["target_cutoff_time"]) 

712 

713 def set_general_extractor_cutoff_time( 

714 self: Settings, value: int = DEFAULT_general_extractor_cutoff_time, 

715 origin: SettingState = SettingState.DEFAULT) -> None: 

716 """Set the cutoff time in seconds for feature extraction.""" 

717 section = "general" 

718 name = "extractor_cutoff_time" 

719 

720 if value is not None and self.__check_setting_state( 

721 self.__general_extractor_cutoff_time_set, origin, name): 

722 self.__init_section(section) 

723 self.__general_extractor_cutoff_time_set = origin 

724 self.__settings[section][name] = str(value) 

725 

726 def get_general_extractor_cutoff_time(self: Settings) -> int: 

727 """Return the cutoff time in seconds for feature extraction.""" 

728 if self.__general_extractor_cutoff_time_set == SettingState.NOT_SET: 

729 self.set_general_extractor_cutoff_time() 

730 return int(self.__settings["general"]["extractor_cutoff_time"]) 

731 

732 def set_number_of_jobs_in_parallel( 

733 self: Settings, value: int = DEFAULT_number_of_jobs_in_parallel, 

734 origin: SettingState = SettingState.DEFAULT) -> None: 

735 """Set the number of runs Sparkle can do in parallel.""" 

736 section = "slurm" 

737 name = "number_of_jobs_in_parallel" 

738 

739 if value is not None and self.__check_setting_state( 

740 self.__number_of_jobs_in_parallel_set, origin, name): 

741 self.__init_section(section) 

742 self.__number_of_jobs_in_parallel_set = origin 

743 self.__settings[section][name] = str(value) 

744 

745 def get_number_of_jobs_in_parallel(self: Settings) -> int: 

746 """Return the number of runs Sparkle can do in parallel.""" 

747 if self.__number_of_jobs_in_parallel_set == SettingState.NOT_SET: 

748 self.set_number_of_jobs_in_parallel() 

749 

750 return int(self.__settings["slurm"]["number_of_jobs_in_parallel"]) 

751 

752 def set_general_verbosity( 

753 self: Settings, value: VerbosityLevel = DEFAULT_general_verbosity, 

754 origin: SettingState = SettingState.DEFAULT) -> None: 

755 """Set the general verbosity to use.""" 

756 section = "general" 

757 name = "verbosity" 

758 

759 if value is not None and self.__check_setting_state( 

760 self.__general_verbosity_set, origin, name): 

761 self.__init_section(section) 

762 self.__general_verbosity_set = origin 

763 self.__settings[section][name] = value.name 

764 

765 def get_general_verbosity(self: Settings) -> VerbosityLevel: 

766 """Return the general verbosity.""" 

767 if self.__general_verbosity_set == SettingState.NOT_SET: 

768 self.set_general_verbosity() 

769 

770 return VerbosityLevel.from_string( 

771 self.__settings["general"]["verbosity"]) 

772 

773 def set_general_check_interval( 

774 self: Settings, 

775 value: int = DEFAULT_general_check_interval, 

776 origin: SettingState = SettingState.DEFAULT) -> None: 

777 """Set the general check interval.""" 

778 section = "general" 

779 name = "check_interval" 

780 

781 if value is not None and self.__check_setting_state( 

782 self.__general_check_interval_set, origin, name): 

783 self.__init_section(section) 

784 self.__general_check_interval_set = origin 

785 self.__settings[section][name] = str(value) 

786 

787 def get_general_check_interval(self: Settings) -> int: 

788 """Return the general check interval.""" 

789 if self.__general_check_interval_set == SettingState.NOT_SET: 

790 self.set_general_check_interval() 

791 

792 return int(self.__settings["general"]["check_interval"]) 

793 

794 # Configuration settings General ### 

795 

796 def get_configurator_settings(self: Settings, 

797 configurator_name: str) -> dict[str, any]: 

798 """Return the configurator settings.""" 

799 configurator_settings = { 

800 "number_of_runs": self.get_configurator_number_of_runs(), 

801 "solver_calls": self.get_configurator_solver_calls(), 

802 "cutoff_time": self.get_general_target_cutoff_time(), 

803 "max_iterations": self.get_configurator_max_iterations() 

804 } 

805 # In the settings below, we default to the configurator general settings if no 

806 # specific configurator settings are given, by using the [None] or [Value] 

807 if configurator_name == cim.SMAC2.__name__: 

808 # Return all settings from the SMAC2 section 

809 configurator_settings.update({ 

810 "cpu_time": self.get_smac2_cpu_time(), 

811 "wallclock_time": self.get_smac2_wallclock_time(), 

812 "target_cutoff_length": self.get_smac2_target_cutoff_length(), 

813 "use_cpu_time_in_tunertime": self.get_smac2_use_cpu_time_in_tunertime(), 

814 "cli_cores": self.get_smac2_cli_cores(), 

815 "max_iterations": self.get_smac2_max_iterations() 

816 or configurator_settings["max_iterations"], 

817 }) 

818 elif configurator_name == cim.SMAC3.__name__: 

819 # Return all settings from the SMAC3 section 

820 del configurator_settings["max_iterations"] # SMAC3 does not have this? 

821 configurator_settings.update({ 

822 "smac_facade": self.get_smac3_smac_facade(), 

823 "max_ratio": self.get_smac3_facade_max_ratio(), 

824 "crash_cost": self.get_smac3_crash_cost(), 

825 "termination_cost_threshold": 

826 self.get_smac3_termination_cost_threshold(), 

827 "walltime_limit": self.get_smac3_walltime_limit(), 

828 "cputime_limit": self.get_smac3_cputime_limit(), 

829 "use_default_config": self.get_smac3_use_default_config(), 

830 "min_budget": self.get_smac3_min_budget(), 

831 "max_budget": self.get_smac3_max_budget(), 

832 "solver_calls": self.get_smac3_number_of_trials() 

833 or configurator_settings["solver_calls"], 

834 }) 

835 # Do not pass None values to SMAC3, it Scenario resolves default settings 

836 configurator_settings = {key: value 

837 for key, value in configurator_settings.items() 

838 if value is not None} 

839 elif configurator_name == cim.IRACE.__name__: 

840 # Return all settings from the IRACE section 

841 configurator_settings.update({ 

842 "solver_calls": self.get_irace_max_experiments(), 

843 "max_time": self.get_irace_max_time(), 

844 "first_test": self.get_irace_first_test(), 

845 "mu": self.get_irace_mu(), 

846 "max_iterations": self.get_irace_max_iterations() 

847 or configurator_settings["max_iterations"], 

848 }) 

849 if (configurator_settings["solver_calls"] == 0 

850 and configurator_settings["max_time"] == 0): # Default to base 

851 configurator_settings["solver_calls"] =\ 

852 self.get_configurator_solver_calls() 

853 return configurator_settings 

854 

855 def set_configurator_solver_calls( 

856 self: Settings, value: int = DEFAULT_configurator_solver_calls, 

857 origin: SettingState = SettingState.DEFAULT) -> None: 

858 """Set the number of solver calls.""" 

859 section = "configuration" 

860 name = "solver_calls" 

861 

862 if value is not None and self.__check_setting_state( 

863 self.__config_solver_calls_set, origin, name): 

864 self.__init_section(section) 

865 self.__config_solver_calls_set = origin 

866 self.__settings[section][name] = str(value) 

867 

868 def get_configurator_solver_calls(self: Settings) -> int | None: 

869 """Return the maximum number of solver calls the configurator can do.""" 

870 if self.__config_solver_calls_set == SettingState.NOT_SET: 

871 self.set_configurator_solver_calls() 

872 

873 return int(self.__settings["configuration"]["solver_calls"]) 

874 

875 def set_configurator_number_of_runs( 

876 self: Settings, value: int = DEFAULT_configurator_number_of_runs, 

877 origin: SettingState = SettingState.DEFAULT) -> None: 

878 """Set the number of configuration runs.""" 

879 section = "configuration" 

880 name = "number_of_runs" 

881 

882 if value is not None and self.__check_setting_state( 

883 self.__config_number_of_runs_set, origin, name): 

884 self.__init_section(section) 

885 self.__config_number_of_runs_set = origin 

886 self.__settings[section][name] = str(value) 

887 

888 def get_configurator_number_of_runs(self: Settings) -> int: 

889 """Return the number of configuration runs.""" 

890 if self.__config_number_of_runs_set == SettingState.NOT_SET: 

891 self.set_configurator_number_of_runs() 

892 

893 return int(self.__settings["configuration"]["number_of_runs"]) 

894 

895 def set_configurator_max_iterations( 

896 self: Settings, value: int = DEFAULT_configurator_maximum_iterations, 

897 origin: SettingState = SettingState.DEFAULT) -> None: 

898 """Set the number of configuration runs.""" 

899 section = "configuration" 

900 name = "max_iterations" 

901 

902 if self.__check_setting_state( 

903 self.__config_max_iterations_set, origin, name): 

904 self.__init_section(section) 

905 self.__config_max_iterations_set = origin 

906 self.__settings[section][name] = str(value) 

907 

908 def get_configurator_max_iterations(self: Settings) -> int | None: 

909 """Get the maximum number of configurator iterations.""" 

910 if self.__config_max_iterations_set == SettingState.NOT_SET: 

911 self.set_configurator_max_iterations() 

912 max_iterations = self.__settings["configuration"]["max_iterations"] 

913 return int(max_iterations) if max_iterations.isdigit() else None 

914 

915 # Configuration: SMAC2 specific settings ### 

916 

917 def set_smac2_wallclock_time( 

918 self: Settings, value: int = DEFAULT_smac2_wallclock_time, 

919 origin: SettingState = SettingState.DEFAULT) -> None: 

920 """Set the budget per configuration run in seconds (wallclock).""" 

921 section = "smac2" 

922 name = "wallclock_time" 

923 

924 if self.__check_setting_state( 

925 self.__smac2_wallclock_time_set, origin, name): 

926 self.__init_section(section) 

927 self.__smac2_wallclock_time_set = origin 

928 self.__settings[section][name] = str(value) 

929 

930 def get_smac2_wallclock_time(self: Settings) -> int | None: 

931 """Return the budget per configuration run in seconds (wallclock).""" 

932 if self.__smac2_wallclock_time_set == SettingState.NOT_SET: 

933 self.set_smac2_wallclock_time() 

934 wallclock_time = self.__settings["smac2"]["wallclock_time"] 

935 return int(wallclock_time) if wallclock_time.isdigit() else None 

936 

937 def set_smac2_cpu_time( 

938 self: Settings, value: int = DEFAULT_smac2_cpu_time, 

939 origin: SettingState = SettingState.DEFAULT) -> None: 

940 """Set the budget per configuration run in seconds (cpu).""" 

941 section = "smac2" 

942 name = "cpu_time" 

943 

944 if self.__check_setting_state( 

945 self.__smac2_cpu_time_set, origin, name): 

946 self.__init_section(section) 

947 self.__smac2_cpu_time_set = origin 

948 self.__settings[section][name] = str(value) 

949 

950 def get_smac2_cpu_time(self: Settings) -> int | None: 

951 """Return the budget per configuration run in seconds (cpu).""" 

952 if self.__smac2_cpu_time_set == SettingState.NOT_SET: 

953 self.set_smac2_cpu_time() 

954 cpu_time = self.__settings["smac2"]["cpu_time"] 

955 return int(cpu_time) if cpu_time.isdigit() else None 

956 

957 def set_smac2_target_cutoff_length( 

958 self: Settings, value: str = DEFAULT_smac2_target_cutoff_length, 

959 origin: SettingState = SettingState.DEFAULT) -> None: 

960 """Set the target algorithm cutoff length.""" 

961 section = "smac2" 

962 name = "target_cutoff_length" 

963 

964 if value is not None and self.__check_setting_state( 

965 self.__smac2_target_cutoff_length_set, origin, name): 

966 self.__init_section(section) 

967 self.__smac2_target_cutoff_length_set = origin 

968 self.__settings[section][name] = str(value) 

969 

970 def get_smac2_target_cutoff_length(self: Settings) -> str: 

971 """Return the target algorithm cutoff length. 

972 

973 'A domain specific measure of when the algorithm should consider itself done.' 

974 

975 Returns: 

976 The target algorithm cutoff length. 

977 """ 

978 if self.__smac2_target_cutoff_length_set == SettingState.NOT_SET: 

979 self.set_smac2_target_cutoff_length() 

980 return self.__settings["smac2"]["target_cutoff_length"] 

981 

982 def set_smac2_use_cpu_time_in_tunertime( 

983 self: Settings, value: bool = DEFAULT_smac2_use_cpu_time_in_tunertime, 

984 origin: SettingState = SettingState.DEFAULT) -> None: 

985 """Set whether to use CPU time in tunertime.""" 

986 section = "smac2" 

987 name = "use_cpu_time_in_tunertime" 

988 

989 if self.__check_setting_state( 

990 self.__smac2_use_cpu_time_in_tunertime_set, origin, name): 

991 self.__init_section(section) 

992 self.__smac2_use_cpu_time_in_tunertime_set = origin 

993 self.__settings[section][name] = str(value) 

994 

995 def get_smac2_use_cpu_time_in_tunertime(self: Settings) -> bool: 

996 """Return whether to use CPU time in tunertime.""" 

997 if self.__smac2_use_cpu_time_in_tunertime_set == SettingState.NOT_SET: 

998 self.set_smac2_use_cpu_time_in_tunertime() 

999 return ast.literal_eval(self.__settings["smac2"]["use_cpu_time_in_tunertime"]) 

1000 

1001 def set_smac2_cli_cores( 

1002 self: Settings, value: int = DEFAULT_smac2_cli_cores, 

1003 origin: SettingState = SettingState.DEFAULT) -> None: 

1004 """Set the number of cores to use for SMAC2 CLI.""" 

1005 section = "smac2" 

1006 name = "cli_cores" 

1007 

1008 if self.__check_setting_state( 

1009 self.__smac2_cli_cores_set, origin, name): 

1010 self.__init_section(section) 

1011 self.__smac2_cli_cores_set = origin 

1012 self.__settings[section][name] = str(value) 

1013 

1014 def get_smac2_cli_cores(self: Settings) -> int | None: 

1015 """Number of cores to use to execute runs. 

1016 

1017 In other words, the number of requests to run at a given time. 

1018 """ 

1019 if self.__smac2_cli_cores_set == SettingState.NOT_SET: 

1020 self.set_smac2_cli_cores() 

1021 cli_cores = self.__settings["smac2"]["cli_cores"] 

1022 return int(cli_cores) if cli_cores.isdigit() else None 

1023 

1024 def set_smac2_max_iterations( 

1025 self: Settings, value: int = DEFAULT_smac2_max_iterations, 

1026 origin: SettingState = SettingState.DEFAULT) -> None: 

1027 """Set the maximum number of SMAC2 iterations.""" 

1028 section = "smac2" 

1029 name = "max_iterations" 

1030 

1031 if self.__check_setting_state( 

1032 self.__smac2_max_iterations_set, origin, name): 

1033 self.__init_section(section) 

1034 self.__smac2_max_iterations_set = origin 

1035 self.__settings[section][name] = str(value) 

1036 

1037 def get_smac2_max_iterations(self: Settings) -> int | None: 

1038 """Get the maximum number of SMAC2 iterations.""" 

1039 if self.__smac2_max_iterations_set == SettingState.NOT_SET: 

1040 self.set_smac2_max_iterations() 

1041 max_iterations = self.__settings["smac2"]["max_iterations"] 

1042 return int(max_iterations) if max_iterations.isdigit() else None 

1043 

1044 # Configuration: SMAC3 specific settings ### 

1045 

1046 def set_smac3_number_of_trials( 

1047 self: Settings, value: int = DEFAULT_smac3_number_of_runs, 

1048 origin: SettingState = SettingState.DEFAULT) -> None: 

1049 """Set the number of SMAC3 trials.""" 

1050 section = "smac3" 

1051 name = "number_of_runs" 

1052 

1053 if self.__check_setting_state( 

1054 self.__smac3_number_of_trials_set, origin, name): 

1055 self.__init_section(section) 

1056 self.__smac3_number_of_trials_set = origin 

1057 self.__settings[section][name] = str(value) 

1058 

1059 def get_smac3_number_of_trials(self: Settings) -> int | None: 

1060 """Return the number of SMAC3 trials (Solver calls). 

1061 

1062 'The maximum number of trials (combination of configuration, seed, budget, 

1063 and instance, depending on the task) to run.' 

1064 """ 

1065 if self.__smac3_number_of_trials_set == SettingState.NOT_SET: 

1066 self.set_smac3_number_of_trials() 

1067 number_of_runs = self.__settings["smac3"]["number_of_runs"] 

1068 return int(number_of_runs) if number_of_runs.isdigit() else None 

1069 

1070 def set_smac3_smac_facade( 

1071 self: Settings, value: str = DEFAULT_smac3_facade, 

1072 origin: SettingState = SettingState.DEFAULT) -> None: 

1073 """Set the SMAC3 facade.""" 

1074 section = "smac3" 

1075 name = "facade" 

1076 

1077 if self.__check_setting_state(self.__smac3_smac_facade_set, origin, name): 

1078 self.__init_section(section) 

1079 self.__smac3_smac_facade_set = origin 

1080 self.__settings[section][name] = str(value) 

1081 

1082 def get_smac3_smac_facade(self: Settings) -> str: 

1083 """Return the SMAC3 facade.""" 

1084 if self.__smac3_smac_facade_set == SettingState.NOT_SET: 

1085 self.set_smac3_smac_facade() 

1086 return self.__settings["smac3"]["facade"] 

1087 

1088 def set_smac3_facade_max_ratio( 

1089 self: Settings, value: float = DEFAULT_smac3_facade_max_ratio, 

1090 origin: SettingState = SettingState.DEFAULT) -> None: 

1091 """Set the SMAC3 facade max ratio.""" 

1092 section = "smac3" 

1093 name = "facade_max_ratio" 

1094 

1095 if self.__check_setting_state( 

1096 self.__smac3_facade_max_ratio_set, origin, name): 

1097 self.__init_section(section) 

1098 self.__smac3_facade_max_ratio_set = origin 

1099 self.__settings[section][name] = str(value) 

1100 

1101 def get_smac3_facade_max_ratio(self: Settings) -> float: 

1102 """Return the SMAC3 facade max ratio.""" 

1103 if self.__smac3_facade_max_ratio_set == SettingState.NOT_SET: 

1104 self.set_smac3_facade_max_ratio() 

1105 return ast.literal_eval(self.__settings["smac3"]["facade_max_ratio"]) 

1106 

1107 def set_smac3_crash_cost(self: Settings, value: float = DEFAULT_smac3_crash_cost, 

1108 origin: SettingState = SettingState.DEFAULT) -> None: 

1109 """Set the SMAC3 objective crash cost.""" 

1110 section = "smac3" 

1111 name = "crash_cost" 

1112 

1113 if self.__check_setting_state(self.__smac3_crash_cost_set, origin, name): 

1114 self.__init_section(section) 

1115 self.__smac3_smac_facade_set = origin 

1116 self.__settings[section][name] = str(value) 

1117 

1118 def get_smac3_crash_cost(self: Settings) -> float | list[float]: 

1119 """Get the SMAC3 objective crash cost. 

1120 

1121 'crash_cost : float | list[float], defaults to np.inf 

1122 Defines the cost for a failed trial. In case of multi-objective, 

1123 each objective can be associated with a different cost.' 

1124 """ 

1125 if self.__smac3_crash_cost_set == SettingState.NOT_SET: 

1126 self.set_smac3_crash_cost() 

1127 return ast.literal_eval(self.__settings["smac3"]["crash_cost"]) 

1128 

1129 def set_smac3_termination_cost_threshold( 

1130 self: Settings, 

1131 value: float = DEFAULT_smac3_termination_cost_threshold, 

1132 origin: SettingState = SettingState.DEFAULT) -> None: 

1133 """Set the SMAC3 termination cost threshold.""" 

1134 section = "smac3" 

1135 name = "termination_cost_threshold" 

1136 

1137 if self.__check_setting_state( 

1138 self.__smac3_termination_cost_threshold_set, origin, name): 

1139 self.__init_section(section) 

1140 self.__smac3_termination_cost_threshold_set = origin 

1141 self.__settings[section][name] = str(value) 

1142 

1143 def get_smac3_termination_cost_threshold(self: Settings) -> float | list[float]: 

1144 """Get the SMAC3 termination cost threshold. 

1145 

1146 'Defines a cost threshold when the optimization should stop. In case of 

1147 multi-objective, each objective *must* be associated with a cost. 

1148 The optimization stops when all objectives crossed the threshold.' 

1149 """ 

1150 if self.__smac3_termination_cost_threshold_set == SettingState.NOT_SET: 

1151 self.set_smac3_termination_cost_threshold() 

1152 return ast.literal_eval(self.__settings["smac3"]["termination_cost_threshold"]) 

1153 

1154 def set_smac3_walltime_limit( 

1155 self: Settings, value: float = DEFAULT_smac3_walltime_limit, 

1156 origin: SettingState = SettingState.DEFAULT) -> None: 

1157 """Set the SMAC3 walltime limit.""" 

1158 section = "smac3" 

1159 name = "walltime_limit" 

1160 

1161 if self.__check_setting_state(self.__smac3_walltime_limit_set, origin, name): 

1162 self.__init_section(section) 

1163 self.__smac3_walltime_limit_set = origin 

1164 self.__settings[section][name] = str(value) 

1165 

1166 def get_smac3_walltime_limit(self: Settings) -> float: 

1167 """Get the SMAC3 walltime limit. 

1168 

1169 'The maximum time in seconds that SMAC is allowed to run.' 

1170 """ 

1171 if self.__smac3_walltime_limit_set == SettingState.NOT_SET: 

1172 self.set_smac3_walltime_limit() 

1173 return ast.literal_eval(self.__settings["smac3"]["walltime_limit"]) 

1174 

1175 def set_smac3_cputime_limit( 

1176 self: Settings, value: float = DEFAULT_smac3_cputime_limit, 

1177 origin: SettingState = SettingState.DEFAULT) -> None: 

1178 """Set the SMAC3 CPU time limit.""" 

1179 section = "smac3" 

1180 name = "cputime_limit" 

1181 

1182 if self.__check_setting_state(self.__smac3_cputime_limit_set, origin, name): 

1183 self.__init_section(section) 

1184 self.__smac3_cputime_limit_set = origin 

1185 self.__settings[section][name] = str(value) 

1186 

1187 def get_smac3_cputime_limit(self: Settings) -> float: 

1188 """Get the SMAC3 CPU time limit. 

1189 

1190 'The maximum CPU time in seconds that SMAC is allowed to run.' 

1191 """ 

1192 if self.__smac3_cputime_limit_set == SettingState.NOT_SET: 

1193 self.set_smac3_cputime_limit() 

1194 return ast.literal_eval(self.__settings["smac3"]["cputime_limit"]) 

1195 

1196 def set_smac3_use_default_config( 

1197 self: Settings, value: bool = DEFAULT_smac3_use_default_config, 

1198 origin: SettingState = SettingState.DEFAULT) -> None: 

1199 """Set the SMAC3 to use default config.""" 

1200 section = "smac3" 

1201 name = "use_default_config" 

1202 

1203 if self.__check_setting_state(self.__smac3_use_default_config_set, origin, name): 

1204 self.__init_section(section) 

1205 self.__smac3_use_default_config_set = origin 

1206 self.__settings[section][name] = str(value) 

1207 

1208 def get_smac3_use_default_config(self: Settings) -> bool: 

1209 """Get the SMAC3 to use default config. 

1210 

1211 'If True, the configspace's default configuration is evaluated in the 

1212 initial design. For historic benchmark reasons, this is False by default. 

1213 Notice, that this will result in n_configs + 1 for the initial design. 

1214 Respecting n_trials, this will result in one fewer evaluated 

1215 configuration in the optimization.' 

1216 """ 

1217 if self.__smac3_use_default_config_set == SettingState.NOT_SET: 

1218 self.set_smac3_use_default_config() 

1219 return ast.literal_eval(self.__settings["smac3"]["use_default_config"]) 

1220 

1221 def set_smac3_min_budget( 

1222 self: Settings, value: int | float = DEFAULT_smac3_min_budget, 

1223 origin: SettingState = SettingState.DEFAULT) -> None: 

1224 """Set the SMAC3 min budget.""" 

1225 section = "smac3" 

1226 name = "min_budget" 

1227 

1228 if self.__check_setting_state(self.__smac3_min_budget_set, origin, name): 

1229 self.__init_section(section) 

1230 self.__smac3_min_budget_set = origin 

1231 self.__settings[section][name] = str(value) 

1232 

1233 def get_smac3_min_budget(self: Settings) -> int | float: 

1234 """Get the SMAC3 min budget. 

1235 

1236 'The minimum budget (epochs, subset size, number of instances, ...) that 

1237 is used for the optimization. Use this argument if you use multi-fidelity 

1238 or instance optimization.' 

1239 """ 

1240 if self.__smac3_min_budget_set == SettingState.NOT_SET: 

1241 self.set_smac3_min_budget() 

1242 return ast.literal_eval(self.__settings["smac3"]["min_budget"]) 

1243 

1244 def set_smac3_max_budget( 

1245 self: Settings, value: int | float = DEFAULT_smac3_max_budget, 

1246 origin: SettingState = SettingState.DEFAULT) -> None: 

1247 """Set the SMAC3 max budget.""" 

1248 section = "smac3" 

1249 name = "max_budget" 

1250 

1251 if self.__check_setting_state(self.__smac3_max_budget_set, origin, name): 

1252 self.__init_section(section) 

1253 self.__smac3_max_budget_set = origin 

1254 self.__settings[section][name] = str(value) 

1255 

1256 def get_smac3_max_budget(self: Settings) -> int | float: 

1257 """Get the SMAC3 max budget. 

1258 

1259 'The maximum budget (epochs, subset size, number of instances, ...) that 

1260 is used for the optimization. Use this argument if you use multi-fidelity 

1261 or instance optimization.' 

1262 """ 

1263 if self.__smac3_max_budget_set == SettingState.NOT_SET: 

1264 self.set_smac3_max_budget() 

1265 return ast.literal_eval(self.__settings["smac3"]["max_budget"]) 

1266 

1267 # Configuration: IRACE specific settings ### 

1268 

1269 def get_irace_max_time(self: Settings) -> int: 

1270 """Return the max time in seconds for IRACE.""" 

1271 if self.__irace_max_time_set == SettingState.NOT_SET: 

1272 self.set_irace_max_time() 

1273 return int(self.__settings["irace"]["max_time"]) 

1274 

1275 def set_irace_max_time( 

1276 self: Settings, value: int = DEFAULT_irace_max_time, 

1277 origin: SettingState = SettingState.DEFAULT) -> None: 

1278 """Set the max time in seconds for IRACE.""" 

1279 section = "irace" 

1280 name = "max_time" 

1281 

1282 if value is not None and self.__check_setting_state( 

1283 self.__irace_max_time_set, origin, name): 

1284 self.__init_section(section) 

1285 self.__irace_max_time_set = origin 

1286 self.__settings[section][name] = str(value) 

1287 

1288 def get_irace_max_experiments(self: Settings) -> int: 

1289 """Return the max number of experiments for IRACE.""" 

1290 if self.__irace_max_experiments_set == SettingState.NOT_SET: 

1291 self.set_irace_max_experiments() 

1292 return int(self.__settings["irace"]["max_experiments"]) 

1293 

1294 def set_irace_max_experiments( 

1295 self: Settings, value: int = DEFAULT_irace_max_experiments, 

1296 origin: SettingState = SettingState.DEFAULT) -> None: 

1297 """Set the max number of experiments for IRACE.""" 

1298 section = "irace" 

1299 name = "max_experiments" 

1300 

1301 if value is not None and self.__check_setting_state( 

1302 self.__irace_max_experiments_set, origin, name): 

1303 self.__init_section(section) 

1304 self.__irace_max_experiments_set = origin 

1305 self.__settings[section][name] = str(value) 

1306 

1307 def get_irace_first_test(self: Settings) -> int | None: 

1308 """Return the first test for IRACE. 

1309 

1310 Specifies how many instances are evaluated before the first 

1311 elimination test. IRACE Default: 5. [firstTest] 

1312 """ 

1313 if self.__irace_first_test_set == SettingState.NOT_SET: 

1314 self.set_irace_first_test() 

1315 first_test = self.__settings["irace"]["first_test"] 

1316 return int(first_test) if first_test.isdigit() else None 

1317 

1318 def set_irace_first_test( 

1319 self: Settings, value: int = DEFAULT_irace_first_test, 

1320 origin: SettingState = SettingState.DEFAULT) -> None: 

1321 """Set the first test for IRACE.""" 

1322 section = "irace" 

1323 name = "first_test" 

1324 

1325 if self.__check_setting_state( 

1326 self.__irace_first_test_set, origin, name): 

1327 self.__init_section(section) 

1328 self.__irace_first_test_set = origin 

1329 self.__settings[section][name] = str(value) 

1330 

1331 def get_irace_mu(self: Settings) -> int | None: 

1332 """Return the mu for IRACE. 

1333 

1334 Parameter used to define the number of configurations sampled and 

1335 evaluated at each iteration. IRACE Default: 5. [mu] 

1336 """ 

1337 if self.__irace_mu_set == SettingState.NOT_SET: 

1338 self.set_irace_mu() 

1339 mu = self.__settings["irace"]["mu"] 

1340 return int(mu) if mu.isdigit() else None 

1341 

1342 def set_irace_mu( 

1343 self: Settings, value: int = DEFAULT_irace_mu, 

1344 origin: SettingState = SettingState.DEFAULT) -> None: 

1345 """Set the mu for IRACE.""" 

1346 section = "irace" 

1347 name = "mu" 

1348 

1349 if self.__check_setting_state( 

1350 self.__irace_mu_set, origin, name): 

1351 self.__init_section(section) 

1352 self.__irace_mu_set = origin 

1353 self.__settings[section][name] = str(value) 

1354 

1355 def get_irace_max_iterations(self: Settings) -> int: 

1356 """Return the number of iterations for IRACE.""" 

1357 if self.__irace_max_iterations_set == SettingState.NOT_SET: 

1358 self.set_irace_max_iterations() 

1359 max_iterations = self.__settings["irace"]["max_iterations"] 

1360 return int(max_iterations) if max_iterations.isdigit() else None 

1361 

1362 def set_irace_max_iterations( 

1363 self: Settings, value: int = DEFAULT_irace_max_iterations, 

1364 origin: SettingState = SettingState.DEFAULT) -> None: 

1365 """Set the number of iterations for IRACE. 

1366 

1367 Maximum number of iterations to be executed. Each iteration involves the 

1368 generation of new configurations and the use of racing to select the best 

1369 configurations. By default (with 0), irace calculates a minimum number of 

1370 iterations as N^iter = ⌊2 + log2 N param⌋, where N^param is the number of 

1371 non-fixed parameters to be tuned. 

1372 Setting this parameter may make irace stop sooner than it should without using 

1373 all the available budget. IRACE recommends to use the default value (Empty). 

1374 """ 

1375 section = "irace" 

1376 name = "max_iterations" 

1377 

1378 if self.__check_setting_state( 

1379 self.__irace_max_iterations_set, origin, name): 

1380 self.__init_section(section) 

1381 self.__irace_max_iterations_set = origin 

1382 self.__settings[section][name] = str(value) 

1383 

1384 # Slurm settings ### 

1385 

1386 def set_slurm_max_parallel_runs_per_node( 

1387 self: Settings, 

1388 value: int = DEFAULT_slurm_max_parallel_runs_per_node, 

1389 origin: SettingState = SettingState.DEFAULT) -> None: 

1390 """Set the number of algorithms Slurm can run in parallel per node.""" 

1391 section = "slurm" 

1392 name = "max_parallel_runs_per_node" 

1393 

1394 if value is not None and self.__check_setting_state( 

1395 self.__slurm_max_parallel_runs_per_node_set, origin, name): 

1396 self.__init_section(section) 

1397 self.__slurm_max_parallel_runs_per_node_set = origin 

1398 self.__settings[section][name] = str(value) 

1399 

1400 def get_slurm_max_parallel_runs_per_node(self: Settings) -> int: 

1401 """Return the number of algorithms Slurm can run in parallel per node.""" 

1402 if self.__slurm_max_parallel_runs_per_node_set == SettingState.NOT_SET: 

1403 self.set_slurm_max_parallel_runs_per_node() 

1404 

1405 return int(self.__settings["slurm"]["max_parallel_runs_per_node"]) 

1406 

1407 # SLURM extra options 

1408 

1409 def add_slurm_extra_option(self: Settings, name: str, value: str, 

1410 origin: SettingState = SettingState.DEFAULT) -> None: 

1411 """Add additional Slurm options.""" 

1412 section = "slurm_extra" 

1413 

1414 current_state = (self.__slurm_extra_options_set[name] 

1415 if name in self.__slurm_extra_options_set 

1416 else SettingState.NOT_SET) 

1417 

1418 if value is not None and self.__check_setting_state(current_state, origin, name): 

1419 self.__init_section(section) 

1420 self.__slurm_extra_options_set[name] = origin 

1421 self.__settings[section][name] = str(value) 

1422 

1423 def get_slurm_extra_options(self: Settings, 

1424 as_args: bool = False) -> dict | list: 

1425 """Return a dict with additional Slurm options.""" 

1426 section = "slurm_extra" 

1427 options = dict() 

1428 

1429 if "slurm_extra" in self.__settings.sections(): 

1430 for option in self.__settings["slurm_extra"]: 

1431 options[option] = self.__settings.get(section, option) 

1432 if as_args: 

1433 return [f"--{key}={options[key]}" for key in options.keys()] 

1434 return options 

1435 

1436 # Ablation settings ### 

1437 

1438 def set_ablation_racing_flag(self: Settings, value: bool = DEFAULT_ablation_racing, 

1439 origin: SettingState = SettingState.DEFAULT) -> None: 

1440 """Set a flag indicating whether racing should be used for ablation.""" 

1441 section = "ablation" 

1442 name = "racing" 

1443 

1444 if value is not None and self.__check_setting_state( 

1445 self.__ablation_racing_flag_set, origin, name): 

1446 self.__init_section(section) 

1447 self.__ablation_racing_flag_set = origin 

1448 self.__settings[section][name] = str(value) 

1449 

1450 def get_ablation_racing_flag(self: Settings) -> bool: 

1451 """Return a bool indicating whether the racing flag is set for ablation.""" 

1452 if self.__ablation_racing_flag_set == SettingState.NOT_SET: 

1453 self.set_ablation_racing_flag() 

1454 

1455 return bool(self.__settings["ablation"]["racing"]) 

1456 

1457 # Parallel Portfolio settings 

1458 

1459 def set_parallel_portfolio_check_interval( 

1460 self: Settings, 

1461 value: int = DEFAULT_parallel_portfolio_check_interval, 

1462 origin: SettingState = SettingState.DEFAULT) -> None: 

1463 """Set the parallel portfolio check interval.""" 

1464 section = "parallel_portfolio" 

1465 name = "check_interval" 

1466 

1467 if value is not None and self.__check_setting_state( 

1468 self.__parallel_portfolio_check_interval_set, origin, name): 

1469 self.__init_section(section) 

1470 self.__parallel_portfolio_check_interval_set = origin 

1471 self.__settings[section][name] = str(value) 

1472 

1473 def get_parallel_portfolio_check_interval(self: Settings) -> int: 

1474 """Return the parallel portfolio check interval.""" 

1475 if self.__parallel_portfolio_check_interval_set == SettingState.NOT_SET: 

1476 self.set_parallel_portfolio_check_interval() 

1477 

1478 return int( 

1479 self.__settings["parallel_portfolio"]["check_interval"]) 

1480 

1481 def set_parallel_portfolio_number_of_seeds_per_solver( 

1482 self: Settings, 

1483 value: int = DEFAULT_parallel_portfolio_num_seeds_per_solver, 

1484 origin: SettingState = SettingState.DEFAULT) -> None: 

1485 """Set the parallel portfolio seeds per solver to start.""" 

1486 section = "parallel_portfolio" 

1487 name = "num_seeds_per_solver" 

1488 

1489 if value is not None and self.__check_setting_state( 

1490 self.__parallel_portfolio_num_seeds_per_solver_set, origin, name): 

1491 self.__init_section(section) 

1492 self.__parallel_portfolio_num_seeds_per_solver_set = origin 

1493 self.__settings[section][name] = str(value) 

1494 

1495 def get_parallel_portfolio_number_of_seeds_per_solver(self: Settings) -> int: 

1496 """Return the parallel portfolio seeds per solver to start.""" 

1497 if self.__parallel_portfolio_num_seeds_per_solver_set == SettingState.NOT_SET: 

1498 self.set_parallel_portfolio_number_of_seeds_per_solver() 

1499 

1500 return int( 

1501 self.__settings["parallel_portfolio"]["num_seeds_per_solver"]) 

1502 

1503 def set_run_on(self: Settings, value: Runner = DEFAULT_general_run_on, 

1504 origin: SettingState = SettingState.DEFAULT) -> None: 

1505 """Set the compute on which to run.""" 

1506 section = "general" 

1507 name = "run_on" 

1508 

1509 if value is not None and self.__check_setting_state( 

1510 self.__run_on_set, origin, name): 

1511 self.__init_section(section) 

1512 self.__run_on_set = origin 

1513 self.__settings[section][name] = value 

1514 

1515 def get_run_on(self: Settings) -> Runner: 

1516 """Return the compute on which to run.""" 

1517 if self.__run_on_set == SettingState.NOT_SET: 

1518 self.set_run_on() 

1519 

1520 return Runner(self.__settings["general"]["run_on"]) 

1521 

1522 @staticmethod 

1523 def check_settings_changes(cur_settings: Settings, prev_settings: Settings) -> bool: 

1524 """Check if there are changes between the previous and the current settings. 

1525 

1526 Prints any section changes, printing None if no setting was found. 

1527 

1528 Args: 

1529 cur_settings: The current settings 

1530 prev_settings: The previous settings 

1531 

1532 Returns: 

1533 True iff there are no changes. 

1534 """ 

1535 cur_dict = cur_settings.__settings._sections 

1536 prev_dict = prev_settings.__settings._sections 

1537 

1538 cur_sections_set = set(cur_dict.keys()) 

1539 prev_sections_set = set(prev_dict.keys()) 

1540 sections_removed = prev_sections_set - cur_sections_set 

1541 if sections_removed: 

1542 print("Warning: the following sections have been removed:") 

1543 for section in sections_removed: 

1544 print(f" - Section '{section}'") 

1545 

1546 sections_added = cur_sections_set - prev_sections_set 

1547 if sections_added: 

1548 print("Warning: the following sections have been added:") 

1549 for section in sections_added: 

1550 print(f" - Section '{section}'") 

1551 

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 for name in names: 

1558 # if name is not present in one of the two dicts, get None as placeholder 

1559 cur_val = cur_dict[section].get(name, None) 

1560 prev_val = prev_dict[section].get(name, None) 

1561 

1562 # If cur val is None, it is default 

1563 if cur_val is not None and cur_val != prev_val: 

1564 # Have we printed the initial warning? 

1565 if not option_changed: 

1566 print("Warning: The following attributes/options have changed:") 

1567 option_changed = True 

1568 

1569 # do we have yet to print the section? 

1570 if not printed_section: 

1571 print(f" - In the section '{section}':") 

1572 printed_section = True 

1573 

1574 # print actual change 

1575 print(f" · '{name}' changed from '{prev_val}' to '{cur_val}'") 

1576 

1577 return not (sections_removed or sections_added or option_changed)