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

727 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-05 14:48 +0000

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.solver.verifier import SATVerifier 

14from sparkle.configurator import implementations as cim 

15 

16from runrunner import Runner 

17from sparkle.platform.cli_types import VerbosityLevel 

18 

19 

20class SettingState(Enum): 

21 """Enum of possible setting states.""" 

22 

23 NOT_SET = 0 

24 DEFAULT = 1 

25 FILE = 2 

26 CMD_LINE = 3 

27 

28 

29class Settings: 

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

31 # CWD Prefix 

32 cwd_prefix = Path() # Empty for now 

33 

34 # Library prefix 

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

36 

37 # Default directory names 

38 rawdata_dir = Path("Raw_Data") 

39 analysis_dir = Path("Analysis") 

40 DEFAULT_settings_dir = Path("Settings") 

41 __settings_file = Path("sparkle_settings.ini") 

42 

43 # Default settings path 

44 DEFAULT_settings_path = PurePath(cwd_prefix / DEFAULT_settings_dir / __settings_file) 

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_validation_output = DEFAULT_output / "Validation" 

80 DEFAULT_parallel_portfolio_output = DEFAULT_output / "Parallel_Portfolio" 

81 DEFAULT_ablation_output = DEFAULT_output / "Ablation" 

82 DEFAULT_log_output = DEFAULT_output / "Log" 

83 

84 # Default output subdirs 

85 DEFAULT_configuration_output_raw = DEFAULT_configuration_output / rawdata_dir 

86 DEFAULT_configuration_output_analysis = DEFAULT_configuration_output / analysis_dir 

87 DEFAULT_selection_output_raw = DEFAULT_selection_output / rawdata_dir 

88 DEFAULT_selection_output_analysis = DEFAULT_selection_output / analysis_dir 

89 DEFAULT_parallel_portfolio_output_raw =\ 

90 DEFAULT_parallel_portfolio_output / rawdata_dir 

91 DEFAULT_parallel_portfolio_output_analysis =\ 

92 DEFAULT_parallel_portfolio_output / analysis_dir 

93 

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

95 DEFAULT_feature_data = DEFAULT_output / "Feature_Data" 

96 DEFAULT_performance_data = DEFAULT_output / "Performance_Data" 

97 

98 # Collection of all working dirs for platform 

99 DEFAULT_working_dirs = [ 

100 DEFAULT_output, DEFAULT_configuration_output, 

101 DEFAULT_selection_output, DEFAULT_validation_output, 

102 DEFAULT_tmp_output, DEFAULT_log_output, 

103 DEFAULT_solver_dir, DEFAULT_instance_dir, 

104 DEFAULT_feature_data, DEFAULT_performance_data, 

105 DEFAULT_extractor_dir, DEFAULT_settings_dir 

106 ] 

107 

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

109 DEFAULT_feature_data_path =\ 

110 DEFAULT_feature_data / "feature_data.csv" 

111 DEFAULT_performance_data_path =\ 

112 DEFAULT_performance_data / "performance_data.csv" 

113 

114 # Constant default values 

115 DEFAULT_general_sparkle_objective = PAR(10) 

116 DEFAULT_general_sparkle_configurator = cim.SMAC2.__name__ 

117 DEFAULT_general_solution_verifier = str(None) 

118 DEFAULT_general_target_cutoff_time = 60 

119 DEFAULT_general_extractor_cutoff_time = 60 

120 DEFAULT_number_of_jobs_in_parallel = 25 

121 DEFAULT_general_verbosity = VerbosityLevel.STANDARD 

122 DEFAULT_general_check_interval = 10 

123 DEFAULT_general_run_on = "local" 

124 

125 DEFAULT_configurator_number_of_runs = 25 

126 DEFAULT_configurator_solver_calls = 100 

127 DEFAULT_configurator_maximum_iterations = None 

128 

129 DEFAULT_smac2_wallclock_time = None 

130 DEFAULT_smac2_cpu_time = None 

131 DEFAULT_smac2_target_cutoff_length = "max" 

132 DEFAULT_smac2_use_cpu_time_in_tunertime = None 

133 DEFAULT_smac2_max_iterations = None 

134 

135 DEFAULT_portfolio_construction_timeout = None 

136 

137 DEFAULT_slurm_max_parallel_runs_per_node = 8 

138 

139 DEFAULT_ablation_racing = False 

140 

141 DEFAULT_parallel_portfolio_check_interval = 4 

142 DEFAULT_parallel_portfolio_num_seeds_per_solver = 1 

143 

144 # Default IRACE settings 

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

146 DEFAULT_irace_max_experiments = 0 

147 DEFAULT_irace_first_test = None 

148 DEFAULT_irace_mu = None 

149 DEFAULT_irace_max_iterations = None 

150 

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

152 """Initialise a settings object.""" 

