PICurv 0.1.0
A Parallel Particle-In-Cell Solver for Curvilinear LES
Loading...
Searching...
No Matches
cli.py
Go to the documentation of this file.
1"""Argument parser construction and command dispatch for PICurv."""
2
3from .core import *
4
5
6# ==============================================================================
7# MAIN COMMAND-LINE INTERFACE PARSER
8# ==============================================================================
9
10def _add_run_parser(subparsers):
11 """!
12 @brief Attach `run` parser with staged execution and dry-run support.
13 @param[in] subparsers Argument passed to `_add_run_parser()`.
14 @return Value returned by `_add_run_parser()`.
15 """
16 p_run = subparsers.add_parser(
17 "run",
18 help="Execute a simulation workflow (solve and/or post-process).",
19 formatter_class=argparse.RawTextHelpFormatter,
20 description=(
21 "Execute solver and/or post-processing stages.\n\n"
22 "Notes:\n"
23 " - --num-procs applies to solver stage launches.\n"
24 " - Post-processing defaults to a single MPI rank/task.\n"
25 " - With --solve, --continue resumes the existing run directory in-place.\n"
26 " - With --post-process, --continue resumes the same recipe from the first unfinished step\n"
27 " and caps the launch to the highest fully available contiguous source frontier.\n\n"
28 "Diagnostics:\n"
29 " - PETSc and runtime memory diagnostics live under monitor.yml -> diagnostics.\n"
30 " - Use --dry-run to inspect resolved PETSc flags and expected log destinations.\n\n"
31 "Examples:\n"
32 " picurv run --solve -n 8 --case case.yml --solver solver.yml --monitor monitor.yml\n"
33 " picurv run --solve --restart-from runs/old_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
34 " picurv run --solve --continue --run-dir runs/my_run --case case.yml --solver solver.yml --monitor monitor.yml\n"
35 " picurv run --post-process --run-dir runs/my_run --post post.yml\n"
36 " picurv run --post-process --continue --run-dir runs/my_run --post post.yml\n"
37 " picurv run --solve --case case.yml --solver solver.yml --monitor monitor.yml --dry-run"
38 ),
39 epilog="Next: run `picurv validate ...` first for config-only checks.",
40 )
41 run_group = p_run.add_argument_group("stages")
42 run_group.add_argument("--solve", action="store_true", help="Execute the solver stage (creates a new run directory).")
43 run_group.add_argument("--post-process", action="store_true", help="Execute the post-processing stage on a run directory.")
44
45 solver_group = p_run.add_argument_group("solver inputs (required for --solve)")
46 solver_group.add_argument("--case", help="Path to the case definition file (e.g., case.yml).")
47 solver_group.add_argument("--solver", help="Path to the solver settings profile (e.g., solver.yml).")
48 solver_group.add_argument("--monitor", help="Path to the monitoring, diagnostics, and I/O profile (e.g., monitor.yml).")
49 solver_group.add_argument(
50 "--restart-from",
51 help="Path to an existing run directory to restart from.\n"
52 "Creates a new run directory and copies/references checkpoint data from the source.",
53 )
54 run_group.add_argument(
55 "--continue",
56 action="store_true",
57 dest="continue_run",
58 help="Resume an existing run directory in-place. Requires --run-dir.\n"
59 "With --solve, appends to existing solver output/logs.\n"
60 "With --post-process, resumes the same recipe from the first unfinished step\n"
61 "and skips already-complete work inside the current live source frontier.",
62 )
63
64 post_group = p_run.add_argument_group("post-processor inputs (required for --post-process)")
65 post_group.add_argument("--run-dir", help="Path to an existing run directory.\n(Used with --post-process or --continue).")
66 post_group.add_argument("--post", help="Path to the post-processing recipe file (e.g., post.yml).")
67
68 p_run.add_argument(
69 "-n",
70 "--num-procs",
71 type=int,
72 default=1,
73 help="Number of MPI processes for the solver stage. Post-processing defaults to 1 rank.",
74 )
75 p_run.add_argument("--cluster", help="Path to cluster.yml for Slurm execution mode.")
76 p_run.add_argument("--scheduler", help="Explicit scheduler selector (currently 'slurm').")
77 p_run.add_argument("--no-submit", action="store_true", help="Stage run artifacts without starting local execution or Slurm submission.")
78 p_run.add_argument(
79 "--dry-run",
80 action="store_true",
81 help="Resolve and print planned commands/artifacts, including diagnostic flags and log paths, without writing files.",
82 )
83 p_run.add_argument(
84 "--format",
85 dest="output_format",
86 choices=["text", "json"],
87 default="text",
88 help="Output format for --dry-run (default: text).",
89 )
90 return p_run
91
92
93def _add_sweep_parser(subparsers):
94 """!
95 @brief Perform add sweep parser.
96 @param[in] subparsers Argument passed to `_add_sweep_parser()`.
97 @return Value returned by `_add_sweep_parser()`.
98 """
99 p_sweep = subparsers.add_parser(
100 "sweep",
101 help="Launch or continue a Slurm-based parameter sweep/study.",
102 formatter_class=argparse.RawTextHelpFormatter,
103 description=(
104 "Launch studies from study.yml + cluster.yml, whether the study uses\n"
105 "cross-product parameter combinations or explicit coupled parameter_sets, continue\n"
106 "partially-completed studies, or re-aggregate metrics.\n\n"
107 "Examples:\n"
108 " picurv sweep --study study.yml --cluster cluster.yml\n"
109 " picurv sweep --study study.yml --cluster cluster.yml --no-submit\n"
110 " picurv sweep --continue --study-dir studies/<id>\n"
111 " picurv sweep --continue --study-dir studies/<id> --cluster cluster_more_time.yml\n"
112 " picurv sweep --reaggregate --study-dir studies/<id>"
113 ),
114 epilog=(
115 "Study files support either `parameters` cross-product expansion or explicit `parameter_sets`.\n"
116 "Next: inspect studies/<study_id>/results/metrics_table.csv for aggregated metrics."
117 ),
118 )
119 p_sweep.add_argument("--study", help="Path to study.yml defining either a `parameters` cross-product expansion or explicit parameter_sets, plus metrics.")
120 p_sweep.add_argument("--cluster", help="Path to cluster.yml defining Slurm resources.")
121 p_sweep.add_argument("--no-submit", action="store_true", help="Generate all study artifacts without submitting jobs.")
122 p_sweep.add_argument("--study-dir", help="Path to an existing study directory (for --continue/--reaggregate).")
123 p_sweep.add_argument("--continue", action="store_true", dest="continue_study",
124 help="Continue a partially-completed study. Requires --study-dir.")
125 p_sweep.add_argument("--reaggregate", action="store_true",
126 help="Re-run metrics aggregation on a completed study. Requires --study-dir.")
127 return p_sweep
128
129
130def _add_validate_parser(subparsers):
131 """!
132 @brief Perform add validate parser.
133 @param[in] subparsers Argument passed to `_add_validate_parser()`.
134 @return Value returned by `_add_validate_parser()`.
135 """
136 p_validate = subparsers.add_parser(
137 "validate",
138 help="Validate config files without launching solver/post.",
139 formatter_class=argparse.RawTextHelpFormatter,
140 description=(
141 "Validate one or more config roles. No solver/post execution and no run/study artifact writes.\n\n"
142 "Examples:\n"
143 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml\n"
144 " picurv validate --post post.yml --cluster cluster.yml\n"
145 " picurv validate --study study.yml --cluster cluster.yml --strict"
146 ),
147 epilog="Next: run `picurv run --dry-run ...` to inspect resolved commands/artifacts.",
148 )
149 p_validate.add_argument("--case", help="Path to case.yml")
150 p_validate.add_argument("--solver", help="Path to solver.yml")
151 p_validate.add_argument("--monitor", help="Path to monitor.yml (logging, diagnostics, profiling, and I/O)")
152 p_validate.add_argument("--restart-from", help="Path to a run directory to validate restart from.")
153 p_validate.add_argument("--continue", action="store_true", dest="continue_run", help="Validate continue-in-place mode. Requires --run-dir.")
154 p_validate.add_argument("--run-dir", help="Path to an existing run directory (for --continue validation).")
155 p_validate.add_argument("--post", help="Path to post.yml")
156 p_validate.add_argument("--cluster", help="Path to cluster.yml")
157 p_validate.add_argument("--study", help="Path to study.yml")
158 p_validate.add_argument("--strict", action="store_true", help="Enable additional strict checks for selected roles.")
159 return p_validate
160
162 """!
163 @brief Attach `precompute` parser for deterministic artifact generation.
164 @param[in] subparsers Argument passed to `_add_precompute_parser()`.
165 @return Configured parser.
166 """
167 p_precompute = subparsers.add_parser(
168 "precompute",
169 help="Generate deterministic case artifacts without launching the solver.",
170 formatter_class=argparse.RawTextHelpFormatter,
171 description=(
172 "Generate configured deterministic artifacts, such as grid_gen grids,\n"
173 "generated prescribed-flow inlet profiles, and ic_gen initial conditions,\n"
174 "into a run-like config layout.\n\n"
175 "Examples:\n"
176 " picurv precompute --case case.yml\n"
177 " picurv precompute --case case.yml --output-dir precomputed/channel"
178 ),
179 epilog="Next: inspect generated artifacts or run solve with the configured generated modes directly.",
180 )
181 p_precompute.add_argument("--case", required=True, help="Path to case.yml containing grid/profile/IC generator settings.")
182 p_precompute.add_argument(
183 "--output-dir",
184 help="Directory where precomputed artifacts should be written. Defaults to precomputed/<case-name>.",
185 )
186 return p_precompute
187
188
189def _add_summarize_parser(subparsers):
190 """!
191 @brief Attach `summarize` parser for read-only run-health views.
192 @param[in] subparsers Argument passed to `_add_summarize_parser()`.
193 @return Value returned by `_add_summarize_parser()`.
194 """
195 p_summarize = subparsers.add_parser(
196 "summarize",
197 help="Summarize run configs/health and plot scalar log histories.",
198 formatter_class=argparse.RawTextHelpFormatter,
199 description=(
200 "Build read-only configuration overviews, run-health summaries, and scalar time-history plots.\n"
201 "It does not modify solver output and works for active or completed runs.\n\n"
202 "Examples:\n"
203 " picurv summarize --run-dir runs/my_run --overview\n"
204 " picurv summarize --run-dir runs/my_run --case --solver\n"
205 " picurv summarize --run-dir runs/my_run --latest\n"
206 " picurv summarize --run-dir runs/my_run --monitor --step 500\n"
207 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
208 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
209 " picurv summarize --run-dir runs/my_run --latest --format json"
210 ),
211 epilog="Next: use `picurv run ...` to create runs or `picurv sweep ...` for multi-case studies.",
212 )
213 p_summarize.add_argument("--run-dir", required=True, help="Path to the run directory to inspect.")
214 p_summarize.add_argument(
215 "--overview",
216 action="store_true",
217 help="Summarize run metadata plus copied case, solver, and monitor configs without implicitly requesting health.",
218 )
219 p_summarize.add_argument("--case", action="store_true", help="Summarize the copied run-local case.yml.")
220 p_summarize.add_argument("--solver", action="store_true", help="Summarize the copied run-local solver.yml.")
221 p_summarize.add_argument("--monitor", action="store_true", help="Summarize the copied run-local monitor.yml.")
222 plot_group = p_summarize.add_mutually_exclusive_group()
223 plot_group.add_argument(
224 "--list-plot-series",
225 dest="list_plot_series",
226 action="store_true",
227 help="List scalar time histories available to --plot.",
228 )
229 plot_group.add_argument(
230 "--list-series",
231 dest="list_plot_series",
232 action="store_true",
233 help=argparse.SUPPRESS,
234 )
235 plot_group.add_argument(
236 "--plot",
237 dest="plot_series",
238 help="Plot one qualified scalar history, such as momentum.residual_norm (requires matplotlib).",
239 )
240 p_summarize.add_argument("--last", dest="last_n", type=int, help="Plot only the last N chronological records per line.")
241 p_summarize.add_argument("--plot-output", help="Save the plot to this path instead of opening an interactive window.")
242 p_summarize.add_argument("--linear-y", action="store_true", help="Force linear y-axis scaling instead of automatic residual/norm log scaling.")
243 step_group = p_summarize.add_mutually_exclusive_group()
244 step_group.add_argument("--step", type=int, help="Specific completed timestep to summarize.")
245 step_group.add_argument(
246 "--latest",
247 action="store_true",
248 help="Summarize the most recently appended completed step found in available artifacts (default behavior).",
249 )
250 step_group.add_argument(
251 "--max-step",
252 action="store_true",
253 help="Summarize the numerically largest timestep found in available artifacts.",
254 )
255 p_summarize.add_argument(
256 "--snapshot-rows",
257 type=int,
258 default=5,
259 help="Number of sampled particle snapshot rows to preview when solver stream output contains them.",
260 )
261 p_summarize.add_argument(
262 "--format",
263 dest="output_format",
264 choices=["text", "json"],
265 default="text",
266 help="Output format (default: text).",
267 )
268 return p_summarize
269
270
271def _add_cancel_parser(subparsers):
272 """!
273 @brief Perform add cancel parser.
274 @param[in] subparsers Argument passed to `_add_cancel_parser()`.
275 @return Value returned by `_add_cancel_parser()`.
276 """
277 p_cancel = subparsers.add_parser(
278 "cancel",
279 help="Cancel Slurm-submitted jobs for an existing run directory.",
280 formatter_class=argparse.RawTextHelpFormatter,
281 description=(
282 "Look up scheduler/submission.json inside an existing run directory and cancel\n"
283 "the recorded Slurm job(s) without requiring manual job-id lookup.\n\n"
284 "Examples:\n"
285 " picurv cancel --run-dir runs/my_run\n"
286 " picurv cancel --run-dir runs/my_run --stage solve\n"
287 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
288 " picurv cancel --run-dir runs/my_run --dry-run\n\n"
289 "Default cancellation is a hard Slurm cancel (`scancel <job_id>`). With\n"
290 "`--graceful`, solver jobs receive SIGUSR1 so PICurv can write the latest\n"
291 "safe off-cadence step at the next runtime checkpoint before exiting.\n"
292 "Post-process jobs still use ordinary hard cancellation."
293 ),
294 epilog="Next: use `picurv summarize --run-dir ...` to inspect whatever output the run already produced.",
295 )
296 p_cancel.add_argument("--run-dir", required=True, help="Path to the run directory whose Slurm job(s) should be canceled.")
297 p_cancel.add_argument(
298 "--stage",
299 choices=["all", "solve", "post-process"],
300 default="all",
301 help="Which recorded stage job(s) to cancel (default: all).",
302 )
303 p_cancel.add_argument(
304 "--dry-run",
305 action="store_true",
306 help="Show which `scancel` command(s) would run without actually canceling anything.",
307 )
308 p_cancel.add_argument(
309 "--graceful",
310 action="store_true",
311 help=(
312 "For solver jobs, request a clean runtime shutdown by sending SIGUSR1 instead of "
313 "hard-canceling immediately. The solver writes the latest safe off-cadence step at "
314 "the next checkpoint. Non-solver stages still use ordinary scancel."
315 ),
316 )
317 return p_cancel
318
319
320def _add_submit_parser(subparsers):
321 """!
322 @brief Perform add submit parser.
323 @param[in] subparsers Argument passed to `_add_submit_parser()`.
324 @return Value returned by `_add_submit_parser()`.
325 """
326 p_submit = subparsers.add_parser(
327 "submit",
328 help="Execute or submit previously staged artifacts from an existing run or study directory.",
329 formatter_class=argparse.RawTextHelpFormatter,
330 description=(
331 "Consume an existing artifact set created by picurv --no-submit and\n"
332 "execute/submit it later without regenerating configs or scripts.\n\n"
333 "Examples:\n"
334 " picurv submit --run-dir runs/my_run\n"
335 " picurv submit --run-dir runs/my_run --stage solve\n"
336 " picurv submit --study-dir studies/my_study --dry-run"
337 ),
338 epilog="Next: use `picurv summarize --run-dir ...` or `picurv cancel --run-dir ...` after submission as needed.",
339 )
340 target_group = p_submit.add_mutually_exclusive_group(required=True)
341 target_group.add_argument("--run-dir", help="Path to a staged run directory created by `picurv run ... --no-submit`.")
342 target_group.add_argument("--study-dir", help="Path to a staged study directory created by `picurv sweep --cluster ... --no-submit`.")
343 p_submit.add_argument(
344 "--stage",
345 choices=["all", "solve", "post-process"],
346 default="all",
347 help="Which staged job(s) to submit (default: all).",
348 )
349 p_submit.add_argument(
350 "--force",
351 action="store_true",
352 help="Allow re-submitting a stage already marked as submitted in scheduler/submission.json.",
353 )
354 p_submit.add_argument(
355 "--dry-run",
356 action="store_true",
357 help="Show which local command(s) or `sbatch` command(s) would run without starting anything.",
358 )
359 return p_submit
360
361
362def _add_init_parser(subparsers):
363 """!
364 @brief Perform add init parser.
365 @param[in] subparsers Argument passed to `_add_init_parser()`.
366 @return Value returned by `_add_init_parser()`.
367 """
368 p_init = subparsers.add_parser(
369 "init",
370 help="Initialize a new case study directory from a template.",
371 formatter_class=argparse.RawTextHelpFormatter,
372 description=(
373 "Create a study directory from examples/<template_name>.\n\n"
374 "Examples:\n"
375 " picurv init flat_channel --dest my_case\n"
376 " picurv init bent_channel --dest my_bent_case"
377 ),
378 epilog="Next: run `picurv validate --case ... --solver ... --monitor ...` before execution.",
379 )
380 p_init.add_argument("template_name", help="Name of the case template directory to copy (e.g., 'flat_channel').")
381 p_init.add_argument(
382 "--dest",
383 dest="dest_name",
384 help="Optional name for the new directory. Defaults to the template name.\nPath is relative to your current working directory.",
385 )
386 p_init.add_argument(
387 "--source-root",
388 help="Optional override for the PICurv source repository root.\nUseful when running from a copied case without metadata.",
389 )
390 p_init.add_argument(
391 "--pin-binaries",
392 action="store_true",
393 default=False,
394 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.",
395 )
396 return p_init
397
398
399def _add_build_parser(subparsers):
400 """!
401 @brief Perform add build parser.
402 @param[in] subparsers Argument passed to `_add_build_parser()`.
403 @return Value returned by `_add_build_parser()`.
404 """
405 p_build = subparsers.add_parser(
406 "build",
407 help="Build project executables using the Makefile.",
408 formatter_class=argparse.RawTextHelpFormatter,
409 description=(
410 "Calls the project's Makefile directly through `make`.\n"
411 "If you do not pass an explicit make target, `picurv build` runs `make all`.\n"
412 "Any arguments provided after 'build' are passed directly to make.\n\n"
413 "Examples:\n"
414 " picurv build\n"
415 " picurv build clean-project\n"
416 " picurv build SYSTEM=cluster\n"
417 " picurv build all SYSTEM=cluster\n"
418 " picurv build postprocessor\n"
419 " ./picurv build clean-project # from an initialized case directory"
420 ),
421 epilog="Next: run `picurv --help` or `picurv run --help` for execution commands.",
422 )
423 p_build.add_argument(
424 "--source-root",
425 help="Optional override for the PICurv source repository root.",
426 )
427 p_build.add_argument(
428 "--case-dir",
429 help="Optional case directory used to resolve .picurv-origin.json when not running from that case.",
430 )
431 p_build.add_argument(
432 "make_args",
433 nargs=argparse.REMAINDER,
434 help="Arguments to pass directly to the make command (e.g., 'clean-project').",
435 )
436 return p_build
437
438
440 """!
441 @brief Perform add sync binaries parser.
442 @param[in] subparsers Argument passed to `_add_sync_binaries_parser()`.
443 @return Value returned by `_add_sync_binaries_parser()`.
444 """
445 p_sync_binaries = subparsers.add_parser(
446 "sync-binaries",
447 help="Pin specific binary versions into a case directory (optional).",
448 formatter_class=argparse.RawTextHelpFormatter,
449 description=(
450 "Copy simulator/postprocessor from the source repo bin/ into a case directory.\n"
451 "This is optional — binaries are normally resolved via PATH. Use this only\n"
452 "when you need to pin a specific build into a case for reproducibility.\n\n"
453 "Examples:\n"
454 " picurv sync-binaries --case-dir my_case\n"
455 " picurv sync-binaries --source-root /path/to/PICurv"
456 ),
457 epilog="Next: run `./picurv build ...` first if the source repo bin/ is out of date.",
458 )
459 p_sync_binaries.add_argument("--case-dir", help="Optional case directory to refresh. Defaults to the current case.")
460 p_sync_binaries.add_argument("--source-root", help="Optional override for the PICurv source repository root.")
461 return p_sync_binaries
462
463
465 """!
466 @brief Perform add sync config parser.
467 @param[in] subparsers Argument passed to `_add_sync_config_parser()`.
468 @return Value returned by `_add_sync_config_parser()`.
469 """
470 p_sync_config = subparsers.add_parser(
471 "sync-config",
472 help="Refresh template-managed files in a case directory from examples/<template>.",
473 formatter_class=argparse.RawTextHelpFormatter,
474 description=(
475 "Copy updated example template files into an existing case directory.\n"
476 "Modified files are preserved unless --overwrite is used.\n"
477 "--prune removes only files that were previously tracked as template-managed and\n"
478 "have since been removed from the source template.\n\n"
479 "Examples:\n"
480 " ./picurv sync-config\n"
481 " ./picurv sync-config --overwrite\n"
482 " ./picurv sync-config --prune\n"
483 " ./bin/picurv sync-config --case-dir my_case --template-name flat_channel"
484 ),
485 epilog="Next: run `picurv validate ...` after syncing configs.",
486 )
487 p_sync_config.add_argument("--case-dir", help="Optional case directory to refresh. Defaults to the current case.")
488 p_sync_config.add_argument("--source-root", help="Optional override for the PICurv source repository root.")
489 p_sync_config.add_argument(
490 "--template-name",
491 help="Optional template name override (e.g., flat_channel). Required when metadata is absent.",
492 )
493 p_sync_config.add_argument("--overwrite", action="store_true", help="Overwrite case files even when they differ from the template.")
494 p_sync_config.add_argument(
495 "--prune",
496 action="store_true",
497 help="Remove stale files that were previously tracked as template-managed but no longer exist in the source template.",
498 )
499 return p_sync_config
500
501
503 """!
504 @brief Perform add pull source parser.
505 @param[in] subparsers Argument passed to `_add_pull_source_parser()`.
506 @return Value returned by `_add_pull_source_parser()`.
507 """
508 p_pull = subparsers.add_parser(
509 "pull-source",
510 help="Refresh source branches from an initialized case directory.",
511 formatter_class=argparse.RawTextHelpFormatter,
512 description=(
513 "Update the source repository without leaving an initialized case directory.\n\n"
514 "By default this refreshes every local branch that tracks an upstream,\n"
515 "then restores the branch you started on.\n\n"
516 "Examples:\n"
517 " ./picurv pull-source\n"
518 " ./picurv pull-source --current-branch-only\n"
519 " ./picurv pull-source --no-rebase\n"
520 " ./picurv pull-source --remote origin --branch main"
521 ),
522 epilog="Next: run `./picurv build` and `./picurv sync-binaries` if source changes require rebuilt executables.",
523 )
524 p_pull.add_argument("--case-dir", help="Optional case directory used to resolve .picurv-origin.json.")
525 p_pull.add_argument("--source-root", help="Optional override for the PICurv source repository root.")
526 p_pull.add_argument("--remote", help="Optional git remote name (e.g., origin).")
527 p_pull.add_argument("--branch", help="Optional branch name. If provided without --remote, origin is assumed.")
528 p_pull.add_argument(
529 "--current-branch-only",
530 action="store_true",
531 help="Only pull the currently checked out branch instead of iterating across all local tracking branches.",
532 )
533 p_pull.add_argument("--no-rebase", action="store_true", help="Use plain git pull instead of git pull --rebase.")
534 return p_pull
535
536
538 """!
539 @brief Perform add status source parser.
540 @param[in] subparsers Argument passed to `_add_status_source_parser()`.
541 @return Value returned by `_add_status_source_parser()`.
542 """
543 p_status = subparsers.add_parser(
544 "status-source",
545 help="Report source/case drift for an initialized case directory.",
546 formatter_class=argparse.RawTextHelpFormatter,
547 description=(
548 "Inspect whether the source repo, copied binaries, and template-managed files have drifted\n"
549 "from the current case directory.\n\n"
550 "Examples:\n"
551 " ./picurv status-source\n"
552 " ./picurv status-source --format json\n"
553 " ./bin/picurv status-source --case-dir my_case"
554 ),
555 epilog="Next: use `pull-source`, `build`, `sync-binaries`, or `sync-config` based on the reported drift.",
556 )
557 p_status.add_argument("--case-dir", help="Optional case directory used to resolve .picurv-origin.json.")
558 p_status.add_argument("--source-root", help="Optional override for the PICurv source repository root.")
559 p_status.add_argument("--template-name", help="Optional template name override when metadata is absent.")
560 p_status.add_argument(
561 "--format",
562 dest="output_format",
563 choices=["text", "json"],
564 default="text",
565 help="Output format (default: text).",
566 )
567 return p_status
568
569
571 """!
572 @brief Build and return the top-level CLI parser.
573 @return Value returned by `build_main_parser()`.
574 """
575 parser = argparse.ArgumentParser(
576 description="picurv: A comprehensive conductor for the PICurv simulation platform.",
577 formatter_class=argparse.RawTextHelpFormatter,
578 epilog=(
579 "Examples:\n"
580 " picurv validate --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml\n"
581 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --dry-run\n"
582 " picurv run --solve --post-process --case case.yml --solver solver.yml --monitor monitor.yml --post post.yml --no-submit\n"
583 " picurv precompute --case case.yml --output-dir precomputed/my_case\n"
584 " picurv run --post-process --continue --run-dir runs/my_run --post post.yml\n"
585 " picurv summarize --run-dir runs/my_run --latest\n"
586 " picurv summarize --run-dir runs/my_run --list-plot-series\n"
587 " picurv summarize --run-dir runs/my_run --plot momentum.residual_norm --last 100\n"
588 " picurv submit --run-dir runs/my_run\n"
589 " picurv cancel --run-dir runs/my_run\n"
590 " picurv cancel --run-dir runs/my_run --stage solve --graceful\n"
591 " picurv sweep --study study.yml --cluster cluster.yml\n\n"
592 "Next commands:\n"
593 " - First run: picurv init ... -> picurv validate ... -> picurv run ...\n"
594 " - Config debugging: picurv validate ...\n"
595 " - Artifact generation: picurv precompute ...\n"
596 " - Launch planning: picurv run ... --dry-run\n"
597 " - Post-only catch-up: picurv run --post-process --continue --run-dir ... --post ...\n"
598 " - Deferred submission: picurv submit --run-dir ...\n"
599 " - Run inspection/plots: picurv summarize ...\n"
600 " - Run cancellation: picurv cancel --run-dir ...; use --graceful for solver final-output shutdown"
601 ),
602 )
603 parser.add_argument("-v", "--version", action="version", version=f"picurv {PICURV_VERSION}")
604 subparsers = parser.add_subparsers(dest="command", required=True, help="Available commands")
605 _add_run_parser(subparsers)
606 _add_sweep_parser(subparsers)
607 _add_validate_parser(subparsers)
608 _add_precompute_parser(subparsers)
609 _add_summarize_parser(subparsers)
610 _add_submit_parser(subparsers)
611 _add_cancel_parser(subparsers)
612 _add_init_parser(subparsers)
613 _add_build_parser(subparsers)
614 _add_sync_binaries_parser(subparsers)
615 _add_sync_config_parser(subparsers)
616 _add_pull_source_parser(subparsers)
617 _add_status_source_parser(subparsers)
618 return parser
619
620
621def dispatch_command(args):
622 """!
623 @brief Validate argument combinations and dispatch to command handlers.
624 @param[in] args Command-line style argument list supplied to the function.
625 """
626 if args.command == "run":
627 if not args.solve and not args.post_process:
628 fail_cli_usage("At least one stage (--solve or --post-process) must be selected.")
629 if args.solve and (not args.case or not args.solver or not args.monitor):
630 fail_cli_usage("--solve requires --case, --solver, and --monitor.")
631 if args.post_process and not args.post:
632 fail_cli_usage("--post-process requires --post.")
633 if args.scheduler and not args.cluster:
634 fail_cli_usage("--scheduler requires --cluster in this version.")
635 run_workflow(args)
636 return
637 if args.command == "sweep":
638 if args.continue_study:
639 if not args.study_dir:
640 fail_cli_usage("--continue requires --study-dir.")
642 return
643 if args.reaggregate:
644 if not args.study_dir:
645 fail_cli_usage("--reaggregate requires --study-dir.")
647 return
648 # Default: launch new study
649 if not args.study:
650 fail_cli_usage("--study is required when launching a new sweep.")
651 if not args.cluster:
652 fail_cli_usage("--cluster is required when launching a new sweep.")
653 sweep_workflow(args)
654 return
655 if args.command == "validate":
657 return
658 if args.command == "precompute":
659 try:
661 except ValueError as e:
663 ERROR_CODE_CFG_INVALID_VALUE,
664 key="precompute",
665 file_path=getattr(args, "case", "-"),
666 message=str(e),
667 )
668 sys.exit(1)
669 return
670 if args.command == "summarize":
672 return
673 if args.command == "submit":
675 return
676 if args.command == "cancel":
677 cancel_run_jobs(args)
678 return
679 if args.command == "init":
680 init_case(args)
681 return
682 if args.command == "build":
683 build_project(args)
684 return
685 if args.command == "sync-binaries":
687 return
688 if args.command == "sync-config":
690 return
691 if args.command == "pull-source":
692 pull_source_repo(args)
693 return
694 if args.command == "status-source":
696 return
697 fail_cli_usage(f"Unsupported command '{args.command}'.")
_add_sweep_parser(subparsers)
Perform add sweep parser.
Definition cli.py:93
_add_run_parser(subparsers)
Attach run parser with staged execution and dry-run support.
Definition cli.py:10
_add_build_parser(subparsers)
Perform add build parser.
Definition cli.py:399
_add_init_parser(subparsers)
Perform add init parser.
Definition cli.py:362
build_main_parser()
Build and return the top-level CLI parser.
Definition cli.py:570
_add_sync_config_parser(subparsers)
Perform add sync config parser.
Definition cli.py:464
_add_pull_source_parser(subparsers)
Perform add pull source parser.
Definition cli.py:502
_add_precompute_parser(subparsers)
Attach precompute parser for deterministic artifact generation.
Definition cli.py:161
_add_cancel_parser(subparsers)
Perform add cancel parser.
Definition cli.py:271
_add_status_source_parser(subparsers)
Perform add status source parser.
Definition cli.py:537
_add_sync_binaries_parser(subparsers)
Perform add sync binaries parser.
Definition cli.py:439
_add_submit_parser(subparsers)
Perform add submit parser.
Definition cli.py:320
_add_summarize_parser(subparsers)
Attach summarize parser for read-only run-health views.
Definition cli.py:189
_add_validate_parser(subparsers)
Perform add validate parser.
Definition cli.py:130
summarize_workflow(args)
Build and render a read-only health summary for a run step.
Definition core.py:13427
status_source_command(args)
Report source/case drift for an initialized case directory.
Definition core.py:1327
validate_workflow(args)
Implements picurv validate without launching solver/post workflows.
Definition core.py:9949
sweep_reaggregate_workflow(args)
Re-run metrics aggregation and plot generation for an existing study.
Definition core.py:11184
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.
Definition core.py:366
init_case(args)
Implements the 'init' command.
Definition core.py:14127
sweep_workflow(args)
Study/sweep orchestration using Slurm job arrays.
Definition core.py:10723
submit_staged_jobs(args)
Submit previously staged Slurm artifacts from an existing run/study directory.
Definition core.py:13786
sync_case_config_command(args)
Refresh template-managed config/docs files in a case directory.
Definition core.py:14244
build_project(args)
Implements the 'build' command.
Definition core.py:14336
fail_cli_usage(str message, str hint=None)
Emit a structured CLI usage error and exit with code 2.
Definition core.py:389
sync_case_binaries_command(args)
Refresh case-local executables from the source repository bin directory.
Definition core.py:14213
sweep_continue_workflow(args)
Continue a partially-completed Slurm parameter sweep study.
Definition core.py:10961
cancel_run_jobs(args)
Cancel Slurm-submitted jobs for an existing run directory.
Definition core.py:14006
precompute_workflow(args)
Generate deterministic case artifacts without launching solver/post stages.
Definition core.py:10093
run_workflow(args)
Main orchestrator for the 'run' command (local and Slurm modes).
Definition core.py:10179
pull_source_repo(args)
Refresh source branches in the repository resolved from a case directory.
Definition core.py:14296