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

99 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-05 14:48 +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 

85AblationArgument = ArgumentContainer(names=["--ablation"], 

86 kwargs={"required": False, 

87 "action": "store_true", 

88 "help": "run ablation after configuration"}) 

89 

90ActualMarginalContributionArgument = \ 

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

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

93 "help": "compute the marginal contribution " 

94 "for the actual selector"}) 

95 

96AllJobsArgument = \ 

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

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

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

100 

101AlsoConstructSelectorAndReportArgument = \ 

102 ArgumentContainer(names=["--also-construct-selector-and-report"], 

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

104 "help": "after running the solvers also construct the " 

105 "selector and generate the report"}) 

106 

107CleanupArgumentAll = \ 

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

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

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

111 

112CleanupArgumentRemove = \ 

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

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

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

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

117 

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

119 kwargs={"type": str, 

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

121 

122CPUTimeArgument = \ 

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

124 kwargs={"type": int, 

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

126 "seconds (cpu)"}) 

127 

128CutOffTimeArgument = \ 

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

130 kwargs={"type": int, 

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

132 "solvers within the portfolio will be stopped " 

133 "(default: " 

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

135 

136DeterministicArgument =\ 

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

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

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

140 

141DownloadExamplesArgument =\ 

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

143 kwargs={"action": argparse.BooleanOptionalAction, 

144 "default": False, 

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

146 

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

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

149 "type": str, 

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

151 "feature extractor" 

152 }) 

153 

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

155 kwargs={"required": False, 

156 "default": False, 

157 "type": bool, 

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

159 "machine readable output" 

160 }) 

161 

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

163 kwargs={"type": Path, 

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

165 

166InstancePath = ArgumentContainer(names=["--instance-path"], 

167 kwargs={"required": True, 

168 "type": Path, 

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

170 

171InstanceSetTestArgument = \ 

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

173 kwargs={"required": False, 

174 "type": Path, 

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

176 

177InstanceSetTrainArgument = \ 

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

179 kwargs={"required": True, 

180 "type": Path, 

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

182 

183InstanceSetTestAblationArgument = \ 

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

185 kwargs={"required": False, 

186 "type": str, 

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

188 

189InstanceSetTrainAblationArgument = \ 

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

191 kwargs={"required": False, 

192 "type": str, 

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

194 

195InstanceSetTestReportArgument = \ 

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

197 kwargs={"required": False, 

198 "type": str, 

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

200 "Sparkle for an algorithm configuration report"}) 

201 

202InstanceSetTrainReportArgument = \ 

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

204 kwargs={"required": False, 

205 "type": str, 

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

207 "Sparkle for an algorithm configuration report"}) 

208 

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

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

211 "type": str, 

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

213 

214InstancesPathRemoveArgument = \ 

215 ArgumentContainer(names=["instances_path"], 

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

217 "type": str, 

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

219 

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

221 kwargs={"required": False, 

222 "nargs": "+", 

223 "type": str, 

224 "default": None, 

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

226 

227NicknameFeatureExtractorArgument = \ 

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

229 kwargs={"type": str, 

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

231 

232NicknameInstanceSetArgument = \ 

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

234 kwargs={"type": str, 

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

236 

237NicknamePortfolioArgument = \ 

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

239 kwargs={"type": Path, 

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

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

242 

243NicknameSolverArgument = \ 

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

245 kwargs={"type": str, 

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

247 

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

249 kwargs={"required": False, 

250 "dest": "flag_ablation", 

251 "default": True, 

252 "const": False, 

253 "nargs": "?", 

254 "help": "turn off reporting on " 

255 "ablation for an algorithm " 

256 "configuration report"}) 

257 

258NumberOfRunsConfigurationArgument = \ 

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

260 kwargs={"type": int, 

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

262 

263NumberOfRunsAblationArgument = \ 

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

265 kwargs={"type": int, 

266 "default": Settings.DEFAULT_configurator_number_of_runs, 

267 "action": SetByUser, 

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

269 

270PerfectSelectorMarginalContributionArgument =\ 

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

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

273 "help": "compute the marginal contribution " 

274 "for the perfect selector"}) 

275 

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

277 kwargs={"type": bool, 

278 "default": Settings. 

279 DEFAULT_ablation_racing, 

280 "action": SetByUser, 

281 "help": "Performs abaltion analysis with " 

282 "racing"}) 

283 

284RecomputeFeaturesArgument = \ 

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

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

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

288 "previously computed features"}) 

289 

290RecomputeMarginalContributionArgument = \ 

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

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

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

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

295 "selector"}) 

296 

297RecomputeMarginalContributionForSelectorArgument = \ 

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

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

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

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

302 "selector"}) 

303 

304RecomputePortfolioSelectorArgument = \ 

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

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

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

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

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

310 "result in the computation of the marginal contributions " 

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

312 

313RecomputeRunSolversArgument = \ 

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

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

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

317 "instances"}) 

318 

319RunExtractorNowArgument = \ 

320 ArgumentContainer(names=["--run-extractor-now"], 

321 kwargs={"default": False, 

322 "action": "store_true", 

323 "help": "immediately run the feature extractor(s) on all " 

324 "the instances"}) 

325 

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

327 kwargs={"type": Runner, 

328 "choices": [Runner.LOCAL, 

329 Runner.SLURM], 

330 "action": EnumAction, 

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

332 "environment to execute the " 

333 "calculation."}) 

