Coverage for sparkle/CLI/help/argparse_custom.py: 95%

102 statements  

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

1"""Custom helper class and functions to process CLI arguments with argparse.""" 

2 

3from __future__ import annotations 

4import argparse 

5import enum 

6from pathlib import Path 

7from typing import Any 

8 

9from runrunner.base import Runner 

10 

11from sparkle.platform.settings_objects import SettingState, Settings 

12 

13 

14class SetByUser(argparse.Action): 

15 """Possible action to execute for CLI argument.""" 

16 

17 def __call__(self: SetByUser, parser: argparse.ArgumentParser, 

18 namespace: argparse.Namespace, values: str, option_string: str = None)\ 

19 -> None: 

20 """Set attributes when called.""" 

21 setattr(namespace, self.dest, values) 

22 setattr(namespace, self.dest + "_nondefault", True) 

23 

24 

25# Taken from https://stackoverflow.com/a/60750535 

26class EnumAction(argparse.Action): 

27 """Argparse action for handling Enums.""" 

28 def __init__(self: EnumAction, **kwargs: str) -> None: 

29 """Initialise the EnumAction.""" 

30 # Pop off the type value 

31 enum_type = kwargs.pop("type", None) 

32 

33 # Ensure an Enum subclass is provided 

34 if enum_type is None: 

35 raise ValueError("type must be assigned an Enum when using EnumAction") 

36 if not issubclass(enum_type, enum.Enum): 

37 raise TypeError("type must be an Enum when using EnumAction") 

38 

39 # Generate choices from the Enum 

40 kwargs.setdefault("choices", tuple(e.value for e in enum_type)) 

41 

42 super(EnumAction, self).__init__(**kwargs) 

43 

44 self._enum = enum_type 

45 

46 def __call__(self: EnumAction, parser: argparse.ArgumentParser, 

47 namespace: argparse.Namespace, values: str, option_string: str = None) \ 

48 -> None: 

49 """Converts value back to Enum.""" 

50 value = self._enum(values) 

51 setattr(namespace, self.dest, value) 

52 

53 

54def user_set_state(args: argparse.Namespace, arg_name: str) -> SettingState: 

55 """Return the SettingState of an argument.""" 

56 if hasattr(args, arg_name + "_nondefault"): 

57 return SettingState.CMD_LINE 

58 else: 

59 return SettingState.DEFAULT 

60 

61 

62def set_by_user(args: argparse.Namespace, arg_name: str) -> bool: 

63 """Return whether an argument was set through the CLI by the user or not.""" 

64 return hasattr(args, arg_name + "_nondefault") 

65 

66 

67class ArgumentContainer(): 

68 """Helper class for more convenient argument packaging and access.""" 

69 def __init__(self: ArgumentContainer, names: list[str], kwargs: dict[str, Any])\ 

70 -> None: 

71 """Create an ArgumentContainer. 

72 

73 Args: 

74 names: List of names for the contained argument. For positional arguments, 

75 this will contain a single string. For positional arguments this will 

76 typically contain two, the first one starting with '-' and the second one 

77 starting with '--'. 

78 kwargs: Keyword arguments needed by the method parser.add_argument which adds 

79 the contained argument to a parser. 

80 """ 

81 self.names = names 

82 self.kwargs = kwargs 

83 

84 

85ActualMarginalContributionArgument = \ 

86 ArgumentContainer(names=["--actual"], 

87 kwargs={"action": "store_true", 

88 "help": "compute the marginal contribution " 

89 "for the actual selector"}) 

90 

91AllJobsArgument = \ 

92 ArgumentContainer(names=["--all"], 

93 kwargs={"action": "store_true", 

94 "help": "use all known job ID(s) for the command"}) 

95 

96BestConfigurationArgument = \ 

97 ArgumentContainer(names=["--best-configuration"], 

98 kwargs={"required": False, 

99 "nargs": "?", 

100 "type": Path, 

101 "const": True, 

102 "default": False, 

103 "help": "Paths to instance(s) or instanceset(s) over " 

104 "which to determine the best configuration. If " 

105 "empty, all known instances are used."}) 

