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 `run` parser with staged execution and dry-run support.
30 @param[in] subparsers Argument passed to `_add_run_parser()`.
31 @return Value returned by `_add_run_parser()`.
33 p_run = subparsers.add_parser(
35 help=
"Execute a simulation workflow (solve and/or post-process).",
36 formatter_class=argparse.RawTextHelpFormatter,
38 "Execute solver and/or post-processing stages.\n\n"
40 " - --num-procs applies to solver and post-processing stage launches.\n"
41 " - With --solve, --continue resumes the existing run directory in-place.\n"
42 " - With --post-process, --continue resumes the same recipe from the first unfinished step\n"
43 " and caps the launch to the highest fully available contiguous source frontier.\n\n"
45 " - PETSc and runtime memory diagnostics live under monitor.yml -> diagnostics.\n"
46 " - Use --dry-run to inspect resolved PETSc flags and expected log destinations.\n\n"
48 " picurv run --solve -n 8 --case case.yml --solver solver.yml --monitor monitor.yml\n"
49 " picurv run --solve --restart-from runs/old_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
50 " picurv run --solve --continue --run-dir runs/my_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
51 " picurv run --post-process --run-dir runs/my_run --post post.yml\n"
52 " picurv run --post-process --continue --run-dir runs/my_run --post post.yml\n"
53 " picurv run --solve --case case.yml --solver solver.yml --monitor monitor.yml --dry-run"
55 epilog=
"Next: run `picurv validate ...` first for config-only checks.",
57 run_group = p_run.add_argument_group(
"stages")
58 run_group.add_argument(
"--solve", action=
"store_true", help=
"Execute the solver stage (creates a new run directory).")
59 run_group.add_argument(
"--post-process", action=
"store_true", help=
"Execute the post-processing stage on a run directory.")
61 solver_group = p_run.add_argument_group(
"solver inputs (required for --solve)")
62 solver_group.add_argument(
"--case", help=
"Path to the case definition file (e.g., case.yml).")
63 solver_group.add_argument(
"--solver", help=
"Path to the solver settings profile (e.g., solver.yml).")
64 solver_group.add_argument(
"--monitor", help=
"Path to the monitoring, diagnostics, and I/O profile (e.g., monitor.yml).")
65 solver_group.add_argument(
66 "--restart-from",
"--from",
68 help=
"Path to an existing run directory to restart from.\n"
69 "Use 'latest' to select the newest compatible local workspace run.",
71 solver_group.add_argument(
73 choices=RESTART_STATISTICS_MODES,
75 help=
"For a branched restart with field statistics enabled, this is required:\n"
76 "'reset' discards the parent's accumulated windows, 'carry' resumes compatible\n"
77 "saved window state. Ignored when field statistics are disabled.",
79 solver_group.add_argument(
80 "--require-precomputed",
82 help=
"Refuse to build missing or stale deterministic assets while staging the run.",
84 solver_group.add_argument(
87 help=
"Try the configured storage profile before rebuilding a missing workspace asset.",
89 run_group.add_argument(
93 help=
"Resume an existing run directory in-place. Requires --run-dir.\n"
94 "With --solve, requires start_step > 0 and appends to existing solver output/logs.\n"
95 "With --post-process, resumes the same recipe from the first unfinished step\n"
96 "and skips already-complete work inside the current live source frontier.",
99 post_group = p_run.add_argument_group(
"post-processor inputs (required for --post-process)")
100 post_group.add_argument(
"--run-dir", help=
"Path to an existing run directory.\n(Used with --post-process or --continue).")
101 post_group.add_argument(
"--post", help=
"Path to the post-processing recipe file (e.g., post.yml).")
102 post_group.add_argument(
104 help=
"Comma-separated post stages to run: 'fields' (the field post-processor)\n"
105 "and/or 'spectra'. Defaults to every stage.\n"
106 "Use --only spectra to re-measure spectra without rebuilding field output.",
114 help=
"Number of MPI processes for solver and post-processing stages.",
116 p_run.add_argument(
"--cluster", help=
"Path to cluster.yml for Slurm execution mode.")
117 p_run.add_argument(
"--scheduler", help=
"Explicit scheduler selector (currently 'slurm').")
118 p_run.add_argument(
"--no-submit", action=
"store_true", help=
"Stage run artifacts without starting local execution or Slurm submission.")
122 help=
"Resolve and print planned commands/artifacts, including diagnostic flags and log paths, without writing files.",
126 dest=
"output_format",
127 choices=
list(CLI_OUTPUT_FORMATS),
129 help=
"Output format for --dry-run (default: text).",
136 @brief Perform add sweep parser.
137 @param[in] subparsers Argument passed to `_add_sweep_parser()`.
138 @return Value returned by `_add_sweep_parser()`.
140 p_sweep = subparsers.add_parser(
142 help=
"Launch or continue a Slurm-based parameter sweep/study.",
143 formatter_class=argparse.RawTextHelpFormatter,
145 "Launch studies from study.yml + cluster.yml, whether the study uses\n"
146 "cross-product parameter combinations or explicit coupled parameter_sets, continue\n"
147 "partially-completed studies, or re-aggregate metrics.\n\n"
149 " picurv sweep --study study.yml --cluster cluster.yml\n"
150 " picurv sweep --study study.yml --cluster cluster.yml --no-submit\n"
151 " picurv sweep --continue --study-dir studies/<id>\n"
152 " picurv sweep --continue --study-dir studies/<id> --cluster cluster_more_time.yml\n"
153 " picurv sweep --reaggregate --study-dir studies/<id>"
156 "Study files support either `parameters` cross-product expansion or explicit `parameter_sets`.\n"
157 "Next: inspect studies/<study_id>/results/metrics_table.csv for aggregated metrics."
160 p_sweep.add_argument(
"--study", help=
"Path to study.yml defining either a `parameters` cross-product expansion or explicit parameter_sets, plus metrics.")
161 p_sweep.add_argument(
"--cluster", help=
"Path to cluster.yml defining Slurm resources.")
162 p_sweep.add_argument(
"--no-submit", action=
"store_true", help=
"Generate all study artifacts without submitting jobs.")
163 p_sweep.add_argument(
"--study-dir", help=
"Path to an existing study directory (for --continue/--reaggregate).")
164 p_sweep.add_argument(
"--continue", action=
"store_true", dest=
"continue_study",
165 help=
"Continue a partially-completed study. Requires --study-dir.")
166 p_sweep.add_argument(
"--reaggregate", action=
"store_true",
167 help=
"Re-run metrics aggregation on a completed study. Requires --study-dir.")
168 p_sweep.add_argument(
"--auto-fetch", action=
"store_true",
169 help=
"Restore cold-storage study members automatically instead of\n"
170 "refusing. Requires a configured storage profile.")
176 @brief Perform add validate parser.
177 @param[in] subparsers Argument passed to `_add_validate_parser()`.
178 @return Value returned by `_add_validate_parser()`.
180 p_validate = subparsers.add_parser(
182 help=
"Validate config files without launching solver/post.",
183 formatter_class=argparse.RawTextHelpFormatter,
185 "Validate one or more config roles. No solver/post execution and no run/study artifact writes.\n\n"
187 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml\n"
188 " picurv validate --post post.yml --cluster cluster.yml\n"
189 " picurv validate --study study.yml --cluster cluster.yml --strict"
191 epilog=
"Next: run `picurv run --dry-run ...` to inspect resolved commands/artifacts.",
193 p_validate.add_argument(
"--case", help=
"Path to case.yml")
194 p_validate.add_argument(
"--solver", help=
"Path to solver.yml")
195 p_validate.add_argument(
"--monitor", help=
"Path to monitor.yml (logging, diagnostics, profiling, and I/O)")
196 p_validate.add_argument(
"--restart-from", help=
"Path to a run directory to validate restart from.")
197 p_validate.add_argument(
"--continue", action=
"store_true", dest=
"continue_run", help=
"Validate continue-in-place mode. Requires --run-dir.")
198 p_validate.add_argument(
"--run-dir", help=
"Path to an existing run directory (for --continue validation).")
199 p_validate.add_argument(
"--post", help=
"Path to post.yml")
200 p_validate.add_argument(
"--cluster", help=
"Path to cluster.yml")
201 p_validate.add_argument(
"--study", help=
"Path to study.yml")
202 p_validate.add_argument(
"--strict", action=
"store_true", help=
"Enable additional strict checks for selected roles.")
207 @brief Attach `precompute` parser for deterministic artifact generation.
208 @param[in] subparsers Argument passed to `_add_precompute_parser()`.
209 @return Configured parser.
211 p_precompute = subparsers.add_parser(
213 help=
"Generate deterministic case artifacts without launching the solver.",
214 formatter_class=argparse.RawTextHelpFormatter,
216 "Generate configured deterministic artifacts, such as grid_gen grids,\n"
217 "generated prescribed-flow inlet profiles, and ic_gen initial conditions,\n"
218 "as immutable objects in the initialized workspace asset store.\n\n"
220 " picurv precompute --case config/case.yml\n"
221 " picurv precompute --case config/case.yml --only grid,initial-condition"
223 epilog=
"Next: inspect the reported asset set, then stage a run that reuses it.",
225 p_precompute.add_argument(
"--case", required=
True, help=
"Path to case.yml containing grid/profile/IC generator settings.")
226 p_precompute.add_argument(
229 help=
"Comma-separated asset kinds (grid, initial-condition, inlet-profiles), or all.",
236 @brief Attach explicit workspace input-ingress commands.
237 @param[in] subparsers Top-level argparse subparser collection.
238 @return Configured inputs command parser.
240 parser = subparsers.add_parser(
242 help=
"Import or register files used by workspace configurations.",
243 formatter_class=argparse.RawTextHelpFormatter,
245 actions = parser.add_subparsers(dest=
"inputs_action", required=
True)
246 p_import = actions.add_parser(
"import", help=
"Import one external file into its canonical workspace home.")
247 p_import.add_argument(
249 choices=WORKSPACE_INPUT_KINDS,
251 p_import.add_argument(
"source", help=
"Existing source file.")
252 p_import.add_argument(
"--name", help=
"Destination name; defaults to the source basename.")
253 p_import.add_argument(
254 "--mode", choices=WORKSPACE_INPUT_MODES, default=
"copy",
255 help=
"How to retain the input. Reference mode records but does not copy an external path.",
257 p_import.add_argument(
"--workspace", help=
"Workspace root; defaults to discovery from the current directory.")
263 @brief Attach unified source/build/workspace-version commands.
264 @param[in] subparsers Top-level argparse subparser collection.
265 @return Version, versions, and source command parsers.
267 p_version = subparsers.add_parser(
269 help=
"Report the active PICurv release and build identity.",
270 formatter_class=argparse.RawTextHelpFormatter,
272 "Report the build identity shared by the Python conductor and the native\n"
273 "executables, plus any workspace version requirement.\n\n"
274 "With the 'status' action this also validates that they agree, and exits 1\n"
275 "when they do not, so a job script can refuse an incoherent build.\n\n"
278 " picurv version status\n"
279 " picurv version status --format json"
282 p_version.add_argument(
283 "version_action", nargs=
"?", choices=[
"status"], default=
None,
284 help=
"'status' validates conductor/executable/workspace coherence and exits 1 on disagreement.",
286 p_version.add_argument(
"--format", dest=
"output_format", choices=
list(CLI_OUTPUT_FORMATS), default=
"text")
288 p_versions = subparsers.add_parser(
"versions", help=
"List, install, or activate PICurv source versions.")
289 actions = p_versions.add_subparsers(dest=
"versions_action", required=
True)
290 actions.add_parser(
"list", help=
"List local Git tags and the active build.")
291 p_install = actions.add_parser(
"install", help=
"Fetch a named version and build it in the source checkout.")
292 p_install.add_argument(
"version")
293 p_activate = actions.add_parser(
"activate", help=
"Checkout and build the version required by this workspace.")
294 p_activate.add_argument(
"version", nargs=
"?", help=
"Version/tag; defaults to the workspace requirement.")
295 p_activate.add_argument(
"--workspace", help=
"Workspace root; defaults to discovery from the current directory.")
297 p_source = subparsers.add_parser(
"source", help=
"Manage the source checkout used by PICurv.")
298 source_actions = p_source.add_subparsers(dest=
"source_action", required=
True)
299 p_update = source_actions.add_parser(
"update", help=
"Fetch source updates without changing the active version.")
300 p_update.add_argument(
"--remote", default=
"origin")
301 return p_version, p_versions, p_source
306 @brief Attach `summarize` parser for read-only run-health views.
307 @param[in] subparsers Argument passed to `_add_summarize_parser()`.
308 @return Value returned by `_add_summarize_parser()`.
310 p_summarize = subparsers.add_parser(
312 help=
"Summarize run configs/health and plot scalar log histories.",
313 formatter_class=argparse.RawTextHelpFormatter,
315 "Build read-only configuration overviews, run-health summaries, and scalar time-history plots.\n"
316 "It does not modify solver output and works for active or completed runs.\n\n"
318 " picurv summarize --run-dir runs/my_run --overview\n"
319 " picurv summarize --run-dir runs/my_run --case --solver\n"
320 " picurv summarize --run-dir runs/my_run --latest\n"
321 " picurv summarize --run-dir runs/my_run --monitor --step 500\n"
322 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
323 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
324 " picurv summarize --run-dir runs/my_run --latest --format json"
326 epilog=
"Next: use `picurv run ...` to create runs or `picurv sweep ...` for multi-case studies.",
328 p_summarize.add_argument(
"--run-dir", required=
True, help=
"Path to the run directory to inspect.")
329 p_summarize.add_argument(
332 help=
"Summarize run metadata plus copied case, solver, and monitor configs without implicitly requesting health.",
334 p_summarize.add_argument(
"--case", action=
"store_true", help=
"Summarize the copied run-local case.yml.")
335 p_summarize.add_argument(
"--solver", action=
"store_true", help=
"Summarize the copied run-local solver.yml.")
336 p_summarize.add_argument(
"--monitor", action=
"store_true", help=
"Summarize the copied run-local monitor.yml.")
337 plot_group = p_summarize.add_mutually_exclusive_group()
338 plot_group.add_argument(
339 "--list-plot-series",
340 dest=
"list_plot_series",
342 help=
"List scalar histories available to --plot.",
344 plot_group.add_argument(
346 dest=
"list_plot_series",
348 help=argparse.SUPPRESS,
350 plot_group.add_argument(
353 help=
"Plot one qualified scalar history, such as momentum.residual_norm (requires matplotlib).",
355 plot_group.add_argument(
357 dest=
"plot_spectrum",
360 help=
"Plot up to six representative measured energy spectra, including the first\n"
361 "and last states, overlaid on the initial-condition spectrum. Takes an optional\n"
362 "task-name substring when the recipe measured more than one spectrum\n"
363 "(requires matplotlib).",
365 p_summarize.add_argument(
"--last", dest=
"last_n", type=int, help=
"Plot only the last N chronological records per line.")
366 p_summarize.add_argument(
"--plot-output", help=
"Save the plot to this path instead of opening an interactive window.")
367 p_summarize.add_argument(
"--linear-y", action=
"store_true", help=
"Force linear y-axis scaling instead of automatic residual/norm log scaling.")
368 step_group = p_summarize.add_mutually_exclusive_group()
369 step_group.add_argument(
"--step", type=int, help=
"Specific completed timestep to summarize.")
370 step_group.add_argument(
373 help=
"Summarize the most recently appended completed step found in available artifacts (default behavior).",
375 step_group.add_argument(
378 help=
"Summarize the numerically largest timestep found in available artifacts.",
380 p_summarize.add_argument(
384 help=
"Number of sampled particle snapshot rows to preview when solver stream output contains them.",
386 p_summarize.add_argument(
388 dest=
"output_format",
389 choices=
list(CLI_OUTPUT_FORMATS),
391 help=
"Output format (default: text).",
398 @brief Perform add cancel parser.
399 @param[in] subparsers Argument passed to `_add_cancel_parser()`.
400 @return Value returned by `_add_cancel_parser()`.
402 p_cancel = subparsers.add_parser(
404 help=
"Cancel Slurm-submitted jobs for an existing run directory.",
405 formatter_class=argparse.RawTextHelpFormatter,
407 "Look up scheduler/submission.json inside an existing run directory and cancel\n"
408 "the recorded Slurm job(s) without requiring manual job-id lookup.\n\n"
410 " picurv cancel --run-dir runs/my_run\n"
411 " picurv cancel --run-dir runs/my_run --stage solve\n"
412 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
413 " picurv cancel --run-dir runs/my_run --dry-run\n\n"
414 "Default cancellation is a hard Slurm cancel (`scancel <job_id>`). With\n"
415 "`--graceful`, Slurm sends SIGUSR1 to the solver process tree so PICurv can write the latest\n"
416 "safe off-cadence step at the next runtime checkpoint before exiting.\n"
417 "Post-process jobs still use ordinary hard cancellation."
419 epilog=
"Next: use `picurv summarize --run-dir ...` to inspect whatever output the run already produced.",
421 p_cancel.add_argument(
"--run-dir", required=
True, help=
"Path to the run directory whose Slurm job(s) should be canceled.")
422 p_cancel.add_argument(
424 choices=
list(RUN_STAGE_CHOICES),
426 help=
"Which recorded stage job(s) to cancel (default: all).",
428 p_cancel.add_argument(
431 help=
"Show which `scancel` command(s) would run without actually canceling anything.",
433 p_cancel.add_argument(
437 "For solver jobs, request a clean runtime shutdown by sending SIGUSR1 to the process tree instead of "
438 "hard-canceling immediately. The solver writes the latest safe off-cadence step at "
439 "the next checkpoint. Non-solver stages still use ordinary scancel."
447 @brief Perform add submit parser.
448 @param[in] subparsers Argument passed to `_add_submit_parser()`.
449 @return Value returned by `_add_submit_parser()`.
451 p_submit = subparsers.add_parser(
453 help=
"Execute or submit previously staged artifacts from an existing run or study directory.",
454 formatter_class=argparse.RawTextHelpFormatter,
456 "Consume an existing artifact set created by picurv --no-submit and\n"
457 "execute/submit it later without regenerating configs or scripts.\n\n"
459 " picurv submit --run-dir runs/my_run\n"
460 " picurv submit --run-dir runs/my_run --stage solve\n"
461 " picurv submit --study-dir studies/my_study --dry-run"
463 epilog=
"Next: use `picurv summarize --run-dir ...` or `picurv cancel --run-dir ...` after submission as needed.",
465 target_group = p_submit.add_mutually_exclusive_group(required=
True)
466 target_group.add_argument(
"--run-dir", help=
"Path to a staged run directory created by `picurv run ... --no-submit`.")
467 target_group.add_argument(
"--study-dir", help=
"Path to a staged study directory created by `picurv sweep --cluster ... --no-submit`.")
468 p_submit.add_argument(
470 choices=
list(RUN_STAGE_CHOICES),
472 help=
"Which staged job(s) to submit (default: all).",
474 p_submit.add_argument(
477 help=
"Allow re-submitting a stage already marked as submitted in scheduler/submission.json.",
479 p_submit.add_argument(
482 help=
"Show which local command(s) or `sbatch` command(s) would run without starting anything.",
489 @brief Perform add init parser.
490 @param[in] subparsers Argument passed to `_add_init_parser()`.
491 @return Value returned by `_add_init_parser()`.
493 p_init = subparsers.add_parser(
495 help=
"Initialize a new case study directory from a template.",
496 formatter_class=argparse.RawTextHelpFormatter,
498 "Create a study directory from examples/<template_name>.\n\n"
500 " picurv init flat_channel --dest my_case\n"
501 " picurv init bent_channel --dest my_bent_case\n"
502 " picurv init decaying_isotropic_turbulence --dest dit_case"
504 epilog=
"Next: run `picurv validate --case ... --solver ... --monitor ...` before execution.",
506 p_init.add_argument(
"template_name", help=
"Name of the case template directory to copy (e.g., 'flat_channel').")
510 help=
"Optional name for the new directory. Defaults to the template name.\nPath is relative to your current working directory.",
514 help=
"Optional override for the PICurv source repository root.\nUseful when running from a copied case without metadata.",
520 help=
"Copy simulator and postprocessor into the case directory.\nUse this to freeze specific binary versions for reproducibility\nor to protect running jobs from concurrent rebuilds.",
527 @brief Perform add build parser.
528 @param[in] subparsers Argument passed to `_add_build_parser()`.
529 @return Value returned by `_add_build_parser()`.
531 p_build = subparsers.add_parser(
533 help=
"Build project executables using the Makefile.",
534 formatter_class=argparse.RawTextHelpFormatter,
536 "Calls the project's Makefile directly through `make`.\n"
537 "If you do not pass an explicit make target, `picurv build` runs `make all`.\n"
538 "Any arguments provided after 'build' are passed directly to make.\n\n"
541 " picurv build clean-project\n"
542 " picurv build SYSTEM=cluster\n"
543 " picurv build all SYSTEM=cluster\n"
544 " picurv build postprocessor\n"
545 " ./picurv build clean-project # from an initialized case directory"
547 epilog=
"Next: run `picurv --help` or `picurv run --help` for execution commands.",
549 p_build.add_argument(
551 help=
"Optional override for the PICurv source repository root.",
553 p_build.add_argument(
555 help=
"Optional case directory used to resolve .picurv-origin.json when not running from that case.",
557 p_build.add_argument(
559 nargs=argparse.REMAINDER,
560 help=
"Arguments to pass directly to the make command (e.g., 'clean-project').",
567 @brief Perform add sync config parser.
568 @param[in] subparsers Argument passed to `_add_sync_config_parser()`.
569 @return Value returned by `_add_sync_config_parser()`.
571 p_sync_config = subparsers.add_parser(
573 help=
"Refresh template-managed files in a case directory from examples/<template>.",
574 formatter_class=argparse.RawTextHelpFormatter,
576 "Copy updated example template files into an existing case directory.\n"
577 "Modified files are preserved unless --overwrite is used.\n"
578 "--prune removes only files that were previously tracked as template-managed and\n"
579 "have since been removed from the source template.\n\n"
581 " ./picurv sync-config\n"
582 " ./picurv sync-config --overwrite\n"
583 " ./picurv sync-config --prune\n"
584 " ./bin/picurv sync-config --case-dir my_case --template-name flat_channel"
586 epilog=
"Next: run `picurv validate ...` after syncing configs.",
588 p_sync_config.add_argument(
"--case-dir", help=
"Optional case directory to refresh. Defaults to the current case.")
589 p_sync_config.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
590 p_sync_config.add_argument(
592 help=
"Optional template name override (e.g., flat_channel). Required when metadata is absent.",
594 p_sync_config.add_argument(
"--overwrite", action=
"store_true", help=
"Overwrite case files even when they differ from the template.")
595 p_sync_config.add_argument(
598 help=
"Remove stale files that were previously tracked as template-managed but no longer exist in the source template.",
605 @brief Perform add pull source parser.
606 @param[in] subparsers Argument passed to `_add_pull_source_parser()`.
607 @return Value returned by `_add_pull_source_parser()`.
609 p_pull = subparsers.add_parser(
611 help=
"Refresh source branches from an initialized case directory.",
612 formatter_class=argparse.RawTextHelpFormatter,
614 "Update the source repository without leaving an initialized case directory.\n\n"
615 "By default this refreshes every local branch that tracks an upstream,\n"
616 "then restores the branch you started on.\n\n"
618 " ./picurv pull-source\n"
619 " ./picurv pull-source --current-branch-only\n"
620 " ./picurv pull-source --no-rebase\n"
621 " ./picurv pull-source --remote origin --branch main"
623 epilog=
"Next: run `./picurv build` if source changes require rebuilt executables.",
625 p_pull.add_argument(
"--case-dir", help=
"Optional case directory used to resolve .picurv-origin.json.")
626 p_pull.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
627 p_pull.add_argument(
"--remote", help=
"Optional git remote name (e.g., origin).")
628 p_pull.add_argument(
"--branch", help=
"Optional branch name. If provided without --remote, origin is assumed.")
630 "--current-branch-only",
632 help=
"Only pull the currently checked out branch instead of iterating across all local tracking branches.",
634 p_pull.add_argument(
"--no-rebase", action=
"store_true", help=
"Use plain git pull instead of git pull --rebase.")
640 @brief Perform add status source parser.
641 @param[in] subparsers Argument passed to `_add_status_source_parser()`.
642 @return Value returned by `_add_status_source_parser()`.
644 p_status = subparsers.add_parser(
646 help=
"Report source/case drift for an initialized case directory.",
647 formatter_class=argparse.RawTextHelpFormatter,
649 "Inspect whether the source repo, copied binaries, and template-managed files have drifted\n"
650 "from the current case directory.\n\n"
652 " ./picurv status-source\n"
653 " ./picurv status-source --format json\n"
654 " ./bin/picurv status-source --case-dir my_case"
656 epilog=
"Next: use `pull-source`, `build`, or `sync-config` based on the reported drift.",
658 p_status.add_argument(
"--case-dir", help=
"Optional case directory used to resolve .picurv-origin.json.")
659 p_status.add_argument(
"--source-root", help=
"Optional override for the PICurv source repository root.")
660 p_status.add_argument(
"--template-name", help=
"Optional template name override when metadata is absent.")
661 p_status.add_argument(
663 dest=
"output_format",
664 choices=
list(CLI_OUTPUT_FORMATS),
666 help=
"Output format (default: text).",
673 @brief Build and return the top-level CLI parser.
674 @return Value returned by `build_main_parser()`.
676 parser = argparse.ArgumentParser(
677 description=
"picurv: A comprehensive conductor for the PICurv simulation platform.",
678 formatter_class=argparse.RawTextHelpFormatter,
681 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml\n"
682 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --dry-run\n"
683 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --no-submit\n"
684 " picurv precompute --case config/case.yml --only grid,initial-condition\n"
685 " picurv run --post-process --continue --run-dir runs/my_run --post post.yml\n"
686 " picurv summarize --run-dir runs/my_run --latest\n"
687 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
688 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
689 " picurv submit --run-dir runs/my_run\n"
690 " picurv cancel --run-dir runs/my_run\n"
691 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
692 " picurv sweep --study study.yml --cluster cluster.yml\n\n"
694 " - First run: picurv init ... -> picurv validate ... -> picurv run ...\n"
695 " - Config debugging: picurv validate ...\n"
696 " - Artifact generation: picurv precompute ...\n"
697 " - Launch planning: picurv run ... --dry-run\n"
698 " - Post-only catch-up: picurv run --post-process --continue --run-dir ... --post ...\n"
699 " - Deferred submission: picurv submit --run-dir ...\n"
700 " - Run inspection/plots: picurv summarize ...\n"
701 " - Run cancellation: picurv cancel --run-dir ...; use --graceful for solver final-output shutdown"
704 parser.add_argument(
"-v",
"--version", action=
"version", version=f
"picurv {PICURV_VERSION}")
705 subparsers = parser.add_subparsers(dest=
"command", required=
True, help=
"Available commands")
720 add_storage_parser(subparsers)
724def dispatch_command(args):
726 @brief Validate argument combinations and dispatch to command handlers.
727 @param[in] args Command-line style argument list supplied to the function.
729 if args.command ==
"run":
730 if not args.solve
and not args.post_process:
731 fail_cli_usage(
"At least one stage (--solve or --post-process) must be selected.")
732 if args.solve
and (
not args.case
or not args.solver
or not args.monitor):
733 fail_cli_usage(
"--solve requires --case, --solver, and --monitor.")
734 if args.post_process
and not args.post:
736 if getattr(args,
"only",
None)
and not args.post_process:
737 fail_cli_usage(
"--only selects post-processing stages and requires --post-process.")
738 if args.scheduler
and not args.cluster:
742 if args.command ==
"sweep":
743 if args.continue_study:
744 if not args.study_dir:
749 if not args.study_dir:
757 fail_cli_usage(
"--cluster is required when launching a new sweep.")
760 if args.command ==
"validate":
763 if args.command ==
"precompute":
766 except ValueError
as e:
768 ERROR_CODE_CFG_INVALID_VALUE,
770 file_path=getattr(args,
"case",
"-"),
775 if args.command ==
"inputs":
778 except ValueError
as exc:
779 print(f
"[FATAL] {exc}", file=sys.stderr)
782 if args.command ==
"version":
785 if args.command ==
"versions":
788 except ValueError
as exc:
789 print(f
"[FATAL] {exc}", file=sys.stderr)
792 if args.command ==
"source":
795 except ValueError
as exc:
796 print(f
"[FATAL] {exc}", file=sys.stderr)
799 if args.command ==
"summarize":
802 if args.command ==
"submit":
805 if args.command ==
"cancel":
808 if args.command ==
"init":
811 if args.command ==
"build":
814 if args.command ==
"sync-config":
817 if args.command ==
"pull-source":
820 if args.command ==
"status-source":
823 if args.command ==
"storage":
824 storage_workflow(args)
_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_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.
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.
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.