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
« 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."""
3from __future__ import annotations
4import argparse
5import enum
6from pathlib import Path
7from typing import Any
9from runrunner.base import Runner
11from sparkle.platform.settings_objects import SettingState, Settings
14class SetByUser(argparse.Action):
15 """Possible action to execute for CLI argument."""
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)
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)
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")
39 # Generate choices from the Enum
40 kwargs.setdefault("choices", tuple(e.value for e in enum_type))
42 super(EnumAction, self).__init__(**kwargs)
44 self._enum = enum_type
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)
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
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")
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.
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
85ActualMarginalContributionArgument = \
86 ArgumentContainer(names=["--actual"],
87 kwargs={"action": "store_true",
88 "help": "compute the marginal contribution "
89 "for the actual selector"})
91AllJobsArgument = \
92 ArgumentContainer(names=["--all"],
93 kwargs={"action": "store_true",
94 "help": "use all known job ID(s) for the command"})
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."})
107CancelJobsArgument = \
108 ArgumentContainer(names=["--cancel"],
109 kwargs={"action": "store_true",
110 "help": "cancel the job(s) with the given ID(s)"})
112CleanupArgumentAll = \
113 ArgumentContainer(names=["--all"],
114 kwargs={"action": "store_true",
115 "help": "clean all output files"})
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"})
123CleanUpPerformanceDataArgument = \
124 ArgumentContainer(names=["--performance-data"],
125 kwargs={"action": "store_true",
126 "help": "clean performance data from empty lines"})
128ConfigurationArgument = \
129 ArgumentContainer(names=["--configuration"],
130 kwargs={"required": False,
131 "type": int,
132 "help": "The run index of which configuration to use"})
134ConfiguratorArgument = ArgumentContainer(names=["--configurator"],
135 kwargs={"type": str,
136 "help": "name of the configurator"})
138CPUTimeArgument = \
139 ArgumentContainer(names=["--cpu-time"],
140 kwargs={"type": int,
141 "help": "configuration budget per configurator run in "
142 "seconds (cpu)"})
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})"})
152DeterministicArgument =\
153 ArgumentContainer(names=["--deterministic"],
154 kwargs={"action": "store_true",
155 "help": "Flag indicating the solver is deterministic"})
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."})
164ExtractorPathArgument = ArgumentContainer(names=["extractor_path"],
165 kwargs={"metavar": "extractor-path",
166 "type": str,
167 "help": "path or nickname of the "
168 "feature extractor"
169 })
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 })
179InstancePathPositional = ArgumentContainer(names=["instance_path"],
180 kwargs={"type": Path,
181 "help": "Path to an instance (set)"})
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)"})
193InstanceSetTestArgument = \
194 ArgumentContainer(names=["--instance-set-test"],
195 kwargs={"required": False,
196 "type": Path,
197 "help": "path to test instance set (only for validating)"})
199InstanceSetTrainArgument = \
200 ArgumentContainer(names=["--instance-set-train"],
201 kwargs={"required": True,
202 "type": Path,
203 "help": "path to training instance set"})
205InstanceSetTestAblationArgument = \
206 ArgumentContainer(names=["--instance-set-test"],
207 kwargs={"required": False,
208 "type": str,
209 "help": "path to test instance set"})
211InstanceSetTrainAblationArgument = \
212 ArgumentContainer(names=["--instance-set-train"],
213 kwargs={"required": False,
214 "type": str,
215 "help": "path to training instance set"})
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"})
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"})
231InstancesPathArgument = ArgumentContainer(names=["instances_path"],
232 kwargs={"metavar": "instances-path",
233 "type": str,
234 "help": "path to the instance set"})
236InstancesPathRemoveArgument = \
237 ArgumentContainer(names=["instances_path"],
238 kwargs={"metavar": "instances-path",
239 "type": str,
240 "help": "path to or nickname of the instance set"})
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"})
249NicknameFeatureExtractorArgument = \
250 ArgumentContainer(names=["--nickname"],
251 kwargs={"type": str,
252 "help": "set a nickname for the feature extractor"})
254NicknameInstanceSetArgument = \
255 ArgumentContainer(names=["--nickname"],
256 kwargs={"type": str,
257 "help": "set a nickname for the instance set"})
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."})
265NicknameSolverArgument = \
266 ArgumentContainer(names=["--nickname"],
267 kwargs={"type": str,
268 "help": "set a nickname for the solver"})
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"})
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"})
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."})
294NumberOfRunsConfigurationArgument = \
295 ArgumentContainer(names=["--number-of-runs"],
296 kwargs={"type": int,
297 "help": "number of configuration runs to execute"})
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"})
306PerfectSelectorMarginalContributionArgument =\
307 ArgumentContainer(names=["--perfect"],
308 kwargs={"action": "store_true",
309 "help": "compute the marginal contribution "
310 "for the perfect selector"})
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"})
320RecomputeFeaturesArgument = \
321 ArgumentContainer(names=["--recompute"],
322 kwargs={"action": "store_true",
323 "help": "Re-run feature extractor for instances with "
324 "previously computed features"})
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"})
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"})
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."})
349RecomputeRunSolversArgument = \
350 ArgumentContainer(names=["--recompute"],
351 kwargs={"action": "store_true",
352 "help": "recompute the performance of all solvers on all "
353 "instances"})
355PerformanceDataJobsArgument = \
356 ArgumentContainer(names=["--performance-data-jobs"],
357 kwargs={"action": "store_true",
358 "help": "compute the remaining jobs in the Performance "
359 "DataFrame"})
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."})
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."})
377SelectionReportArgument = \
378 ArgumentContainer(names=["--selection"],
379 kwargs={"action": "store_true",
380 "help": "set to generate a normal selection report"})
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"})
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"})
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."})
405SnapshotArgument = ArgumentContainer(names=["snapshot_file_path"],
406 kwargs={"metavar": "snapshot-file-path",
407 "type": str,
408 "help": "path to the snapshot file"})
410SnapshotNameArgument = ArgumentContainer(names=["--name"],
411 kwargs={"required": False,
412 "type": str,
413 "help": "name of the snapshot"})
415SolverArgument = ArgumentContainer(names=["--solver"],
416 kwargs={"required": True,
417 "type": Path,
418 "help": "path to solver"})
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."})
429SolverCallsArgument = \
430 ArgumentContainer(names=["--solver-calls"],
431 kwargs={"type": int,
432 "help": "number of solver calls to execute"})
434SolverSeedsArgument = \
435 ArgumentContainer(names=["--solver-seeds"],
436 kwargs={"type": int,
437 "help": "number of random seeds per solver to execute"})
439SolverRemoveArgument = \
440 ArgumentContainer(names=["solver"],
441 kwargs={"metavar": "solver",
442 "type": str,
443 "help": "name, path to or nickname of the solver"})
445SolverPathArgument = ArgumentContainer(names=["solver_path"],
446 kwargs={"metavar": "solver-path",
447 "type": str,
448 "help": "path to the solver"})
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"})
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"})
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"})
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"})
477UseFeaturesArgument = ArgumentContainer(names=["--use-features"],
478 kwargs={"required": False,
479 "action": "store_true",
480 "help": "use the training set's features"
481 " for configuration"})
483VerboseArgument = ArgumentContainer(names=["--verbose", "-v"],
484 kwargs={"action": "store_true",
485 "help": "output status in verbose mode"})
487WallClockTimeArgument = \
488 ArgumentContainer(names=["--wallclock-time"],
489 kwargs={"type": int,
490 "help": "configuration budget per configurator run in "
491 "seconds (wallclock)"})
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"})
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."})
509ObjectiveArgument = \
510 ArgumentContainer(names=["--objective"],
511 kwargs={"type": str,
512 "help": "the objective to use."})
514ObjectivesArgument = \
515 ArgumentContainer(names=["--objectives"],
516 kwargs={"type": str,
517 "help": "the comma seperated objective(s) to use."})