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

104 statements  

« prev     ^ index     » next       coverage.py v7.9.1, created at 2025-07-01 13:21 +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 

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

30 """Initialise the EnumAction.""" 

31 # Pop off the type value 

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

33 

34 # Ensure an Enum subclass is provided 

35 if enum_type is None: 

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

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

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

39 

40 # Generate choices from the Enum 

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

42 

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

44 

45 self._enum = enum_type 

46 

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

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

49 -> None: 

50 """Converts value back to Enum.""" 

51 value = self._enum(values) 

52 setattr(namespace, self.dest, value) 

53 

54 

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

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

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

58 return SettingState.CMD_LINE 

59 else: 

60 return SettingState.DEFAULT 

61 

62 

63class ArgumentContainer(): 

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

65 

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

67 -> None: 

68 """Create an ArgumentContainer. 

69 

70 Args: 

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

72 this will typically contain two, the first one starting with '-' and the 

73 second one starting with '--'. 

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

75 the contained argument to a parser. 

76 """ 

77 self.names = names 

78 self.kwargs = kwargs 

79 

80 

81ActualMarginalContributionArgument = \ 

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

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

84 "help": "compute the marginal contribution " 

85 "for the actual selector"}) 

86 

87AllJobsArgument = \ 

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

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

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

91 

92AllSolverConfigurationArgument = \ 

93 ArgumentContainer(names=["--all-configurations"], 

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

95 "help": "use all known configurations for the command"}) 

96 

97AllConfigurationArgument = \ 

98 ArgumentContainer(names=["--all-configurations"], 

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

100 "help": "use all known Solver configurations"}) 

101 

102BestConfigurationArgument = \ 

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

104 kwargs={"required": False, 

105 "nargs": "?", 

106 "type": Path, 

107 "const": True, 

108 "default": False, 

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

110 "which to determine the best configuration. If " 

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

112 

113BestSolverConfigurationArgument = \ 

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

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

116 "help": "use the best configurations for the command"}) 

117 

118CancelJobsArgument = \ 

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

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

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

122 

123CheckTypeArgument = \ 

124 ArgumentContainer(names=["type"], 

125 kwargs={"choices": ["extractor", 

126 "feature-extractor", 

127 "solver", 

128 "instance-set", 

129 "Extractor" 

130 "Feature-Extractor", 

131 "Instance-Set", 

132 "Solver", 

133 "FeatureExtractor", 

134 "InstanceSet"], 

135 "help": "type of the object to check"}) 

136 

137CheckPathArgument = \ 

138 ArgumentContainer(names=["path"], 

139 kwargs={"type": Path, 

140 "help": "path to the object to check"}) 

141 

142CleanupArgumentAll = \ 

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

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

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

146 

147CleanupArgumentRemove = \ 

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

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

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

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

152 

153CleanUpPerformanceDataArgument = \ 

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

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

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

157 

158ConfigurationArgument = \ 

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

160 kwargs={"required": False, 

161 "type": str, 

162 "nargs": "+", 

163 "help": "The indices of which configurations to use"}) 

164 

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

166 kwargs={"type": str, 

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

168 

169CPUTimeArgument = \ 

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

171 kwargs={"type": int, 

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

173 "seconds (cpu)"}) 

174 

175CutOffTimeArgument = \ 

176 ArgumentContainer(names=["--cutoff-time", "--cutoff", "--timeout"], 

177 kwargs={"type": int, 

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

179 "solvers within the portfolio will be stopped " 

180 "(default: " 

181 f"{Settings.DEFAULT_general_solver_cutoff_time})"}) 

182 

183DefaultSolverConfigurationArgument = \ 

184 ArgumentContainer(names=["--default-configuration"], 

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

186 "help": "use the default configurations for the command"}) 

187 

188DeterministicArgument =\ 

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

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

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

192 

193DownloadExamplesArgument =\ 

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

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

196 "default": False, 

197 "required": False, 

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

199 

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

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

202 "type": str, 

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

204 "feature extractor" 

205 }) 

206 

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

208 kwargs={"required": False, 

209 "default": False, 

210 "type": bool, 

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

212 "machine readable output" 

213 }) 

214 

215InstancePathOptional =\ 

216 ArgumentContainer(names=["instance_path"], 

217 kwargs={"type": Path, 

218 "nargs": "?", 

219 "help": "Path to an instance to use for the command"}) 

220 

221InstanceSetPathsArgument =\ 

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

223 "--instance", "--instance-set", 

224 "--instances", "--instance-sets", 

225 "--instance-paths", "--instance-set-paths"], 

