1"""Argument parser construction and command dispatch for PICurv."""
4from .storage
import add_storage_parser, storage_workflow
12CLI_OUTPUT_FORMATS = (
"text",
"json")
15RUN_STAGE_CHOICES = (
"all",
"solve",
"post-process")
18RESTART_STATISTICS_MODES = (
"reset",
"carry")
21WORKSPACE_INPUT_KINDS = (
"grid",
"initial-condition",
"inlet-profile",
"reference-field")
24WORKSPACE_INPUT_MODES = (
"copy",
"reflink",
"hardlink",
"reference")
29 @brief Attach the executable-selection switches to a staging parser: pinning and index width.
30 @details Unset pinning means the workspace's `reproducibility.pin_executables` decides; without
31 it the run launches the installation's executables. Unset index width means the
32 conductor chooses from the run's particle and grid sizes, or its restart source.
33 @param[in] parser Parser or argument group receiving the switches.
36 pinning = parser.add_mutually_exclusive_group()
38 "--pin-executables", dest=
"pin_executables", action=
"store_const", const=
True, default=
None,
39 help=
"Copy simulator and postprocessor into the run's config/bin/ at staging, so a later\n"
40 "rebuild of the installation cannot change what the job runs. On a pinned run,\n"
41 "re-pins it to the current build.",
44 "--no-pin-executables", dest=
"pin_executables", action=
"store_const", const=
False,
45 help=
"Launch the installation's executables even when the workspace sets\n"
46 "reproducibility.pin_executables. Slurm jobs still check at start that they\n"
47 "report the build identity read at staging.",
50 "--petsc-index-bits", dest=
"petsc_index_bits", choices=PETSC_INDEX_BITS_CHOICES, default=
None,
51 help=
"Run on the 32- or 64-bit-index PETSc build (simulator64/postprocessor64, from\n"
52 "`make INDEX_BITS=64`). Default: 64 only when a particle or grid field outgrows\n"
53 "32-bit indices; a restart keeps its source's width. Also PICURV_PETSC_INDEX_BITS.",
59 @brief Attach `run` parser with staged execution and dry-run support.
60 @param[in] subparsers Argument passed to `_add_run_parser()`.
61 @return Value returned by `_add_run_parser()`.
63 p_run = subparsers.add_parser(
65 help=
"Execute a simulation workflow (solve and/or post-process).",
66 formatter_class=argparse.RawTextHelpFormatter,
68 "Execute solver and/or post-processing stages.\n\n"
70 " - --num-procs applies to solver and post-processing stage launches.\n"
71 " - With --solve, --continue resumes the existing run directory in-place.\n"
72 " - --post-process processes only the steps whose output is missing or stale: no output,\n"
73 " a changed recipe, or a changed checkpoint. Steps without a committed checkpoint\n"
74 " wait for the solver. --recompute regenerates every step.\n\n"
76 " - PETSc and runtime memory diagnostics live under monitor.yml -> diagnostics.\n"
77 " - Use --dry-run to inspect resolved PETSc flags and expected log destinations.\n\n"
79 " picurv run --solve -n 8 --case case.yml --solver solver.yml --monitor monitor.yml\n"
80 " picurv run --solve --restart-from runs/old_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
81 " picurv run --solve --continue --run-dir runs/my_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
82 " picurv run --post-process --run-dir runs/my_run --post post.yml\n"
83 " picurv run --post-process --recompute --run-dir runs/my_run --post post.yml\n"
84 " picurv run --solve --case case.yml --solver solver.yml --monitor monitor.yml --dry-run"
86 epilog=
"Next: run `picurv validate ...` first for config-only checks.",
88 run_group = p_run.add_argument_group(
"stages")
89 run_group.add_argument(
"--solve", action=
"store_true", help=
"Execute the solver stage (creates a new run directory).")
90 run_group.add_argument(
"--post-process", action=
"store_true", help=
"Execute the post-processing stage on a run directory.")
92 solver_group = p_run.add_argument_group(
"solver inputs (required for --solve)")
93 solver_group.add_argument(
"--case", help=
"Path to the case definition file (e.g., case.yml).")
94 solver_group.add_argument(
"--solver", help=
"Path to the solver settings profile (e.g., solver.yml).")
95 solver_group.add_argument(
"--monitor", help=
"Path to the monitoring, diagnostics, and I/O profile (e.g., monitor.yml).")
96 solver_group.add_argument(
97 "--restart-from",
"--from",
99 help=
"Path to an existing run directory to restart from.\n"
100 "Use 'latest' to select the newest compatible local workspace run.",
102 solver_group.add_argument(
103 "--statistics-state",
104 choices=RESTART_STATISTICS_MODES,
106 help=
"For a branched restart with field statistics enabled, this is required:\n"
107 "'reset' discards the parent's accumulated windows, 'carry' resumes compatible\n"
108 "saved window state. Ignored when field statistics are disabled.",
110 solver_group.add_argument(
111 "--require-precomputed",
113 help=
"Refuse to build missing or stale deterministic assets while staging the run.",
115 solver_group.add_argument(
118 help=
"Try the configured storage profile before rebuilding a missing workspace asset.",
120 run_group.add_argument(
124 help=
"Extend an existing run's solve in place. Requires --solve and --run-dir,\n"
125 "and start_step > 0; appends to existing solver output/logs.\n"
126 "Post-processing needs no flag: it always skips up-to-date steps.",
129 post_group = p_run.add_argument_group(
"post-processor inputs (required for --post-process)")
130 post_group.add_argument(
"--run-dir", help=
"Path to an existing run directory.\n(Used with --post-process or --solve --continue).")
131 post_group.add_argument(
"--post", help=
"Path to the post-processing recipe file (e.g., post.yml).")
132 post_group.add_argument(
134 help=
"Comma-separated post stages to run: 'fields' (the field post-processor),\n"
135 "'spectra', and 'reducers' (the post.yml reducers tasks, on profiles the\n"
136 "field stage wrote). Defaults to every stage.\n"
137 "Use --only spectra to measure spectra without running the field stage.",
139 post_group.add_argument(
142 dest=
"recompute_post",
143 help=
"Regenerate every requested step that has a committed checkpoint, including\n"
144 "steps whose output is up to date (for example, after rebuilding the\n"
145 "postprocessor). Applies to every selected stage.",
153 help=
"Number of MPI processes for solver and post-processing stages (default 1, or\n"
154 "the product of --ni --nj --nk). The decomposition is chosen from the grid and\n"
155 "its boundary conditions unless given.",
157 for axis
in (
"i",
"j",
"k"):
159 f
"--n{axis}", type=int, default=
None,
160 help=f
"MPI ranks along the grid's {axis} axis. Give --ni, --nj and --nk together.",
162 p_run.add_argument(
"--cluster", help=
"Path to cluster.yml for Slurm execution mode.")
163 p_run.add_argument(
"--scheduler", help=
"Explicit scheduler selector (currently 'slurm').")
164 p_run.add_argument(
"--no-submit", action=
"store_true", help=
"Stage run artifacts without starting local execution or Slurm submission.")
169 help=
"Resolve and print planned commands/artifacts, including diagnostic flags and log paths, without writing files.",
173 dest=
"output_format",
174 choices=
list(CLI_OUTPUT_FORMATS),
176 help=
"Output format for --dry-run (default: text).",
183 @brief Perform add sweep parser.
184 @param[in] subparsers Argument passed to `_add_sweep_parser()`.
185 @return Value returned by `_add_sweep_parser()`.
187 p_sweep = subparsers.add_parser(
189 help=
"Launch or continue a Slurm-based parameter sweep/study.",
190 formatter_class=argparse.RawTextHelpFormatter,
192 "Launch studies from study.yml + cluster.yml, whether the study uses\n"
193 "cross-product parameter combinations or explicit coupled parameter_sets, continue\n"
194 "partially-completed studies, or re-aggregate metrics.\n\n"
196 " picurv sweep --study study.yml --cluster cluster.yml\n"
197 " picurv sweep --study study.yml --cluster cluster.yml --no-submit\n"
198 " picurv sweep --continue --study-dir studies/<id>\n"
199 " picurv sweep --continue --study-dir studies/<id> --cluster cluster_more_time.yml\n"
200 " picurv sweep --reaggregate --study-dir studies/<id>"
203 "Study files support either `parameters` cross-product expansion or explicit `parameter_sets`.\n"
204 "Next: inspect studies/<study_id>/output/analysis/metrics_table.csv for aggregated metrics."
207 p_sweep.add_argument(
"--study", help=
"Path to study.yml defining either a `parameters` cross-product expansion or explicit parameter_sets, plus metrics.")
208 p_sweep.add_argument(
"--cluster", help=
"Path to cluster.yml defining Slurm resources.")
209 p_sweep.add_argument(
"--no-submit", action=
"store_true", help=
"Generate all study artifacts without submitting jobs.")
211 p_sweep.add_argument(
"--study-dir", help=
"Path to an existing study directory (for --continue/--reaggregate).")
212 p_sweep.add_argument(
"--continue", action=
"store_true", dest=
"continue_study",
213 help=
"Continue a partially-completed study. Requires --study-dir.")
214 p_sweep.add_argument(
"--reaggregate", action=
"store_true",
215 help=
"Re-run metrics aggregation on a completed study. Requires --study-dir.")
216 p_sweep.add_argument(
"--auto-fetch", action=
"store_true",
217 help=
"Restore cold-storage study members automatically instead of\n"
218 "refusing. Requires a configured storage profile.")
224 @brief `--version`, resolving the build identity only when the flag is given.
225 @details argparse's own version action formats its string while the parser is built,
226 which would run git for every command; this one defers it to the flag.
229 def __init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None):
231 @brief Register a flag that takes no value.
232 @param[in] option_strings Flag spellings.
233 @param[in] dest Unused destination.
234 @param[in] default Unused default.
235 @param[in] help Help text.
237 super().
__init__(option_strings=option_strings, dest=dest, default=default, nargs=0,
238 help=help
or "show program's version number and exit")
240 def __call__(self, parser, namespace, values, option_string=None):
242 @brief Print `picurv <version>` and exit.
243 @param[in] parser Parser being run.
244 @param[in] namespace Unused namespace.
245 @param[in] values Unused values.
246 @param[in] option_string Flag used.
249 print(f
"picurv {picurv_build()['version']}")
255 @brief Perform add validate parser.
256 @param[in] subparsers Argument passed to `_add_validate_parser()`.
257 @return Value returned by `_add_validate_parser()`.
259 p_validate = subparsers.add_parser(
261 help=
"Validate config files without launching solver/post.",
262 formatter_class=argparse.RawTextHelpFormatter,
264 "Validate one or more config roles. No solver/post execution and no run/study artifact writes.\n\n"
266 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml\n"
267 " picurv validate --post post.yml --cluster cluster.yml\n"
268 " picurv validate --study study.yml --cluster cluster.yml --strict"
270 epilog=
"Next: run `picurv run --dry-run ...` to inspect resolved commands/artifacts.",
272 p_validate.add_argument(
"--case", help=
"Path to case.yml")
273 p_validate.add_argument(
"--solver", help=
"Path to solver.yml")
274 p_validate.add_argument(
"--monitor", help=
"Path to monitor.yml (logging, diagnostics, profiling, and I/O)")
275 p_validate.add_argument(
"--restart-from",
"--from", dest=
"restart_from",
276 help=
"Path to a run directory to validate restart from.")
277 p_validate.add_argument(
"--continue", action=
"store_true", dest=
"continue_run", help=
"Validate continue-in-place mode. Requires --run-dir.")
278 p_validate.add_argument(
"--run-dir", help=
"Path to an existing run directory (for --continue validation).")
279 p_validate.add_argument(
"--post", help=
"Path to post.yml")
280 p_validate.add_argument(
"--cluster", help=
"Path to cluster.yml")
281 p_validate.add_argument(
"--study", help=
"Path to study.yml")
282 p_validate.add_argument(
"--strict", action=
"store_true", help=
"Enable additional strict checks for selected roles.")
287 @brief Attach `precompute` parser for deterministic artifact generation.
288 @param[in] subparsers Argument passed to `_add_precompute_parser()`.
289 @return Configured parser.
291 p_precompute = subparsers.add_parser(
293 help=
"Generate deterministic case artifacts without launching the solver.",
294 formatter_class=argparse.RawTextHelpFormatter,
296 "Generate configured deterministic artifacts, such as grid_gen grids,\n"
297 "generated prescribed-flow inlet profiles, and ic_gen initial conditions,\n"
298 "as immutable objects in the initialized workspace asset store.\n\n"
300 " picurv precompute --case config/case.yml\n"
301 " picurv precompute --case config/case.yml --only grid,initial-condition"
303 epilog=
"Next: inspect the reported asset set, then stage a run that reuses it.",
305 p_precompute.add_argument(
"--case", required=
True, help=
"Path to case.yml containing grid/profile/IC generator settings.")
306 p_precompute.add_argument(
309 help=
"Comma-separated asset kinds (grid, initial-condition, inlet-profiles), or all.",
316 @brief Attach explicit workspace input-ingress commands.
317 @param[in] subparsers Top-level argparse subparser collection.
318 @return Configured inputs command parser.
320 parser = subparsers.add_parser(
322 help=
"Import or register files used by workspace configurations.",
323 formatter_class=argparse.RawTextHelpFormatter,
325 actions = parser.add_subparsers(dest=
"inputs_action", required=
True)
326 p_import = actions.add_parser(
"import", help=
"Import one external file into its canonical workspace home.")
327 p_import.add_argument(
329 choices=WORKSPACE_INPUT_KINDS,
331 p_import.add_argument(
"source", help=
"Existing source file.")
332 p_import.add_argument(
"--name", help=
"Destination name; defaults to the source basename.")
333 p_import.add_argument(
334 "--mode", choices=WORKSPACE_INPUT_MODES, default=
"copy",
335 help=
"How to retain the input. Reference mode records but does not copy an external path.",
337 p_import.add_argument(
"--workspace", help=
"Workspace root; defaults to discovery from the current directory.")
343 @brief Attach unified source/build/workspace-version commands.
344 @param[in] subparsers Top-level argparse subparser collection.
345 @return Version, versions, and source command parsers.
347 p_version = subparsers.add_parser(
349 help=
"Report the build identity of the conductor and every native executable.",
350 formatter_class=argparse.RawTextHelpFormatter,
352 "Report the build identity shared by the Python conductor and the native\n"
353 "executables, plus any workspace version requirement.\n\n"
354 "With the 'status' action this also validates that they agree, and exits 1\n"
355 "when they do not, so a job script can refuse an incoherent build.\n\n"
358 " picurv version status\n"
359 " picurv version status --format json"
362 p_version.add_argument(
363 "version_action", nargs=
"?", choices=[
"status"], default=
None,
364 help=
"'status' validates conductor/executable/workspace coherence and exits 1 on disagreement.",
366 p_version.add_argument(
"--format", dest=
"output_format", choices=
list(CLI_OUTPUT_FORMATS), default=
"text",
367 help=
"Output format (default: text).")
369 p_versions = subparsers.add_parser(
"versions", help=
"List, install, or activate PICurv source versions.")
370 actions = p_versions.add_subparsers(dest=
"versions_action", required=
True)
371 actions.add_parser(
"list", help=
"List local Git tags and the active build.")
372 p_install = actions.add_parser(
"install", help=
"Fetch a named version and build it in the source checkout.")
373 p_install.add_argument(
"version", help=
"Tag, bare release (resolved to v<release>), or commit.")
374 p_activate = actions.add_parser(
"activate", help=
"Checkout and build the version required by this workspace.")
375 p_activate.add_argument(
"version", nargs=
"?", help=
"Version/tag; defaults to the workspace requirement.")
376 p_activate.add_argument(
"--workspace", help=
"Workspace root; defaults to discovery from the current directory.")
377 for parser
in (p_install, p_activate):
379 "make_args", nargs=argparse.REMAINDER,
380 help=
"Make variables passed to the build, as with 'picurv build' (for example "
381 "SYSTEM=cluster); make options go after '--'. Place them after every picurv option.",
384 p_source = subparsers.add_parser(
"source", help=
"Manage the source checkout used by PICurv.")
385 source_actions = p_source.add_subparsers(dest=
"source_action", required=
True)
386 p_update = source_actions.add_parser(
"update", help=
"Fetch source updates without changing the active version.")
387 p_update.add_argument(
"--remote", default=
"origin", help=
"Git remote to fetch from (default: origin).")
388 return p_version, p_versions, p_source
393 @brief Attach `summarize` parser for read-only run-health views.
394 @param[in] subparsers Argument passed to `_add_summarize_parser()`.
395 @return Value returned by `_add_summarize_parser()`.
397 p_summarize = subparsers.add_parser(
399 help=
"Summarize run configs/health and plot scalar log histories.",
400 formatter_class=argparse.RawTextHelpFormatter,
402 "Build read-only configuration overviews, run-health summaries, and scalar time-history plots.\n"
403 "It does not modify solver output and works for active or completed runs.\n\n"
405 " picurv summarize --run-dir runs/my_run --overview\n"
406 " picurv summarize --run-dir runs/my_run --case --solver\n"
407 " picurv summarize --run-dir runs/my_run --latest\n"
408 " picurv summarize --run-dir runs/my_run --monitor --step 500\n"
409 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
410 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
411 " picurv summarize --run-dir runs/my_run --table poisson --last 10\n"
412 " picurv summarize --run-dir runs/my_run --latest --format json"
414 epilog=
"Next: use `picurv run ...` to create runs or `picurv sweep ...` for multi-case studies.",
416 p_summarize.add_argument(
"--run-dir", required=
True, help=
"Path to the run directory to inspect.")
417 p_summarize.add_argument(
420 help=
"Summarize run metadata plus copied case, solver, and monitor configs without implicitly requesting health.",
422 p_summarize.add_argument(
"--case", action=
"store_true", help=
"Summarize the copied run-local case.yml.")
423 p_summarize.add_argument(
"--solver", action=
"store_true", help=
"Summarize the copied run-local solver.yml.")
424 p_summarize.add_argument(
"--monitor", action=
"store_true", help=
"Summarize the copied run-local monitor.yml.")
425 plot_group = p_summarize.add_mutually_exclusive_group()
426 plot_group.add_argument(
427 "--list-plot-series",
428 dest=
"list_plot_series",
430 help=
"List scalar histories available to --plot.",
432 plot_group.add_argument(
434 dest=
"list_plot_series",
436 help=argparse.SUPPRESS,
438 plot_group.add_argument(
441 help=
"Plot one qualified scalar history, such as momentum.residual_norm (requires matplotlib).",
443 plot_group.add_argument(
445 dest=
"plot_spectrum",
448 help=
"Plot up to six representative measured energy spectra, including the first\n"
449 "and last states, overlaid on the initial-condition spectrum. Takes an optional\n"
450 "task-name substring when the recipe measured more than one spectrum\n"
451 "(requires matplotlib).",
453 plot_group.add_argument(
456 help=
"Print the last rows of one of the run's CSV records as aligned columns, such as\n"
457 "continuity, poisson, search or memory; a per-block record takes ':<block>'.",
459 plot_group.add_argument(
463 help=
"List the CSV records this run has, for --table.",
465 p_summarize.add_argument(
"--columns", dest=
"table_columns",
466 help=
"With --table: comma-separated columns to show (the first column is always kept).")
467 p_summarize.add_argument(
"--last", dest=
"last_n", type=int,
468 help=
"With --plot: only the last N chronological records per line.\n"
469 "With --table: the last N rows (default 20).")
470 p_summarize.add_argument(
"--plot-output", help=
"Save the plot to this path instead of opening an interactive window.")
471 p_summarize.add_argument(
"--linear-y", action=
"store_true", help=
"Force linear y-axis scaling instead of automatic residual/norm log scaling.")
472 step_group = p_summarize.add_mutually_exclusive_group()
473 step_group.add_argument(
"--step", type=int, help=
"Specific completed timestep to summarize.")
474 step_group.add_argument(
477 help=
"Summarize the most recently appended completed step found in available artifacts (default behavior).",
479 step_group.add_argument(
482 help=
"Summarize the numerically largest timestep found in available artifacts.",
484 p_summarize.add_argument(
488 help=
"Number of sampled particle snapshot rows to preview when solver stream output contains them.",
490 p_summarize.add_argument(
492 dest=
"output_format",
493 choices=
list(CLI_OUTPUT_FORMATS),
495 help=
"Output format (default: text).",
502 @brief Perform add cancel parser.
503 @param[in] subparsers Argument passed to `_add_cancel_parser()`.
504 @return Value returned by `_add_cancel_parser()`.
506 p_cancel = subparsers.add_parser(
508 help=
"Cancel Slurm-submitted jobs for an existing run directory.",
509 formatter_class=argparse.RawTextHelpFormatter,
511 "Look up scheduler/submission.json inside an existing run directory and cancel\n"
512 "the recorded Slurm job(s) without requiring manual job-id lookup.\n\n"
514 " picurv cancel --run-dir runs/my_run\n"
515 " picurv cancel --run-dir runs/my_run --stage solve\n"
516 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
517 " picurv cancel --run-dir runs/my_run --dry-run\n\n"
518 "Default cancellation is a hard Slurm cancel (`scancel <job_id>`). With\n"
519 "`--graceful`, Slurm sends SIGUSR1 to the solver process tree so PICurv can write the latest\n"
520 "safe off-cadence step at the next runtime checkpoint before exiting.\n"
521 "Post-process jobs still use ordinary hard cancellation."
523 epilog=
"Next: use `picurv summarize --run-dir ...` to inspect whatever output the run already produced.",
525 p_cancel.add_argument(
"--run-dir", required=
True, help=
"Path to the run directory whose Slurm job(s) should be canceled.")
526 p_cancel.add_argument(
528 choices=
list(RUN_STAGE_CHOICES),
530 help=
"Which recorded stage job(s) to cancel (default: all).",
532 p_cancel.add_argument(
535 help=
"Show which `scancel` command(s) would run without actually canceling anything.",
537 p_cancel.add_argument(
541 "For solver jobs, request a clean runtime shutdown by sending SIGUSR1 to the process tree instead of "
542 "hard-canceling immediately. The solver writes the latest safe off-cadence step at "
543 "the next checkpoint. Non-solver stages still use ordinary scancel."
551 @brief Perform add submit parser.
552 @param[in] subparsers Argument passed to `_add_submit_parser()`.
553 @return Value returned by `_add_submit_parser()`.
555 p_submit = subparsers.add_parser(
557 help=
"Execute or submit previously staged artifacts from an existing run or study directory.",
558 formatter_class=argparse.RawTextHelpFormatter,
560 "Consume an existing artifact set created by 'picurv run ... --no-submit' or\n"
561 "'picurv sweep ... --no-submit' and execute/submit it later without\n"
562 "regenerating configs or scripts.\n"
563 "A post-process job decides when it starts which steps need work, so\n"
564 "submitting it again skips output that is already up to date.\n\n"
566 " picurv submit --run-dir runs/my_run\n"
567 " picurv submit --run-dir runs/my_run --stage solve\n"
568 " picurv submit --study-dir studies/my_study --dry-run"
570 epilog=
"Next: use `picurv summarize --run-dir ...` or `picurv cancel --run-dir ...` after submission as needed.",
572 target_group = p_submit.add_mutually_exclusive_group(required=
True)
573 target_group.add_argument(
"--run-dir", help=
"Path to a staged run directory created by `picurv run ... --no-submit`.")
574 target_group.add_argument(
"--study-dir", help=
"Path to a staged study directory created by `picurv sweep --cluster ... --no-submit`.")
575 p_submit.add_argument(
577 choices=
list(RUN_STAGE_CHOICES),
579 help=
"Which staged job(s) to submit (default: all, every stage the target staged).",
581 p_submit.add_argument(
584 help=
"Allow re-submitting a stage already marked as submitted in scheduler/submission.json.",
586 p_submit.add_argument(
589 help=
"Show which local command(s) or `sbatch` command(s) would run without starting anything.",
596 @brief Perform add init parser.
597 @param[in] subparsers Argument passed to `_add_init_parser()`.
598 @return Value returned by `_add_init_parser()`.
600 p_init = subparsers.add_parser(
602 help=
"Initialize a new case workspace from a template.",
603 formatter_class=argparse.RawTextHelpFormatter,
605 "Create a case workspace from examples/<template_name>.\n\n"
607 " picurv init flat_channel --dest my_case\n"
608 " picurv init bent_channel --dest my_bent_case\n"
609 " picurv init decaying_isotropic_turbulence --dest dit_case"
611 epilog=
"Next: run `picurv validate --case ... --solver ... --monitor ...` before execution.",
613 p_init.add_argument(
"template_name", help=
"Name of the case template directory to copy (e.g., 'flat_channel').")
617 help=
"Optional name for the new directory. Defaults to the template name.\nPath is relative to your current working directory.",
621 help=
"Optional override for the PICurv source repository root.\nUseful when running from a copied case without metadata.",
627 help=
"(Legacy) Copy simulator and postprocessor into the case directory, used only when\n"
628 "that directory's own picurv is invoked. To freeze the executables a run uses,\n"
629 "use 'picurv run --pin-executables'.",
636 @brief Perform add build parser.
637 @param[in] subparsers Argument passed to `_add_build_parser()`.
638 @return Value returned by `_add_build_parser()`.
640 p_build = subparsers.add_parser(
642 help=
"Build project executables using the Makefile.",
643 formatter_class=argparse.RawTextHelpFormatter,
645 "Calls the project's Makefile directly through `make`.\n"
646 "If you do not pass an explicit make target, `picurv build` runs `make all`.\n"
647 "Targets and variables after 'build' are passed directly to make; make options\n"
648 "such as -j8 go after '--'.\n\n"
651 " picurv build clean-project\n"
652 " picurv build SYSTEM=cluster\n"
653 " picurv build all SYSTEM=cluster\n"
654 " picurv build postprocessor\n"
655 " picurv build -- -j8 all\n"
656 " picurv build INDEX_BITS=64 PETSC_DIR=$HOME/sw/petsc-int64 # simulator64/postprocessor64\n"
657 " ./picurv build clean-project # from an initialized case directory"
659 epilog=
"Next: run `picurv --help` or `picurv run --help` for execution commands.",
661 p_build.add_argument(
663 help=
"Optional override for the PICurv source repository root.",
665 p_build.add_argument(
667 help=
"Optional case directory used to resolve .picurv-origin.json when not running from that case.",
669 p_build.add_argument(
671 nargs=argparse.REMAINDER,
672 help=
"Targets and variables passed to make (e.g., 'clean-project'); make options go after '--'.",
679 @brief Perform add sync config parser.
680 @param[in] subparsers Argument passed to `_add_sync_config_parser()`.
681 @return Value returned by `_add_sync_config_parser()`.
683 p_sync_config = subparsers.add_parser(
685 help=
"Refresh template-managed files in a case directory from examples/<template>.",
686 formatter_class=argparse.RawTextHelpFormatter,
688 "Copy updated example template files into an existing case directory.\n"
689 "Modified files are preserved unless --overwrite is used.\n"
690 "--prune removes only files that were previously tracked as template-managed and\n"
691 "have since been removed from the source template.\n\n"
693 " ./picurv sync-config\n"
694 " ./picurv sync-config --overwrite\n"
695 " ./picurv sync-config --prune\n"
696 " ./bin/picurv sync-config --case-dir my_case --template-name flat_channel"
698 epilog=
"Next: run `picurv validate ...` after syncing configs.",
700 p_sync_config.add_argument(
"--case-dir", help=
"Optional case directory to refresh. Defaults to the current case.")
701 p_sync_config.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
702 p_sync_config.add_argument(
704 help=
"Optional template name override (e.g., flat_channel). Required when metadata is absent.",
706 p_sync_config.add_argument(
"--overwrite", action=
"store_true", help=
"Overwrite case files even when they differ from the template.")
707 p_sync_config.add_argument(
710 help=
"Remove stale files that were previously tracked as template-managed but no longer exist in the source template.",
717 @brief Perform add pull source parser.
718 @param[in] subparsers Argument passed to `_add_pull_source_parser()`.
719 @return Value returned by `_add_pull_source_parser()`.
721 p_pull = subparsers.add_parser(
723 help=
"(Legacy; use 'source update') Refresh source branches from a case directory.",
724 formatter_class=argparse.RawTextHelpFormatter,
726 "Legacy maintenance command; new workspaces use `picurv source update` and\n"
727 "`picurv versions`. Updates the source repository without leaving an\n"
728 "initialized case directory.\n\n"
729 "By default this refreshes every local branch that tracks an upstream,\n"
730 "then restores the branch you started on.\n\n"
732 " ./picurv pull-source\n"
733 " ./picurv pull-source --current-branch-only\n"
734 " ./picurv pull-source --no-rebase\n"
735 " ./picurv pull-source --remote origin --branch main"
737 epilog=
"Next: run `./picurv build` if source changes require rebuilt executables.",
739 p_pull.add_argument(
"--case-dir", help=
"Optional case directory used to resolve .picurv-origin.json.")
740 p_pull.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
741 p_pull.add_argument(
"--remote", help=
"Optional git remote name (e.g., origin).")
742 p_pull.add_argument(
"--branch", help=
"Optional branch name. If provided without --remote, origin is assumed.")
744 "--current-branch-only",
746 help=
"Only pull the currently checked out branch instead of iterating across all local tracking branches.",
748 p_pull.add_argument(
"--no-rebase", action=
"store_true", help=
"Use plain git pull instead of git pull --rebase.")
754 @brief Perform add status source parser.
755 @param[in] subparsers Argument passed to `_add_status_source_parser()`.
756 @return Value returned by `_add_status_source_parser()`.
758 p_status = subparsers.add_parser(
760 help=
"(Legacy; use 'version status') Report source/case drift for a case directory.",
761 formatter_class=argparse.RawTextHelpFormatter,
763 "Legacy maintenance command; `picurv version status` reports build coherence for\n"
764 "new workspaces. Inspects whether the source repo, copied binaries, and template-managed files have drifted\n"
765 "from the current case directory.\n\n"
767 " ./picurv status-source\n"
768 " ./picurv status-source --format json\n"
769 " ./bin/picurv status-source --case-dir my_case"
771 epilog=
"Next: use `source update`, `build`, or `sync-config` based on the reported drift.",
773 p_status.add_argument(
"--case-dir", help=
"Optional case directory used to resolve .picurv-origin.json.")
774 p_status.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
775 p_status.add_argument(
"--template-name", help=
"Optional template name override when metadata is absent.")
776 p_status.add_argument(
778 dest=
"output_format",
779 choices=
list(CLI_OUTPUT_FORMATS),
781 help=
"Output format (default: text).",
788 @brief Build and return the top-level CLI parser.
789 @return Value returned by `build_main_parser()`.
791 parser = argparse.ArgumentParser(
792 description=
"picurv: A comprehensive conductor for the PICurv simulation platform.",
793 formatter_class=argparse.RawTextHelpFormatter,
796 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml\n"
797 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --dry-run\n"
798 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --no-submit\n"
799 " picurv precompute --case config/case.yml --only grid,initial-condition\n"
800 " picurv run --post-process --run-dir runs/my_run --post post.yml\n"
801 " picurv summarize --run-dir runs/my_run --latest\n"
802 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
803 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
804 " picurv submit --run-dir runs/my_run\n"
805 " picurv cancel --run-dir runs/my_run\n"
806 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
807 " picurv sweep --study study.yml --cluster cluster.yml\n\n"
809 " - First run: picurv init ... -> picurv validate ... -> picurv run ...\n"
810 " - Config debugging: picurv validate ...\n"
811 " - Artifact generation: picurv precompute ...\n"
812 " - Launch planning: picurv run ... --dry-run\n"
813 " - Post-only catch-up: picurv run --post-process --run-dir ... --post ...\n"
814 " - Deferred submission: picurv submit --run-dir ...\n"
815 " - Run inspection/plots: picurv summarize ...\n"
816 " - Run cancellation: picurv cancel --run-dir ...; use --graceful for solver final-output shutdown"
820 "-v",
"--version", action=_LazyVersionAction,
821 help=
"Print the conductor's build identity and exit. 'picurv version' also reports\n"
822 "each native executable and the PETSc it uses.",
824 subparsers = parser.add_subparsers(dest=
"command", required=
True, help=
"Available commands")
839 add_storage_parser(subparsers)
843def dispatch_command(args):
845 @brief Validate argument combinations and dispatch to command handlers.
846 @param[in] args Command-line style argument list supplied to the function.
848 if args.command ==
"run":
849 if not args.solve
and not args.post_process:
850 fail_cli_usage(
"At least one stage (--solve or --post-process) must be selected.")
851 if args.solve
and (
not args.case
or not args.solver
or not args.monitor):
852 fail_cli_usage(
"--solve requires --case, --solver, and --monitor.")
853 if args.post_process
and not args.post:
855 if args.post_process
and not args.solve
and not args.run_dir:
856 fail_cli_usage(
"--post-process requires --run-dir when not used with --solve.")
857 if args.continue_run
and not args.run_dir:
859 if getattr(args,
"only",
None)
and not args.post_process:
860 fail_cli_usage(
"--only selects post-processing stages and requires --post-process.")
863 except ValueError
as exc:
865 if getattr(args,
"recompute_post",
False)
and not args.post_process:
866 fail_cli_usage(
"--recompute regenerates post-processing output and requires --post-process.")
867 if args.scheduler
and not args.cluster:
872 if args.command ==
"sweep":
873 if args.continue_study:
874 if not args.study_dir:
879 if not args.study_dir:
887 fail_cli_usage(
"--cluster is required when launching a new sweep.")
890 if args.command ==
"validate":
893 if args.command ==
"precompute":
896 except ValueError
as e:
898 ERROR_CODE_CFG_INVALID_VALUE,
900 file_path=getattr(args,
"case",
"-"),
902 hint=
"'picurv precompute --help' lists the asset kinds --only accepts.",
906 if args.command ==
"inputs":
909 except ValueError
as exc:
910 print(f
"[FATAL] {exc}", file=sys.stderr)
913 if args.command ==
"version":
916 if args.command ==
"versions":
919 except ValueError
as exc:
920 print(f
"[FATAL] {exc}", file=sys.stderr)
923 if args.command ==
"source":
926 except ValueError
as exc:
927 print(f
"[FATAL] {exc}", file=sys.stderr)
930 if args.command ==
"summarize":
933 if args.command ==
"submit":
936 if args.command ==
"cancel":
939 if args.command ==
"init":
942 if args.command ==
"build":
945 if args.command ==
"sync-config":
948 if args.command ==
"pull-source":
951 if args.command ==
"status-source":
954 if args.command ==
"storage":
955 storage_workflow(args)
--version, resolving the build identity only when the flag is given.
__call__(self, parser, namespace, values, option_string=None)
Print picurv <version> and exit.
__init__(self, option_strings, dest=argparse.SUPPRESS, default=argparse.SUPPRESS, help=None)
Register a flag that takes no value.
_add_sweep_parser(subparsers)
Perform add sweep parser.
_add_version_parsers(subparsers)
Attach unified source/build/workspace-version commands.
_add_run_parser(subparsers)
Attach run parser with staged execution and dry-run support.
_add_build_parser(subparsers)
Perform add build parser.
_add_init_parser(subparsers)
Perform add init parser.
build_main_parser()
Build and return the top-level CLI parser.
_add_executable_pinning_arguments(parser)
Attach the executable-selection switches to a staging parser: pinning and index width.
_add_sync_config_parser(subparsers)
Perform add sync config parser.
_add_pull_source_parser(subparsers)
Perform add pull source parser.
_add_precompute_parser(subparsers)
Attach precompute parser for deterministic artifact generation.
_add_cancel_parser(subparsers)
Perform add cancel parser.
_add_status_source_parser(subparsers)
Perform add status source parser.
_add_submit_parser(subparsers)
Perform add submit parser.
_add_inputs_parser(subparsers)
Attach explicit workspace input-ingress commands.
_add_summarize_parser(subparsers)
Attach summarize parser for read-only run-health views.
_add_validate_parser(subparsers)
Perform add validate parser.
summarize_workflow(args)
Build and render a read-only health summary for a run step.
set resolve_post_stage_selection(only)
Resolve the --only selector into the set of post stages to execute.
status_source_command(args)
Report source/case drift for an initialized case directory.
validate_workflow(args)
Implements picurv validate without launching solver/post workflows.
version_workflow(args)
Report, and for the status action validate, the shared build identity.
sweep_reaggregate_workflow(args)
Re-run metrics aggregation and plot generation for an existing study.
emit_structured_error(str code, str key="-", str file_path="-", str message="", str hint=None, stream=None)
Emit one standardized error line for tooling and users.
init_case(args)
Implements the 'init' command.
sweep_workflow(args)
Study/sweep orchestration using Slurm job arrays.
inputs_workflow(args)
Handle explicit workspace input management.
submit_staged_jobs(args)
Submit previously staged Slurm artifacts from an existing run/study directory.
source_workflow(args)
Fetch source history without silently changing the active code.
None normalize_decomposition_args(args)
Resolve -n and the optional --ni/--nj/--nk of picurv run into one rank count.
sync_case_config_command(args)
Refresh template-managed config/docs files in a case directory.
build_project(args)
Implements the 'build' command.
fail_cli_usage(str message, str hint=None)
Emit a structured CLI usage error and exit with code 2.
sweep_continue_workflow(args)
Continue a partially-completed Slurm parameter sweep study.
cancel_run_jobs(args)
Cancel Slurm-submitted jobs for an existing run directory.
versions_workflow(args)
List or activate a release using the existing source/build owners.
precompute_workflow(args)
Resolve, preflight, and atomically publish reusable workspace assets.
run_workflow(args)
Main orchestrator for the 'run' command (local and Slurm modes).
pull_source_repo(args)
Refresh source branches in the repository resolved from a case directory.
Head of a generic C-style linked list.