106 

107CancelJobsArgument = \ 

108 ArgumentContainer(names=["--cancel"], 

109 kwargs={"action": "store_true", 

110 "help": "cancel the job(s) with the given ID(s)"}) 

111 

112CleanupArgumentAll = \ 

113 ArgumentContainer(names=["--all"], 

114 kwargs={"action": "store_true", 

115 "help": "clean all output files"}) 

116 

117CleanupArgumentRemove = \ 

118 ArgumentContainer(names=["--remove"], 

119 kwargs={"action": "store_true", 

120 "help": "remove all files in the platform, including " 

121 "user data such as InstanceSets and Solvers"}) 

122 

123CleanUpPerformanceDataArgument = \ 

124 ArgumentContainer(names=["--performance-data"], 

125 kwargs={"action": "store_true", 

126 "help": "clean performance data from empty lines"}) 

127 

128ConfigurationArgument = \ 

129 ArgumentContainer(names=["--configuration"], 

130 kwargs={"required": False, 

131 "type": int, 

132 "help": "The run index of which configuration to use"}) 

133 

134ConfiguratorArgument = ArgumentContainer(names=["--configurator"], 

135 kwargs={"type": str, 

136 "help": "name of the configurator"}) 

137 

138CPUTimeArgument = \ 

139 ArgumentContainer(names=["--cpu-time"], 

140 kwargs={"type": int, 

141 "help": "configuration budget per configurator run in " 

142 "seconds (cpu)"}) 

143 

144CutOffTimeArgument = \ 

145 ArgumentContainer(names=["--cutoff-time"], 

146 kwargs={"type": int, 

147 "help": "The duration the portfolio will run before the " 

148 "solvers within the portfolio will be stopped " 

149 "(default: " 

150 f"{Settings.DEFAULT_general_target_cutoff_time})"}) 

151 

152DeterministicArgument =\ 

153 ArgumentContainer(names=["--deterministic"], 

154 kwargs={"action": "store_true", 

155 "help": "Flag indicating the solver is deterministic"}) 

156 

157DownloadExamplesArgument =\ 

158 ArgumentContainer(names=["--download-examples"], 

159 kwargs={"action": "store_true", 

160 "default": False, 

161 "required": False, 

162 "help": "Download the Examples into the directory."}) 

163 

164ExtractorPathArgument = ArgumentContainer(names=["extractor_path"], 

165 kwargs={"metavar": "extractor-path", 

166 "type": str, 

167 "help": "path or nickname of the " 

168 "feature extractor" 

169 }) 

170 

171GenerateJSONArgument = ArgumentContainer(names=["--only-json"], 

172 kwargs={"required": False, 

173 "default": False, 

174 "type": bool, 

175 "help": "if set to True, only generate " 

176 "machine readable output" 

177 }) 

178 

179InstancePathPositional = ArgumentContainer(names=["instance_path"], 

180 kwargs={"type": Path, 

181 "help": "Path to an instance (set)"}) 

182 

183InstanceSetPathsArgument =\ 

184 ArgumentContainer(names=["--instance-path", "--instance-set-path", 

185 "--instance", "--instance-set", 

186 "--instances", "--instance-sets", 

187 "--instance-paths", "--instance-set-paths"], 

188 kwargs={"required": False, 

189 "nargs": "+", 

190 "type": Path, 

191 "help": "Path to an instance (set)"}) 

192 

193InstanceSetTestArgument = \ 

194 ArgumentContainer(names=["--instance-set-test"], 

195 kwargs={"required": False, 

196 "type": Path, 

197 "help": "path to test instance set (only for validating)"}) 

198 

199InstanceSetTrainArgument = \ 

200 ArgumentContainer(names=["--instance-set-train"], 

201 kwargs={"required": True, 

202 "type": Path, 

203 "help": "path to training instance set"}) 

204 

205InstanceSetTestAblationArgument = \ 

206 ArgumentContainer(names=["--instance-set-test"], 

207 kwargs={"required": False, 

208 "type": str, 

209 "help": "path to test instance set"}) 

210 

211InstanceSetTrainAblationArgument = \ 