153 # Settings 'dictionary' in configparser format 

154 self.__settings = configparser.ConfigParser() 

155 

156 # Setting flags 

157 self.__general_sparkle_objective_set = SettingState.NOT_SET 

158 self.__general_sparkle_configurator_set = SettingState.NOT_SET 

159 self.__general_sparkle_selector_set = SettingState.NOT_SET 

160 self.__general_solution_verifier_set = SettingState.NOT_SET 

161 self.__general_target_cutoff_time_set = SettingState.NOT_SET 

162 self.__general_extractor_cutoff_time_set = SettingState.NOT_SET 

163 self.__general_verbosity_set = SettingState.NOT_SET 

164 self.__general_check_interval_set = SettingState.NOT_SET 

165 

166 self.__config_solver_calls_set = SettingState.NOT_SET 

167 self.__config_number_of_runs_set = SettingState.NOT_SET 

168 self.__config_max_iterations_set = SettingState.NOT_SET 

169 

170 self.__smac2_wallclock_time_set = SettingState.NOT_SET 

171 self.__smac2_cpu_time_set = SettingState.NOT_SET 

172 self.__smac2_use_cpu_time_in_tunertime_set = SettingState.NOT_SET 

173 self.__smac2_max_iterations_set = SettingState.NOT_SET 

174 

175 self.__run_on_set = SettingState.NOT_SET 

176 self.__number_of_jobs_in_parallel_set = SettingState.NOT_SET 

177 self.__slurm_max_parallel_runs_per_node_set = SettingState.NOT_SET 

178 self.__smac2_target_cutoff_length_set = SettingState.NOT_SET 

179 self.__ablation_racing_flag_set = SettingState.NOT_SET 

180 

181 self.__parallel_portfolio_check_interval_set = SettingState.NOT_SET 

182 self.__parallel_portfolio_num_seeds_per_solver_set = SettingState.NOT_SET 

183 

184 self.__irace_max_time_set = SettingState.NOT_SET 

185 self.__irace_max_experiments_set = SettingState.NOT_SET 

186 self.__irace_first_test_set = SettingState.NOT_SET 

187 self.__irace_mu_set = SettingState.NOT_SET 

188 self.__irace_max_iterations_set = SettingState.NOT_SET 

189 

190 self.__general_sparkle_configurator = None 

191 

192 self.__slurm_extra_options_set = dict() 

193 

194 if file_path is None: 

195 # Initialise settings from default file path 

196 self.read_settings_ini() 

197 else: 

198 # Initialise settings from a given file path 

199 self.read_settings_ini(file_path) 

200 

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

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

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

204 # Read file 

205 file_settings = configparser.ConfigParser() 

206 file_settings.read(file_path) 

207 

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

209 # successfully 

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

211 section = "general" 

212 option_names = ("objective", ) 

213 for option in option_names: 

214 if file_settings.has_option(section, option): 