334 

335RunSolverNowArgument = ArgumentContainer(names=["--run-solver-now"], 

336 kwargs={"default": False, 

337 "action": "store_true", 

338 "help": "immediately run the solver(s) " 

339 "on all instances"}) 

340 

341SelectionReportArgument = \ 

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

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

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

345 

346SelectorAblationArgument =\ 

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

348 kwargs={"required": False, 

349 "action": "store_true", 

350 "help": "construct a selector for " 

351 "each solver ablation combination"}) 

352 

353SettingsFileArgument = \ 

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

355 kwargs={"type": Path, 

356 "default": Settings.DEFAULT_settings_path, 

357 "action": SetByUser, 

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

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

360 

361SkipChecksArgument = ArgumentContainer( 

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

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

364 "default": True, 

365 "action": "store_false", 

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

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

368 

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

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

371 "type": str, 

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

373 

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

375 kwargs={"required": False, 

376 "type": str, 

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

378 

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

380 kwargs={"required": True, 

381 "type": Path, 

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

383 

384SolversArgument = ArgumentContainer(names=["--solvers"], 

385 kwargs={"required": False, 

386 "nargs": "+", 

387 "type": list[str], 

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

389 "used. If not specifed, all solvers " 

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

391 

392SolverCallsArgument = \ 

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

394 kwargs={"type": int, 

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

396 

397SolverSeedsArgument = \ 

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

399 kwargs={"type": int, 

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

401 

402SolverRemoveArgument = \ 

403 ArgumentContainer(names=["solver"], 

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

405 "type": str, 

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

407 

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

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

410 "type": str, 

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

412 

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

414 kwargs={"required": False, 

415 "type": str, 

416 "default": None, 

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

418 "algorithm configuration report"}) 

419 

420TargetCutOffTimeAblationArgument = \ 

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

422 kwargs={"type": int, 

423 "default": Settings.DEFAULT_general_target_cutoff_time, 

424 "action": SetByUser, 

425 "help": "cutoff time per target algorithm run in seconds"}) 

426 

427TargetCutOffTimeConfigurationArgument = \ 

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

429 kwargs={"type": int, 

430 "help": "cutoff time per target algorithm run in seconds"}) 

431 

432TargetCutOffTimeRunSolversArgument = \ 

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

434 kwargs={"type": int, 

435 "help": "cutoff time per target algorithm run in seconds"}) 

436 

437TargetCutOffTimeValidationArgument = \ 

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

439 kwargs={"type": int, 

440 "default": Settings.DEFAULT_general_target_cutoff_time, 

441 "action": SetByUser, 

442 "help": "cutoff time per target algorithm run in seconds"}) 

443 

444TestCaseDirectoryArgument = \ 

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

446 kwargs={"type": str, 

447 "default": None, 

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

449 + "for a selection report"}) 

450 

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

452 kwargs={"required": False, 

453 "action": "store_true", 

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

455 " for configuration"}) 

456 

457ValidateArgument = ArgumentContainer(names=["--validate"], 

458 kwargs={"required": False, 

459 "action": "store_true", 

460 "help": "validate after configuration"}) 

461 

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

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

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

465 

466WallClockTimeArgument = \ 

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

468 kwargs={"type": int, 

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

470 "seconds (wallclock)"}) 

471 

472SelectorTimeoutArgument = \ 

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

474 kwargs={"type": int, 

475 "default": Settings.DEFAULT_portfolio_construction_timeout, 

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

477 "selector construction"}) 

478 

479SparkleObjectiveArgument = \ 

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

481 kwargs={"type": str, 

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