212 ArgumentContainer(names=["--instance-set-train"], 

213 kwargs={"required": False, 

214 "type": str, 

215 "help": "path to training instance set"}) 

216 

217InstanceSetTestReportArgument = \ 

218 ArgumentContainer(names=["--instance-set-test"], 

219 kwargs={"required": False, 

220 "type": str, 

221 "help": "path to testing instance set included in " 

222 "Sparkle for an algorithm configuration report"}) 

223 

224InstanceSetTrainReportArgument = \ 

225 ArgumentContainer(names=["--instance-set-train"], 

226 kwargs={"required": False, 

227 "type": str, 

228 "help": "path to training instance set included in " 

229 "Sparkle for an algorithm configuration report"}) 

230 

231InstancesPathArgument = ArgumentContainer(names=["instances_path"], 

232 kwargs={"metavar": "instances-path", 

233 "type": str, 

234 "help": "path to the instance set"}) 

235 

236InstancesPathRemoveArgument = \ 

237 ArgumentContainer(names=["instances_path"], 

238 kwargs={"metavar": "instances-path", 

239 "type": str, 

240 "help": "path to or nickname of the instance set"}) 

241 

242JobIDsArgument = ArgumentContainer(names=["--job-ids"], 

243 kwargs={"required": False, 

244 "nargs": "+", 

245 "type": str, 

246 "default": None, 

247 "help": "job ID(s) to use for the command"}) 

248 

249NicknameFeatureExtractorArgument = \ 

250 ArgumentContainer(names=["--nickname"], 

251 kwargs={"type": str, 

252 "help": "set a nickname for the feature extractor"}) 

253 

254NicknameInstanceSetArgument = \ 

255 ArgumentContainer(names=["--nickname"], 

256 kwargs={"type": str, 

257 "help": "set a nickname for the instance set"}) 

258 

259NicknamePortfolioArgument = \ 

260 ArgumentContainer(names=["--portfolio-name"], 

261 kwargs={"type": Path, 

262 "help": "Specify a name of the portfolio. " 

263 "If none is given, one will be generated."}) 

264 

265NicknameSolverArgument = \ 

266 ArgumentContainer(names=["--nickname"], 

267 kwargs={"type": str, 

268 "help": "set a nickname for the solver"}) 

269 

270NoAblationReportArgument = ArgumentContainer(names=["--no-ablation"], 

271 kwargs={"required": False, 

272 "dest": "flag_ablation", 

273 "default": True, 

274 "const": False, 

275 "nargs": "?", 

276 "help": "turn off reporting on " 

277 "ablation for an algorithm " 

278 "configuration report"}) 

279 

280NoCopyArgument = ArgumentContainer(names=["--no-copy"], 

281 kwargs={"action": "store_true", 

282 "required": False, 

283 "help": "do not copy the source directory to " 

284 "the platform directory, but create a" 

285 " symbolic link instead"}) 

286 

287NoSavePlatformArgument = ArgumentContainer(names=["--no-save"], 

288 kwargs={"action": "store_false", 

289 "default": True, 

290 "required": False, 

291 "help": "do not save the platform " 

292 "upon re-initialisation."}) 

293 

294NumberOfRunsConfigurationArgument = \ 

295 ArgumentContainer(names=["--number-of-runs"], 

296 kwargs={"type": int, 

297 "help": "number of configuration runs to execute"}) 

298 

299NumberOfRunsAblationArgument = \ 

300 ArgumentContainer(names=["--number-of-runs"], 

301 kwargs={"type": int, 

302 "default": Settings.DEFAULT_configurator_number_of_runs, 

303 "action": SetByUser, 

304 "help": "Number of configuration runs to execute"}) 

305 

306PerfectSelectorMarginalContributionArgument =\ 

307 ArgumentContainer(names=["--perfect"], 

308 kwargs={"action": "store_true", 

309 "help": "compute the marginal contribution " 

310 "for the perfect selector"}) 

311 

312RacingArgument = ArgumentContainer(names=["--racing"], 

313 kwargs={"type": bool, 

314 "default": Settings. 

315 DEFAULT_ablation_racing, 

316 "action": SetByUser, 

317 "help": "Performs abaltion analysis with " 

318 "racing"}) 