215 value = [resolve_objective(obj) for obj in 

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

217 self.set_general_sparkle_objectives(value, state) 

218 file_settings.remove_option(section, option) 

219 

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

221 option_names = ("configurator", ) 

222 for option in option_names: 

223 if file_settings.has_option(section, option): 

224 value = file_settings.get(section, option) 

225 self.set_general_sparkle_configurator(value, state) 

226 file_settings.remove_option(section, option) 

227 

228 option_names = ("selector", ) 

229 for option in option_names: 

230 if file_settings.has_option(section, option): 

231 value = file_settings.get(section, option) 

232 self.set_general_sparkle_selector(value, state) 

233 file_settings.remove_option(section, option) 

234 

235 option_names = ("solution_verifier", ) 

236 for option in option_names: 

237 if file_settings.has_option(section, option): 

238 value = file_settings.get(section, option).lower() 

239 self.set_general_solution_verifier(value, state) 

240 file_settings.remove_option(section, option) 

241 

242 option_names = ("target_cutoff_time", 

243 "cutoff_time_each_solver_call") 

244 for option in option_names: 

245 if file_settings.has_option(section, option): 

246 value = file_settings.getint(section, option) 

247 self.set_general_target_cutoff_time(value, state) 

248 file_settings.remove_option(section, option) 

249 

250 option_names = ("extractor_cutoff_time", 

251 "cutoff_time_each_feature_computation") 

252 for option in option_names: 

253 if file_settings.has_option(section, option): 

254 value = file_settings.getint(section, option) 

255 self.set_general_extractor_cutoff_time(value, state) 

256 file_settings.remove_option(section, option) 

257 

258 option_names = ("run_on", ) 

259 for option in option_names: 

260 if file_settings.has_option(section, option): 

261 value = file_settings.get(section, option) 

262 self.set_run_on(value, state) 

263 file_settings.remove_option(section, option) 

264 

265 option_names = ("verbosity", ) 

266 for option in option_names: 

267 if file_settings.has_option(section, option): 

268 value = VerbosityLevel.from_string( 

269 file_settings.get(section, option)) 

270 self.set_general_verbosity(value, state) 

271 file_settings.remove_option(section, option) 

272 

273 option_names = ("check_interval", ) 

274 for option in option_names: 

275 if file_settings.has_option(section, option): 

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

277 self.set_general_check_interval(value, state) 

278 file_settings.remove_option(section, option) 

279 

280 section = "configuration" 

281 option_names = ("solver_calls", ) 

282 for option in option_names: 

283 if file_settings.has_option(section, option): 

284 value = file_settings.getint(section, option) 

285 self.set_configurator_solver_calls(value, state) 

286 file_settings.remove_option(section, option) 

287 

288 option_names = ("number_of_runs", ) 

289 for option in option_names: 

290 if file_settings.has_option(section, option): 

291 value = file_settings.getint(section, option) 

292 self.set_configurator_number_of_runs(value, state) 

293 file_settings.remove_option(section, option) 

294 

295 option_name = "max_iterations" 

296 if file_settings.has_option(section, option_name): 

297 value = file_settings.getint(section, option_name) 

298 self.set_configurator_max_iterations(value, state) 

299 file_settings.remove_option(section, option_name) 

300 

301 section = "smac2" 

302 option_names = ("wallclock_time", ) 

303 for option in option_names: 

304 if file_settings.has_option(section, option): 

305 value = file_settings.getint(section, option) 

306 self.set_smac2_wallclock_time(value, state) 

307 file_settings.remove_option(section, option) 

308 

309 option_names = ("cpu_time", ) 

310 for option in option_names: 

311 if file_settings.has_option(section, option): 

312 value = file_settings.getint(section, option) 

313 self.set_smac2_cpu_time(value, state) 

314 file_settings.remove_option(section, option) 

315 

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

317 for option in option_names: 

318 if file_settings.has_option(section, option): 

319 value = file_settings.get(section, option) 

320 self.set_smac2_target_cutoff_length(value, state) 

321 file_settings.remove_option(section, option) 

322 

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

324 for option in option_names: 

325 if file_settings.has_option(section, option): 

326 value = file_settings.getboolean(section, option) 

327 self.set_smac2_use_cpu_time_in_tunertime(value, state) 

328 file_settings.remove_option(section, option) 

329 

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

331 "max_iterations") 

332 for option in options_names: 

333 if file_settings.has_option(section, option): 

334 value = file_settings.getint(section, option) 

335 self.set_smac2_max_iterations(value, state) 

336 file_settings.remove_option(section, option) 

337 

338 section = "irace" 

339 option_names = ("max_time", ) 

340 for option in option_names: 

341 if file_settings.has_option(section, option): 

342 value = file_settings.getint(section, option) 

343 self.set_irace_max_time(value, state) 

344 file_settings.remove_option(section, option) 

345 

346 option_names = ("max_experiments", ) 

347 for option in option_names: 

348 if file_settings.has_option(section, option): 

349 value = file_settings.getint(section, option) 

350 self.set_irace_max_experiments(value, state) 

351 file_settings.remove_option(section, option) 

352 

353 option_names = ("first_test", ) 

354 for option in option_names: 

355 if file_settings.has_option(section, option): 

356 value = file_settings.getint(section, option) 

357 self.set_irace_first_test(value, state) 

358 file_settings.remove_option(section, option) 

359 

360 option_names = ("mu", ) 

361 for option in option_names: 

362 if file_settings.has_option(section, option): 

363 value = file_settings.getint(section, option) 

364 self.set_irace_mu(value, state) 

365 file_settings.remove_option(section, option) 

366 

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

368 for option in option_names: 

369 if file_settings.has_option(section, option): 

370 value = file_settings.getint(section, option) 

371 self.set_irace_max_iterations(value, state) 

372 file_settings.remove_option(section, option) 

373 

374 section = "slurm" 

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

376 for option in option_names: 

377 if file_settings.has_option(section, option): 

378 value = file_settings.getint(section, option) 

379 self.set_number_of_jobs_in_parallel(value, state) 

380 file_settings.remove_option(section, option) 

381 

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

383 for option in option_names: 

384 if file_settings.has_option(section, option): 

385 value = file_settings.getint(section, option) 

386 self.set_slurm_max_parallel_runs_per_node(value, state) 

387 file_settings.remove_option(section, option) 

388 

389 section = "ablation" 

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

391 for option in option_names: 

392 if file_settings.has_option(section, option): 

393 value = file_settings.getboolean(section, option) 

394 self.set_ablation_racing_flag(value, state) 

395 file_settings.remove_option(section, option) 

396 

397 section = "parallel_portfolio" 

