09 · Turn analysis into a tool¶
A reusable analysis has a narrow command-line boundary, pure computational functions, project-safe paths, useful errors, and an observable output.
Decision: can another person run the analysis without editing source?
Output: a command-line program with checked inputs and outputs.
Frame¶
This is not a reusable interface:
The path is a caller choice disguised as source code. Expose it:
A thin main¶
def main(arguments=None):
args = build_parser().parse_args(arguments)
log = load_basilisk_log(args.input)
summary = summarise_log(log)
print(json.dumps(summary, indent=2))
return 0
main coordinates. Parsing and reduction remain testable without launching a
subprocess.
The course CLI follows this shape:
"""Command-line front door for the reference workflows."""
from __future__ import annotations
import argparse
import json
from collections.abc import Sequence
from pathlib import Path
from .analysis import summarise_log
from .io import load_basilisk_log, load_regime_map
from .plotting import plot_log, plot_regime_map
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(
prog="comphy-python101",
description="Validated teaching workflows for scientific Python.",
)
commands = parser.add_subparsers(dest="command", required=True)
summary = commands.add_parser("summary", help="summarise a Basilisk log")
summary.add_argument("input", type=Path)
log_plot = commands.add_parser("plot-log", help="plot a Basilisk log")
log_plot.add_argument("input", type=Path)
log_plot.add_argument("--output", type=Path, default=Path("log.png"))
regime_plot = commands.add_parser("plot-regime", help="plot an Oh–Bo map")
regime_plot.add_argument("input", type=Path)
regime_plot.add_argument("--output", type=Path, default=Path("regime-map.png"))
return parser
def main(arguments: Sequence[str] | None = None) -> int:
args = build_parser().parse_args(arguments)
if args.command == "summary":
print(json.dumps(summarise_log(load_basilisk_log(args.input)), indent=2))
elif args.command == "plot-log":
output = plot_log(load_basilisk_log(args.input), args.output)
print(output)
elif args.command == "plot-regime":
output = plot_regime_map(load_regime_map(args.input), args.output)
print(output)
return 0
Command-line arguments are a schema¶
Use argparse to state:
- required inputs;
- optional outputs;
- types;
- help;
- mutually exclusive choices where needed.
The resulting --help is part of the interface:
An argument should change a meaningful choice. Do not expose every local variable merely because you can.
Paths without folklore¶
Use pathlib.Path:
Do not build paths with manual "/" concatenation. Do not assume the user's
home directory or current working directory.
External programs must fail visibly¶
Scientific workflows often call compiled extractors:
Use a list of arguments and check=True. Avoid shell=True with interpolated
input: it adds quoting ambiguity and command-injection risk.
After a successful command, check that the expected output exists and is non-empty. A zero exit status is not evidence that the right file was written.
The public figure rebuilder in
Soft-Matter-Singularities-paper-figures
does this well. A public legacy visualiser,
BasiliskVisualization/vView2D_v1.py,
is a useful refactoring contrast: manual sys.argv, global state, hard-coded
assumptions, nested array loops, and shell=True.
Exit status is an output¶
Return zero for success and non-zero for failure. Let unexpected exceptions produce a traceback during development. For expected user mistakes, give a short actionable message.
Do not catch everything:
That converts evidence into silence.
Make repeated work restartable¶
For a case or snapshot pipeline:
- give outputs deterministic names;
- skip only when an output passes a validity check;
- write partial outputs atomically where possible;
- record failed case IDs;
- avoid deleting raw inputs;
- support selecting a subset from the CLI.
Parallelise across independent cases. On HPC, request a compute node and keep
concurrency within allocated resources. The
Jumping-Drops/postProcess
notes make the important operational point explicit: single-node batch
processing, not login-node work.
Verify¶
Complete Exercise 09, then run:
uv run comphy-python101 summary data/basilisk_log.csv
uv run comphy-python101 plot-log data/basilisk_log.csv \
--output build/log.png
Then run the first command from another directory with an absolute input path. The command should still behave predictably.
Refactor a small personal plotting script so that:
- source paths become arguments;
- computation moves into a function;
- output is explicit;
- invalid input returns a useful failure;
- one test calls the function directly.
Reflect¶
If another student must open your source and edit three path variables before running it, you have shared code, not yet a tool.