Adding a new solver
csauto knows a solver only through its adapter: one Python class that says how to start the solver, where its files are, and how to read its output. Everything else (case generation, launching on a laptop, a container or Slurm, status tracking, the web dashboard) is shared by every solver.
Adding a solver means writing one adapter class, registering its name and adding a sample case to the test suite. No other part of csauto changes, including the dashboard.
Three adapters ship with csauto:
| Adapter | Read it for |
|---|---|
csauto/solvers/stub.py | The smallest complete adapter: a fake solver used by the tests. Start here. |
csauto/solvers/code_aster.py | A real solver in about 100 lines: environment setup in containers, results written straight into RESU/, verdict read from the solver's standard output. |
csauto/solvers/code_saturne.py | The most complete adapter: residuals, probes, timing, restart, live control, GUI, a custom Slurm script. |
1. Write the adapter
Create csauto/solvers/<name>.py with a subclass of SolverAdapter. This
is a complete adapter for a solver started as mysolver input.cfg, which
writes each run's results into a new folder under results/:
Code
That is all csauto needs:
run_argvreturns the arguments after the solver executable. The command always starts inside the case folder, in every runtime, so use paths relative to it.case_diris the case folder on this machine, for when you need to read a file to build the arguments.nprocsis the number of MPI processes andntthe number of threads per process: use them, translate them, or ignore them.find_setup_filereturns the solver's main input file.csauto preparerenders it from each DOE row, andcsauto doctorchecks every case has one.- How a run ends. When the run command exits, csauto reads its exit
status: 0 means DONE, anything else FAILED. If your solver's exit status
is not reliable, override
detect_outcome(see the recipe).
2. Register it
In csauto/solvers/__init__.py, add the name to _SOLVER_NAMES and a branch
in _adapter_for():
Code
Select it in the campaign's csauto.toml (solver = "mysolver").
csauto prepare records it in RUNS/campaign.json, so later commands use the
right adapter from any directory.
3. Test it
Add a sample_mysolver(case_dir) function to
tests/unit/test_adapter_conformance.py. It writes a small finished case,
the way your solver leaves it: the input file, the logs, the results. The
conformance suite then checks your adapter against it and says, in plain
words, what the dashboard would miss: an unknown tab, a log file the Log Tail
offers but cannot open, a capability with nothing behind it, a finished run
that does not read as DONE.
Code
Then run a real campaign: csauto prepare, csauto run, csauto status,
and csauto serve to look at the dashboard. If your solver has a docker
image, add it to tests/integration/test_docker_solvers.py, which runs the
shipped examples for real when CSAUTO_DOCKER_TESTS=1 is set.
csauto doctor RUNS shows what your adapter offers:
Code
4. Fill the dashboard
The dashboard shows the tabs listed in dashboard_panels, in this order:
status, residuals, probes, performance, compare, tail, errors.
Status, Log Tail and Recent Errors work for every solver. List another tab
once your adapter feeds it; the conformance suite refuses a tab nothing feeds.
| Tab | What to add |
|---|---|
| Log Tail | Nothing. It offers csauto.stdout and csauto.stderr (the solver's console output) plus every *.log file of the latest run. Set tail_file_names to put your main logs first; entries may be globs, such as "*.mess". |
| Recent Errors | Nothing. It scans anomaly_file_names for crashes, tracebacks, error: lines and warnings. Add your solver's own markers with anomaly_patterns (labels "error" or "warn") and silence false positives with anomaly_ignore_patterns. The Log Tail colours lines with the same patterns. |
| Compare | compare_kinds: CompareKind(value, label) entries. The value is a name handed to locate_case_file, so it can be an alias for a file whose name changes per case (code_aster maps "export" to the export last run and "comm" to the command file it names). The first entry is the default. |
| Residuals | find_residuals_files returning CSV files with an iteration column and one column per residual, or parse_live_residuals returning rows parsed from a log. default_residual_columns names the curves shown first. |
| Probes | list_probe_files and locate_probe_files. Time series need a t or time column. Files are read as comma-separated CSV; override read_probe_file(path, max_rows) for another format (return at most max_rows rows when it is set: 0 means only the column names are needed). list_profile_files adds spatial profiles. |
| Timing Snapshot | performance_columns (PerfColumn(key, label, kind), kind "time", "int", "float" or "text"), find_performance_log, and parse_performance returning values under those keys. csauto perf exports the same columns. |
The status table's Last Iter column comes from read_progress.
Actions. Run, Kill and Clean work for every solver. These ones appear only when the adapter provides them:
| Button | What to add |
|---|---|
| Restart | build_restart_args and restart_modes: RestartMode(name, label, value_label, value_kind) entries, one per way to restart (code_saturne offers additional iterations or additional physical time). The dialog asks for a value when value_label is set. |
| Live control | control_actions (ControlAction(name, label, value_label, value_kind)) and apply_control. They appear in the dashboard's control menu and as csauto control RUNS CASE ACTION [VALUE]. |
| Open GUI | gui_argv(setup_path): the arguments that open the solver's GUI on the setup file. |
Branding. Point logo_file at an SVG for the dashboard header and
icon_file at a square SVG for the browser tab; keep them in
csauto/solvers/logos/. Without them the header shows the solver's name.
Recipes
The solver prints its verdict in a log
Override detect_outcome and let csauto.logs.scan_outcome do the reading:
it looks at the end of each file, skips files written before the current run
started, and returns DONE, FAILED or None (no verdict yet).
Code
A run stays RUNNING until its process or Slurm job is gone, even after the verdict is printed; the verdict then decides the status. When the logs give no verdict, csauto falls back to the exit status.
The image needs its environment activated
Set container_setup to the shell commands to run in the container before
the solver (code_aster: "[ ! -f /opt/activate.sh ] || source /opt/activate.sh", run by bash). When the image has no
ENTRYPOINT, csauto starts the solver with --entrypoint; an image whose own
ENTRYPOINT starts the solver gets only run_argv, so that script sets up the
environment instead of container_setup.
For native runs, the solver must already be on PATH, or solver_bin in
csauto.toml must point to it.
Results go in one folder per run, or straight into the results folder
list_run_dirs returns the folders that each hold one run's results. Clean
only ever deletes these folders, and "Keep latest N" counts them. The default
lists the subfolders of results_dirname, which suits solvers that create a
new folder per run (code_saturne's RESU/<run_id>). If your solver writes
everything into results_dirname itself, return [self.results_root(case_dir)]
(code_aster does): each case then holds one run, and a new run replaces it.
locate_case_file(case_dir, name) turns the names used by the Log Tail,
Recent Errors and Compare into files. The default resolves paths relative to
the case folder; extend it when your logs live in run folders (see
StubAdapter and CodeSaturneAdapter). These names can come from API
requests: build paths with pathutil.safe_subpath(folder, name), which refuses
.. and absolute paths, as the stub does. csauto also ignores any file you
return from outside the case folder.
The solver needs a file written at launch
Do it in prepare_launch(case_dir, nprocs, nt), called right before each
launch. Never modify a file that came from the template: csauto prepare
compares those to the template to detect changed cases. Write a new file
instead. code_aster writes .csauto.export, a copy of the case's export
carrying the run's n and nt, and runs that one.
Template files contain braces
{name} in a template is a DOE placeholder. When braces belong to the input
language (Python f-strings or sets in a code_aster .comm file), write \{i}
to keep a literal {i}. To keep csauto away from a whole file, list its name
in template_input_names.
Slurm
By default csauto submits the run command with sbatch --wrap, requesting
--ntasks from n and --cpus-per-task from nt. When your solver needs a
real batch script (code_saturne's apptainer runs stage, compute and finalize
in separate steps), return it from build_slurm_script. mpi_env turns the
mpi_exec_options setting into environment variables for the solver.
Runtimes
supported_runtimes lists the runtimes the solver can use (native,
docker, singularity). Auto mode only picks among them, and asking for
another one is refused before anything starts. Containers mount the campaign
folder at container_root (/csauto by default). Keep it out of the image
user's home directory, or the solver's own settings and caches end up in the
campaign folder.
Reference
Required (no default):
| Member | Meaning |
|---|---|
name | value of solver in csauto.toml |
native_bin_name | executable looked up in PATH by the native runtime |
container_bin_name | executable started inside container images |
default_docker_image | image used when csauto.toml sets no docker_image |
results_dirname | results folder inside each case |
dashboard_panels | dashboard tabs |
run_argv(case_dir, nprocs, nt, run_args) | arguments after the executable |
find_setup_file(template_dir) | the main input file |
Optional, with their default:
| Member | Default |
|---|---|
supported_runtimes | native, docker and singularity |
container_root | "/csauto" |
container_setup | none |
shared_dir_names, readonly_shared_dir_names | none: folders next to TEMPLATE shared by every case (code_saturne: MESH, POST), reached as ../MESH from a case |
template_input_names | none: files never rendered |
tail_file_names | csauto.stdout, csauto.stderr |
anomaly_file_names | csauto.stderr, csauto.stdout |
anomaly_patterns, anomaly_ignore_patterns | none |
cleanup_log_names | csauto.stdout, csauto.stderr: logs Clean may shorten |
compare_kinds, performance_columns, control_actions, restart_modes, default_residual_columns | none |
logo_file, icon_file | none |
description | none: one line shown by the VS Code extension's New Campaign; without it the solver is not offered there (test solvers) |
case_inputs, assemble_case | none: the files the extension's New Campaign asks for to build a case (CaseInput: label, extensions, destination folder, required, multiple, rename), and how they are placed; override assemble_case when the files reference each other (code_aster writes or relinks its .export) |
example_dir | none: a folder under examples/ a new campaign can start from (also list its parts under force-include in pyproject.toml, which a test checks) |
prepare_launch | does nothing |
detect_outcome | no verdict: the exit status decides |
read_progress, read_restart_origin | no value |
locate_case_file | paths relative to the case folder |
list_run_dirs | subfolders of results_dirname, newest first |
find_run_config | none: a second rendered input file (code_saturne's run.cfg) |
build_slurm_script, mpi_env | sbatch --wrap, no variables |
doctor_checks | every case has a setup file |
| analytics methods | empty results |
capabilities (what the dashboard and the API allow), performance_fields
and default_compare_kind are derived from the members above; declaring
them is an error.