398 option_names = ("check_interval", ) 

399 for option in option_names: 

400 if file_settings.has_option(section, option): 

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

402 self.set_parallel_portfolio_check_interval(value, state) 

403 file_settings.remove_option(section, option) 

404 

405 option_names = ("num_seeds_per_solver", ) 

406 for option in option_names: 

407 if file_settings.has_option(section, option): 

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

409 self.set_parallel_portfolio_number_of_seeds_per_solver(value, state) 

410 file_settings.remove_option(section, option) 

411 

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

413 sections = file_settings.sections() 

414 

415 for section in sections: 

416 for option in file_settings[section]: 

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

418 if section == "slurm": 

419 value = file_settings.get(section, option) 

420 self.add_slurm_extra_option(option, value, state) 

421 else: 

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

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

424 

425 # Print error if unable to read the settings 

426 else: 

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

428 "been empty, located in a different path, or be in another format than" 

429 " INI. Default Settings values be used.") 

430 

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

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

433 # Write to latest settings file 

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

435 

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

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

438 # Create needed directories if they don't exist 

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

440 slurm_extra_section_options = None 

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

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

443 slurm_extra_section_options = {} 

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

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

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

447 self.__settings.remove_section("slurm_extra") 

448 # We do not write None values 

449 removed = [] 

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

451 for option in self.__settings[section]: 

452 try: 

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

454 del self.__settings[section][option] 

455 removed.append((section, option)) 

456 except Exception: 

457 pass 

458 # Write the settings to file 

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

460 self.__settings.write(settings_file) 

461 # Rebuild slurm extra if needed 

462 if slurm_extra_section_options is not None: 

463 self.__settings.add_section("slurm_extra") 

464 for key in slurm_extra_section_options: 

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

466 # Rebuild None if needed 

467 for section, option in removed: 

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

469 

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

471 if section not in self.__settings: 

472 self.__settings[section] = {} 

473 

474 @staticmethod 