226 kwargs={"required": False, 

227 "nargs": "+", 

228 "type": Path, 

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

230 

231InstanceSetRequiredArgument = \ 

232 ArgumentContainer(names=["--instance", "--instance-set"], 

233 kwargs={"required": True, 

234 "type": Path, 

235 "help": "path to instance (set)"}) 

236 

237 

238InstanceSetTestArgument = \ 

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

240 kwargs={"required": False, 

241 "type": Path, 

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

243 

244InstanceSetTrainArgument = \ 

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

246 kwargs={"required": True, 

247 "type": Path, 

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

249 

250InstanceSetTestAblationArgument = \ 

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

252 kwargs={"required": False, 

253 "type": str, 

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

255 

256InstanceSetTrainOptionalArgument = \ 

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

258 kwargs={"required": False, 

259 "type": Path, 

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

261 

262InstanceSetsReportArgument = \ 

263 ArgumentContainer(names=["--instance-sets", "--instance-set"], 

264 kwargs={"required": False, 

265 "nargs": "+", 

266 "type": str, 

267 "help": "Instance Set(s) to use for the report. If not " 

268 "specified, all Instance Sets are used."}) 

269 

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

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

272 "type": str, 

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

274 

275InstancesPathRemoveArgument = \ 

276 ArgumentContainer(names=["instances_path"], 

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

278 "type": str, 

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

280 

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

282 kwargs={"required": False, 

283 "nargs": "+", 

284 "type": str, 

285 "default": None, 

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

287 

288NicknameFeatureExtractorArgument = \ 

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

290 kwargs={"type": str, 

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

292 

293NicknameInstanceSetArgument = \ 

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

295 kwargs={"type": str, 

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

297 

298NicknamePortfolioArgument = \ 

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

300 kwargs={"type": Path, 

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

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

303 

304NicknameSolverArgument = \ 

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

306 kwargs={"type": str, 

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

308 

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

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

311 "required": False, 

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

313 "the platform directory, but create a" 

314 " symbolic link instead"}) 

315 

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

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

318 "default": True, 

319 "required": False, 

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

321 "upon re-initialisation."}) 

322 

323NumberOfRunsConfigurationArgument = \ 

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

325 kwargs={"type": int, 

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

327 

328NumberOfRunsAblationArgument = \ 

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

330 kwargs={"type": int, 

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

332 

333PerfectSelectorMarginalContributionArgument =\ 

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

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

336 "help": "compute the marginal contribution " 

337 "for the perfect selector"}) 

338 

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

340 kwargs={"type": bool, 

341 "help": "Performs abaltion analysis with " 

342 "racing"}) 

343 

344RecomputeFeaturesArgument = \ 

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

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

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

348 "previously computed features"}) 

349 

350RecomputeMarginalContributionArgument = \ 

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

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

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

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

355 "selector"}) 

356 

357RecomputeMarginalContributionForSelectorArgument = \ 

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

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

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

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

362 "selector"}) 

363 

364RecomputePortfolioSelectorArgument = \ 

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

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

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

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

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

370 "result in the computation of the marginal contributions " 

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

372 

373RecomputeRunSolversArgument = \ 

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

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

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

377 "instances"}) 

378 

379PerformanceDataJobsArgument = \ 

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

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

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

383 "DataFrame"}) 

384 

385RebuildRunsolverArgument = \ 

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

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

388 "required": False, 

389 "default": False, 

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

391 

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

393 kwargs={"type": Runner, 

394 "choices": [Runner.LOCAL, 

395 Runner.SLURM], 

396 "action": EnumAction, 

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

398 "environment to execute the " 

399 "calculation."}) 

400 

401SeedArgument = ArgumentContainer(names=["--seed"], 

402 kwargs={"type": int, 

403 "help": "seed to use for the command"}) 

404 

405SelectionScenarioArgument = \ 

406 ArgumentContainer(names=["--selection-scenario"], 

407 kwargs={"required": True, 

408 "type": Path, 

409 "help": "the selection scenario to use"}) 

410 

411SelectorAblationArgument =\ 

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

413 kwargs={"required": False, 

414 "action": "store_true", 

415 "help": "construct a selector for " 

416 "each solver ablation combination"}) 

417 

418SettingsFileArgument = \ 

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

420 kwargs={"type": Path, 

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

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

423 

424SkipChecksArgument = ArgumentContainer( 

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

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

427 "default": True, 

428 "action": "store_false", 

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

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

431 

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

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

434 "type": str, 

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

436 

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

438 kwargs={"required": False, 

439 "type": str, 

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

441 

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

443 kwargs={"required": True, 

444 "type": Path, 

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

446 

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

448 "--solver", "--solver-path"], 

449 kwargs={"required": False, 

450 "nargs": "+", 

451 "type": str, 

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

453 "used. If not specifed, all solvers " 

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

455 

456SolverCallsArgument = \ 

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

458 kwargs={"type": int, 

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

460 

461SolverSeedsArgument = \ 

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

463 kwargs={"type": int, 

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

465 

466SolverRemoveArgument = \ 

467 ArgumentContainer(names=["solver"], 

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

469 "type": str, 

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

471 

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

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

474 "type": str, 

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

476 

477SolversReportArgument = ArgumentContainer( 

478 names=["--solvers", "--solver"], 

479 kwargs={"nargs": "+", 

480 "type": str, 

481 "default": None, 

482 "help": "Solver(s) to use for the report. If not specified, all solvers are " 

483 "included."}) 

484 

485SolverCutOffTimeArgument = \ 

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

487 kwargs={"type": int, 

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

489 

490TestSetRunAllConfigurationArgument = \ 

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

492 kwargs={"required": False, 

493 "action": "store_true", 

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

495 

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

497 kwargs={"required": False, 

498 "action": "store_true", 

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

500 " for configuration"}) 

501 

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

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

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

505 

506WallClockTimeArgument = \ 

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

508 kwargs={"type": int, 

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

510 "seconds (wallclock)"}) 

511 

512SolutionVerifierArgument = \ 

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

514 kwargs={"type": str, 

515 "default": None, 

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

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

518 "a SolutionFileVerifier class with the specified " 

519 "Path instead."}) 

520 

521ObjectiveArgument = \ 

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

523 kwargs={"type": str, 

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

525 

526ObjectivesArgument = \ 

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

528 kwargs={"type": str, 

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