319 

320RecomputeFeaturesArgument = \ 

321 ArgumentContainer(names=["--recompute"], 

322 kwargs={"action": "store_true", 

323 "help": "Re-run feature extractor for instances with " 

324 "previously computed features"}) 

325 

326RecomputeMarginalContributionArgument = \ 

327 ArgumentContainer(names=["--recompute"], 

328 kwargs={"action": "store_true", 

329 "help": "force marginal contribution to be recomputed even" 

330 " when it already exists in file for the current " 

331 "selector"}) 

332 

333RecomputeMarginalContributionForSelectorArgument = \ 

334 ArgumentContainer(names=["--recompute-marginal-contribution"], 

335 kwargs={"action": "store_true", 

336 "help": "force marginal contribution to be recomputed even" 

337 " when it already exists in file for the current " 

338 "selector"}) 

339 

340RecomputePortfolioSelectorArgument = \ 

341 ArgumentContainer(names=["--recompute-portfolio-selector"], 

342 kwargs={"action": "store_true", 

343 "help": "force the construction of a new portfolio " 

344 "selector even when it already exists for the current " 

345 "feature and performance data. NOTE: This will also " 

346 "result in the computation of the marginal contributions " 

347 "of solvers to the new portfolio selector."}) 

348 

349RecomputeRunSolversArgument = \ 

350 ArgumentContainer(names=["--recompute"], 

351 kwargs={"action": "store_true", 

352 "help": "recompute the performance of all solvers on all " 

353 "instances"}) 

354 

355PerformanceDataJobsArgument = \ 

356 ArgumentContainer(names=["--performance-data-jobs"], 

357 kwargs={"action": "store_true", 

358 "help": "compute the remaining jobs in the Performance " 

359 "DataFrame"}) 

360 

361RebuildRunsolverArgument = \ 

362 ArgumentContainer(names=["--rebuild-runsolver"], 

363 kwargs={"action": "store_true", 

364 "required": False, 

365 "default": False, 

366 "help": "Clean the RunSolver executable and rebuild it."}) 

367 

368RunOnArgument = ArgumentContainer(names=["--run-on"], 

369 kwargs={"type": Runner, 

370 "choices": [Runner.LOCAL, 

371 Runner.SLURM], 

372 "action": EnumAction, 

373 "help": "On which computer or cluster " 

374 "environment to execute the " 

375 "calculation."}) 

376 

377SelectionReportArgument = \ 

378 ArgumentContainer(names=["--selection"], 

379 kwargs={"action": "store_true", 

380 "help": "set to generate a normal selection report"}) 

381 

382SelectorAblationArgument =\ 

383 ArgumentContainer(names=["--solver-ablation"], 

384 kwargs={"required": False, 

385 "action": "store_true", 

386 "help": "construct a selector for " 

387 "each solver ablation combination"}) 

388 

389SettingsFileArgument = \ 

390 ArgumentContainer(names=["--settings-file"], 

391 kwargs={"type": Path, 

392 "default": Settings.DEFAULT_settings_path, 

393 "action": SetByUser, 

394 "help": "Specify the settings file to use in case you want" 

395 " to use one other than the default"}) 

396 

397SkipChecksArgument = ArgumentContainer( 

398 names=["--skip-checks"], 

399 kwargs={"dest": "run_checks", 

400 "default": True, 

401 "action": "store_false", 

402 "help": "Checks the solver's functionality by testing it on an instance " 

403 "and the pcs file, when applicable."}) 

404 

405SnapshotArgument = ArgumentContainer(names=["snapshot_file_path"], 

406 kwargs={"metavar": "snapshot-file-path", 

407 "type": str, 

408 "help": "path to the snapshot file"}) 

409 

410SnapshotNameArgument = ArgumentContainer(names=["--name"], 

411 kwargs={"required": False, 

412 "type": str, 

413 "help": "name of the snapshot"}) 

414 