475 def __check_setting_state(current_state: SettingState, 

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

477 change_setting_ok = True 

478 

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

480 change_setting_ok = False 

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

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

483 elif (current_state == SettingState.CMD_LINE 

484 and new_state == SettingState.DEFAULT): 

485 change_setting_ok = False 

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

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

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

489 change_setting_ok = False 

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

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

492 

493 return change_setting_ok 

494 

495 # General settings ### 

496 def set_general_sparkle_objectives( 

497 self: Settings, 

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

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

500 """Set the sparkle objective.""" 

501 section = "general" 

502 name = "objective" 

503 if value is not None and self.__check_setting_state( 

504 self.__general_sparkle_objective_set, origin, name): 

505 if isinstance(value, list): 

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

507 else: 

508 value = str(value) 

509 # Append standard Sparkle Objectives 

510 if "status" not in value: 

511 value += ",status" 

512 if "cpu_time" not in value: 

513 value += ",cpu_time" 

514 if "wall_time" not in value: 

515 value += ",wall_time" 

516 if "memory" not in value: 

517 value += ",memory" 

518 self.__init_section(section) 

519 self.__general_sparkle_objective_set = origin 

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

521 

522 def get_general_sparkle_objectives(self: Settings) -> list[SparkleObjective]: 

523 """Return the performance measure.""" 

524 if self.__general_sparkle_objective_set == SettingState.NOT_SET: 

525 self.set_general_sparkle_objectives() 

526 

527 return [resolve_objective(obj) 

528 for obj in self.__settings["general"]["objective"].split(",")] 

529 

530 def set_general_sparkle_configurator( 

531 self: Settings, 

532 value: str = DEFAULT_general_sparkle_configurator, 

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

534 """Set the Sparkle configurator.""" 

535 section = "general" 

536 name = "configurator" 

537 if value is not None and self.__check_setting_state( 

538 self.__general_sparkle_configurator_set, origin, name): 

539 self.__init_section(section) 

540 self.__general_sparkle_configurator_set = origin 

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

542 

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

544 """Return the configurator init method.""" 

545 if self.__general_sparkle_configurator_set == SettingState.NOT_SET: 

546 self.set_general_sparkle_configurator() 

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

548 if (self.__general_sparkle_configurator is None 

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

550 configurator_subclass =\ 

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

552 if configurator_subclass is not None: 

553 self.__general_sparkle_configurator = configurator_subclass( 

554 base_dir=Path(), 

555 output_path=Settings.DEFAULT_configuration_output_raw) 

556 else: 

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

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

559 "Configurator not set.") 

560 return self.__general_sparkle_configurator 

561 

562 def set_general_sparkle_selector( 

563 self: Settings, 

564 value: Path = DEFAULT_general_sparkle_selector, 

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

566 """Set the Sparkle selector.""" 

567 section = "general" 

568 name = "selector" 

569 if value is not None and self.__check_setting_state( 

570 self.__general_sparkle_selector_set, origin, name): 

571 self.__init_section(section) 

572 self.__general_sparkle_selector_set = origin 

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

574 

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

576 """Return the selector init method.""" 

577 if self.__general_sparkle_selector_set == SettingState.NOT_SET: 

578 self.set_general_sparkle_selector() 

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

580 self.DEFAULT_selection_output_raw) 

581 

582 def set_general_solution_verifier( 

583 self: Settings, value: str = DEFAULT_general_solution_verifier, 

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

585 """Set the solution verifier to use.""" 

586 section = "general" 

587 name = "solution_verifier" 

588 

589 if value is not None and self.__check_setting_state( 

590 self.__general_solution_verifier_set, origin, name): 

591 self.__init_section(section) 

592 self.__general_solution_verifier_set = origin 

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

594 

595 def get_general_solution_verifier(self: Settings) -> object: 

596 """Return the solution verifier to use.""" 

597 if self.__general_solution_verifier_set == SettingState.NOT_SET: 

598 self.set_general_solution_verifier() 

599 name = self.__settings["general"]["solution_verifier"].lower() 

600 if name == str(SATVerifier()).lower(): 

601 return SATVerifier() 

602 return None 

603 

604 def set_general_target_cutoff_time( 

605 self: Settings, value: int = DEFAULT_general_target_cutoff_time, 

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

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

608 section = "general" 

609 name = "target_cutoff_time" 

610 

611 if value is not None and self.__check_setting_state( 

612 self.__general_target_cutoff_time_set, origin, name): 

613 self.__init_section(section) 

614 self.__general_target_cutoff_time_set = origin 

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

616 

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

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

619 if self.__general_target_cutoff_time_set == SettingState.NOT_SET: 

620 self.set_general_target_cutoff_time() 

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

622 

623 def set_general_extractor_cutoff_time( 

624 self: Settings, value: int = DEFAULT_general_extractor_cutoff_time, 

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

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

627 section = "general" 

628 name = "extractor_cutoff_time" 

629 

630 if value is not None and self.__check_setting_state( 

631 self.__general_extractor_cutoff_time_set, origin, name): 

632 self.__init_section(section) 

633 self.__general_extractor_cutoff_time_set = origin 

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

635 

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

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

638 if self.__general_extractor_cutoff_time_set == SettingState.NOT_SET: 

639 self.set_general_extractor_cutoff_time() 

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

641 

642 def set_number_of_jobs_in_parallel( 

643 self: Settings, value: int = DEFAULT_number_of_jobs_in_parallel, 

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

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

646 section = "slurm" 

647 name = "number_of_jobs_in_parallel" 

648 

649 if value is not None and self.__check_setting_state( 

650 self.__number_of_jobs_in_parallel_set, origin, name): 

651 self.__init_section(section) 

652 self.__number_of_jobs_in_parallel_set = origin 

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

654 

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

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

657 if self.__number_of_jobs_in_parallel_set == SettingState.NOT_SET: 

658 self.set_number_of_jobs_in_parallel() 

659 

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

661 

662 def set_general_verbosity( 

663 self: Settings, value: VerbosityLevel = DEFAULT_general_verbosity, 

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

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

666 section = "general" 

667 name = "verbosity" 

668 

669 if value is not None and self.__check_setting_state( 

670 self.__general_verbosity_set, origin, name): 

671 self.__init_section(section) 

672 self.__general_verbosity_set = origin 

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

674 

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

676 """Return the general verbosity.""" 

677 if self.__general_verbosity_set == SettingState.NOT_SET: 

678 self.set_general_verbosity() 

679 

680 return VerbosityLevel.from_string( 

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

682 

683 def set_general_check_interval( 

684 self: Settings, 

685 value: int = DEFAULT_general_check_interval, 

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

687 """Set the general check interval.""" 

688 section = "general" 

689 name = "check_interval" 

690 

691 if value is not None and self.__check_setting_state( 

692 self.__general_check_interval_set, origin, name): 

693 self.__init_section(section) 

694 self.__general_check_interval_set = origin 

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

696 

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

698 """Return the general check interval.""" 

699 if self.__general_check_interval_set == SettingState.NOT_SET: 

700 self.set_general_check_interval() 

701 

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

703 

704 # Configuration settings General ### 

705 

706 def get_configurator_settings(self: Settings, 

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

708 """Return the configurator settings.""" 

709 configurator_settings = { 

710 "number_of_runs": self.get_configurator_number_of_runs(), 

711 "solver_calls": self.get_configurator_solver_calls(), 

712 "cutoff_time": self.get_general_target_cutoff_time(), 

713 "max_iterations": self.get_configurator_max_iterations() 

714 } 

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

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

717 if configurator_name == cim.SMAC2.__name__: 

718 # Return all settings from the SMAC2 section 

719 configurator_settings.update({ 

720 "cpu_time": self.get_smac2_cpu_time(), 

721 "wallclock_time": self.get_smac2_wallclock_time(), 

722 "target_cutoff_length": self.get_smac2_target_cutoff_length(), 

723 "use_cpu_time_in_tunertime": self.get_smac2_use_cpu_time_in_tunertime(), 

724 "max_iterations": self.get_smac2_max_iterations() 

725 or configurator_settings["max_iterations"], 

726 }) 

727 if configurator_name == cim.IRACE.__name__: 

728 # Return all settings from the IRACE section 

729 configurator_settings.update({ 

730 "solver_calls": self.get_irace_max_experiments(), 

731 "max_time": self.get_irace_max_time(), 

732 "first_test": self.get_irace_first_test(), 

733 "mu": self.get_irace_mu(), 

734 "max_iterations": self.get_irace_max_iterations() 

735 or configurator_settings["max_iterations"], 

736 }) 

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

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

739 configurator_settings["solver_calls"] =\ 

740 self.get_configurator_solver_calls() 

741 return configurator_settings 

742 

743 def set_configurator_solver_calls( 

744 self: Settings, value: int = DEFAULT_configurator_solver_calls, 

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

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

747 section = "configuration" 

748 name = "solver_calls" 

749 

750 if value is not None and self.__check_setting_state( 

751 self.__config_solver_calls_set, origin, name): 

752 self.__init_section(section) 

753 self.__config_solver_calls_set = origin 

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

755 

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

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

758 if self.__config_solver_calls_set == SettingState.NOT_SET: 

759 self.set_configurator_solver_calls() 

760 

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

762 

763 def set_configurator_number_of_runs( 

764 self: Settings, value: int = DEFAULT_configurator_number_of_runs, 

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

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

767 section = "configuration" 

768 name = "number_of_runs" 

769 

770 if value is not None and self.__check_setting_state( 

771 self.__config_number_of_runs_set, origin, name): 

772 self.__init_section(section) 

773 self.__config_number_of_runs_set = origin 

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

775 

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

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

778 if self.__config_number_of_runs_set == SettingState.NOT_SET: 

779 self.set_configurator_number_of_runs() 

780 

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

782 

783 def set_configurator_max_iterations( 

784 self: Settings, value: int = DEFAULT_configurator_maximum_iterations, 

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

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

787 section = "configuration" 

788 name = "max_iterations" 

789 

790 if self.__check_setting_state( 

791 self.__config_max_iterations_set, origin, name): 

792 self.__init_section(section) 

793 self.__config_max_iterations_set = origin 

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

795 

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

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

798 if self.__config_max_iterations_set == SettingState.NOT_SET: 

799 self.set_configurator_max_iterations() 

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

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

802 

803 # Configuration: SMAC specific settings ### 

804 

805 def set_smac2_wallclock_time( 

806 self: Settings, value: int = DEFAULT_smac2_wallclock_time, 

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

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

809 section = "smac2" 

810 name = "wallclock_time" 

811 

812 if self.__check_setting_state( 

813 self.__smac2_wallclock_time_set, origin, name): 

814 self.__init_section(section) 

815 self.__smac2_wallclock_time_set = origin 

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

817 

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

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

820 if self.__smac2_wallclock_time_set == SettingState.NOT_SET: 

821 self.set_smac2_wallclock_time() 

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

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

824 

825 def set_smac2_cpu_time( 

826 self: Settings, value: int = DEFAULT_smac2_cpu_time, 

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

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

829 section = "smac2" 

830 name = "cpu_time" 

831 

832 if self.__check_setting_state( 

833 self.__smac2_cpu_time_set, origin, name): 

834 self.__init_section(section) 

835 self.__smac2_cpu_time_set = origin 

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

837 

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

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

840 if self.__smac2_cpu_time_set == SettingState.NOT_SET: 

841 self.set_smac2_cpu_time() 

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

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

844 

845 def set_smac2_target_cutoff_length( 

846 self: Settings, value: str = DEFAULT_smac2_target_cutoff_length, 

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

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

849 section = "smac2" 

850 name = "target_cutoff_length" 

851 

852 if value is not None and self.__check_setting_state( 

853 self.__smac2_target_cutoff_length_set, origin, name): 

854 self.__init_section(section) 

855 self.__smac2_target_cutoff_length_set = origin 

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

857 

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

859 """Return the target algorithm cutoff length. 

860 

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

862 

863 Returns: 

864 The target algorithm cutoff length. 

865 """ 

866 if self.__smac2_target_cutoff_length_set == SettingState.NOT_SET: 

867 self.set_smac2_target_cutoff_length() 

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

869 

870 def set_smac2_use_cpu_time_in_tunertime( 

871 self: Settings, value: bool = DEFAULT_smac2_use_cpu_time_in_tunertime, 

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

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

874 section = "smac2" 

875 name = "use_cpu_time_in_tunertime" 

876 

877 if self.__check_setting_state( 

878 self.__smac2_use_cpu_time_in_tunertime_set, origin, name): 

879 self.__init_section(section) 

880 self.__smac2_use_cpu_time_in_tunertime_set = origin 

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

882 

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

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

885 if self.__smac2_use_cpu_time_in_tunertime_set == SettingState.NOT_SET: 

886 self.set_smac2_use_cpu_time_in_tunertime() 

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

888 

889 def set_smac2_max_iterations( 

890 self: Settings, value: int = DEFAULT_smac2_max_iterations, 

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

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

893 section = "smac2" 

894 name = "max_iterations" 

895 

896 if self.__check_setting_state( 

897 self.__smac2_max_iterations_set, origin, name): 

898 self.__init_section(section) 

899 self.__smac2_max_iterations_set = origin 

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

901 

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

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

904 if self.__smac2_max_iterations_set == SettingState.NOT_SET: 

905 self.set_smac2_max_iterations() 

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

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

908 

909 # Configuration: IRACE specific settings ### 

910 

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

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

913 if self.__irace_max_time_set == SettingState.NOT_SET: 

914 self.set_irace_max_time() 

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

916 

917 def set_irace_max_time( 

918 self: Settings, value: int = DEFAULT_irace_max_time, 

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

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

921 section = "irace" 

922 name = "max_time" 

923 

924 if value is not None and self.__check_setting_state( 

925 self.__irace_max_time_set, origin, name): 

926 self.__init_section(section) 

927 self.__irace_max_time_set = origin 

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

929 

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

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

932 if self.__irace_max_experiments_set == SettingState.NOT_SET: 

933 self.set_irace_max_experiments() 

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

935 

936 def set_irace_max_experiments( 

937 self: Settings, value: int = DEFAULT_irace_max_experiments, 

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

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

940 section = "irace" 

941 name = "max_experiments" 

942 

943 if value is not None and self.__check_setting_state( 

944 self.__irace_max_experiments_set, origin, name): 

945 self.__init_section(section) 

946 self.__irace_max_experiments_set = origin 

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

948 

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

950 """Return the first test for IRACE. 

951 

952 Specifies how many instances are evaluated before the first 

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

954 """ 

955 if self.__irace_first_test_set == SettingState.NOT_SET: 

956 self.set_irace_first_test() 

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

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

959 

960 def set_irace_first_test( 

961 self: Settings, value: int = DEFAULT_irace_first_test, 

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

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

964 section = "irace" 

965 name = "first_test" 

966 

967 if self.__check_setting_state( 

968 self.__irace_first_test_set, origin, name): 

969 self.__init_section(section) 

970 self.__irace_first_test_set = origin 

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

972 

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

974 """Return the mu for IRACE. 

975 

976 Parameter used to define the number of configurations sampled and 

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

978 """ 

979 if self.__irace_mu_set == SettingState.NOT_SET: 

980 self.set_irace_mu() 

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

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

983 

984 def set_irace_mu( 

985 self: Settings, value: int = DEFAULT_irace_mu, 

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

987 """Set the mu for IRACE.""" 

988 section = "irace" 

989 name = "mu" 

990 

991 if self.__check_setting_state( 

992 self.__irace_mu_set, origin, name): 

993 self.__init_section(section) 

994 self.__irace_mu_set = origin 

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

996 

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

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

999 if self.__irace_max_iterations_set == SettingState.NOT_SET: 

1000 self.set_irace_max_iterations() 

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

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

1003 

1004 def set_irace_max_iterations( 

1005 self: Settings, value: int = DEFAULT_irace_max_iterations, 

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

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

1008 

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

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

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

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

1013 non-fixed parameters to be tuned. 

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

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

1016 """ 

1017 section = "irace" 

1018 name = "max_iterations" 

1019 

1020 if self.__check_setting_state( 

1021 self.__irace_max_iterations_set, origin, name): 

1022 self.__init_section(section) 

1023 self.__irace_max_iterations_set = origin 

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

1025 

1026 # Slurm settings ### 

1027 

1028 def set_slurm_max_parallel_runs_per_node( 

1029 self: Settings, 

1030 value: int = DEFAULT_slurm_max_parallel_runs_per_node, 

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

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

1033 section = "slurm" 

1034 name = "max_parallel_runs_per_node" 

1035 

1036 if value is not None and self.__check_setting_state( 

1037 self.__slurm_max_parallel_runs_per_node_set, origin, name): 

1038 self.__init_section(section) 

1039 self.__slurm_max_parallel_runs_per_node_set = origin 

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

1041 

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

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

1044 if self.__slurm_max_parallel_runs_per_node_set == SettingState.NOT_SET: 

1045 self.set_slurm_max_parallel_runs_per_node() 

1046 

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

1048 

1049 # SLURM extra options 

1050 

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

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

1053 """Add additional Slurm options.""" 

1054 section = "slurm_extra" 

1055 

1056 current_state = (self.__slurm_extra_options_set[name] 

1057 if name in self.__slurm_extra_options_set 

1058 else SettingState.NOT_SET) 

1059 

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

1061 self.__init_section(section) 

1062 self.__slurm_extra_options_set[name] = origin 

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

1064 

1065 def get_slurm_extra_options(self: Settings, 

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

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

1068 section = "slurm_extra" 

1069 options = dict() 

1070 

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

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

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

1074 if as_args: 

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

1076 return options 

1077 

1078 # Ablation settings ### 

1079 

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

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

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

1083 section = "ablation" 

1084 name = "racing" 

1085 

1086 if value is not None and self.__check_setting_state( 

1087 self.__ablation_racing_flag_set, origin, name): 

1088 self.__init_section(section) 

1089 self.__ablation_racing_flag_set = origin 

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

1091 

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

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

1094 if self.__ablation_racing_flag_set == SettingState.NOT_SET: 

1095 self.set_ablation_racing_flag() 

1096 

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

1098 

1099 # Parallel Portfolio settings 

1100 

1101 def set_parallel_portfolio_check_interval( 

1102 self: Settings, 

1103 value: int = DEFAULT_parallel_portfolio_check_interval, 

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

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

1106 section = "parallel_portfolio" 

1107 name = "check_interval" 

1108 

1109 if value is not None and self.__check_setting_state( 

1110 self.__parallel_portfolio_check_interval_set, origin, name): 

1111 self.__init_section(section) 

1112 self.__parallel_portfolio_check_interval_set = origin 

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

1114 

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

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

1117 if self.__parallel_portfolio_check_interval_set == SettingState.NOT_SET: 

1118 self.set_parallel_portfolio_check_interval() 

1119 

1120 return int( 

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

1122 

1123 def set_parallel_portfolio_number_of_seeds_per_solver( 

1124 self: Settings, 

1125 value: int = DEFAULT_parallel_portfolio_num_seeds_per_solver, 

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

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

1128 section = "parallel_portfolio" 

1129 name = "num_seeds_per_solver" 

1130 

1131 if value is not None and self.__check_setting_state( 

1132 self.__parallel_portfolio_num_seeds_per_solver_set, origin, name): 

1133 self.__init_section(section) 

1134 self.__parallel_portfolio_num_seeds_per_solver_set = origin 

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

1136 

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

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

1139 if self.__parallel_portfolio_num_seeds_per_solver_set == SettingState.NOT_SET: 

1140 self.set_parallel_portfolio_number_of_seeds_per_solver() 

1141 

1142 return int( 

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

1144 

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

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

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

1148 section = "general" 

1149 name = "run_on" 

1150 

1151 if value is not None and self.__check_setting_state( 

1152 self.__run_on_set, origin, name): 

1153 self.__init_section(section) 

1154 self.__run_on_set = origin 

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

1156 

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

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

1159 if self.__run_on_set == SettingState.NOT_SET: 

1160 self.set_run_on() 

1161 

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

1163 

1164 @staticmethod 

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

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

1167 

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

1169 

1170 Args: 

1171 cur_settings: The current settings 

1172 prev_settings: The previous settings 

1173 

1174 Returns: 

1175 True iff there are no changes. 

1176 """ 

1177 cur_dict = cur_settings.__settings._sections 

1178 prev_dict = prev_settings.__settings._sections 

1179 

1180 cur_sections_set = set(cur_dict.keys()) 

1181 prev_sections_set = set(prev_dict.keys()) 

1182 sections_removed = prev_sections_set - cur_sections_set 

1183 if sections_removed: 

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

1185 for section in sections_removed: 

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

1187 

1188 sections_added = cur_sections_set - prev_sections_set 

1189 if sections_added: 

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

1191 for section in sections_added: 

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

1193 

1194 sections_remained = cur_sections_set & prev_sections_set 

1195 option_changed = False 

1196 for section in sections_remained: 

1197 printed_section = False 

1198 names = set(cur_dict[section].keys()) | set(prev_dict[section].keys()) 

1199 for name in names: 

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

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

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

1203 

1204 # If cur val is None, it is default 

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

1206 # Have we printed the initial warning? 

1207 if not option_changed: 

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

1209 option_changed = True 

1210 

1211 # do we have yet to print the section? 

1212 if not printed_section: 

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

1214 printed_section = True 

1215 

1216 # print actual change 

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

1218 

1219 return not (sections_removed or sections_added or option_changed)