415SolverArgument = ArgumentContainer(names=["--solver"], 

416 kwargs={"required": True, 

417 "type": Path, 

418 "help": "path to solver"}) 

419 

420SolversArgument = ArgumentContainer(names=["--solvers", "--solver-paths", 

421 "--solver", "--solver-path"], 

422 kwargs={"required": False, 

423 "nargs": "+", 

424 "type": str, 

425 "help": "Specify the list of solvers to be " 

426 "used. If not specifed, all solvers " 

427 "known in Sparkle will be used."}) 

428 

429SolverCallsArgument = \ 

430 ArgumentContainer(names=["--solver-calls"], 

431 kwargs={"type": int, 

432 "help": "number of solver calls to execute"}) 

433 

434SolverSeedsArgument = \ 

435 ArgumentContainer(names=["--solver-seeds"], 

436 kwargs={"type": int, 

437 "help": "number of random seeds per solver to execute"}) 

438 

439SolverRemoveArgument = \ 

440 ArgumentContainer(names=["solver"], 

441 kwargs={"metavar": "solver", 

442 "type": str, 

443 "help": "name, path to or nickname of the solver"}) 

444 

445SolverPathArgument = ArgumentContainer(names=["solver_path"], 

446 kwargs={"metavar": "solver-path", 

447 "type": str, 

448 "help": "path to the solver"}) 

449 

450SolverReportArgument = ArgumentContainer(names=["--solver"], 

451 kwargs={"required": False, 

452 "type": str, 

453 "default": None, 

454 "help": "path to solver for an " 

455 "algorithm configuration report"}) 

456 

457TargetCutOffTimeArgument = \ 

458 ArgumentContainer(names=["--target-cutoff-time", "--solver-cutoff-time"], 

459 kwargs={"type": int, 

460 "default": Settings.DEFAULT_general_target_cutoff_time, 

461 "action": SetByUser, 

462 "help": "cutoff time per Solver run in seconds"}) 

463 

464TestCaseDirectoryArgument = \ 

465 ArgumentContainer(names=["--test-case-directory"], 

466 kwargs={"type": str, 

467 "default": None, 

468 "help": "Path to test case directory of an instance set " 

469 + "for a selection report"}) 

470 

471TestSetRunAllConfigurationArgument = \ 

472 ArgumentContainer(names=["--test-set-run-all-configurations"], 

473 kwargs={"required": False, 

474 "action": "store_true", 

475 "help": "run all found configurations on the test set"}) 

476 

477UseFeaturesArgument = ArgumentContainer(names=["--use-features"], 

478 kwargs={"required": False, 

479 "action": "store_true", 

480 "help": "use the training set's features" 

481 " for configuration"}) 

482 

483VerboseArgument = ArgumentContainer(names=["--verbose", "-v"], 

484 kwargs={"action": "store_true", 

485 "help": "output status in verbose mode"}) 

486 

487WallClockTimeArgument = \ 

488 ArgumentContainer(names=["--wallclock-time"], 

489 kwargs={"type": int, 

490 "help": "configuration budget per configurator run in " 

491 "seconds (wallclock)"}) 

492 

493SelectorTimeoutArgument = \ 

494 ArgumentContainer(names=["--selector-timeout"], 

495 kwargs={"type": int, 

496 "default": Settings.DEFAULT_portfolio_construction_timeout, 

497 "help": "Cuttoff time (in seconds) for the algorithm" 

498 "selector construction"}) 

499 

500SolutionVerifierArgument = \ 

501 ArgumentContainer(names=["--solution-verifier"], 

502 kwargs={"type": str, 

503 "default": None, 

504 "help": "the class name of the solution verifier to use " 

505 "for the Solver. If it is a Path, will resolve as " 

506 "a SolutionFileVerifier class with the specified " 

507 "Path instead."}) 

508 

509ObjectiveArgument = \ 

510 ArgumentContainer(names=["--objective"], 

511 kwargs={"type": str, 

512 "help": "the objective to use."}) 

513 

514ObjectivesArgument = \ 

515 ArgumentContainer(names=["--objectives"], 

516 kwargs={"type": str, 

517 "help": "the comma seperated objective(s) to use."})