Problem
csauto is tightly and diffusely coupled to code_saturne: ~450 references spread across
21 modules, with no abstraction layer. Solver knowledge (command building, log/residual
parsing, file conventions, environment checks) is mixed into generic orchestration code, so
supporting another solver would mean editing ~8 modules with no single entry point.
The good news: the boundary is conceptually clean — a large part of the codebase is already
solver-agnostic and would be reused as-is.
| Layer |
Status |
Modules |
DOE + {placeholder}/IF templating engine, registry, config, CLI, web shell, scheduling (local/Slurm/Docker), locking, history |
✅ generic |
template.py, registry.py, config.py, cli.py, doe.py, web |
| Run/GUI command building |
❌ code_saturne-specific |
execution.py, docker.py |
Output parsing (listing/run_solver.log, residuals, probes, outcome) |
❌ specific |
logs.py, residuals.py, probes.py |
File conventions (setup.xml, run.cfg, RESU/, checkpoint/) |
❌ specific |
runner.py, template.py |
Environment (code_saturne binary, image, mounts, doctor) |
❌ specific |
maintenance.py, execution.py |
Proposal
Extract a SolverAdapter interface that materializes this boundary. code_saturne becomes
one implementation (CodeSaturneAdapter), selected via config (solver = "code_saturne").
class SolverAdapter(Protocol):
def build_run_command(self, case_dir, runtime) -> list[str]: ...
def build_gui_command(self, case_dir, runtime) -> list[str]: ...
def detect_outcome(self, case_dir) -> Status: ... # log parsing
def read_residuals(self, case_dir) -> list[Row]: ...
def read_probes(self, case_dir) -> list[Row]: ...
def setup_files(self) -> list[str]: ... # setup.xml, run.cfg…
def resu_layout(self) -> ResuConvention: ... # RESU/, checkpoint/
def control(self, case_dir, action) -> None: ... # control_file, etc.
def doctor_checks(self) -> list[Check]: ...
Once in place, adding a solver = writing one class, with no changes to the core.
Approach (incremental, shippable at each step)
Refactor in place behind the interface — no behavior change, CodeSaturneAdapter keeps
current behavior. Suggested order (each step stays green):
- Define
SolverAdapter protocol + CodeSaturneAdapter skeleton; wire config solver key.
- Move command building (
execution.py/docker.py) behind build_run_command/build_gui_command.
- Move output parsing (
logs/residuals/probes) behind detect_outcome/read_*.
- Move file conventions + doctor checks behind
setup_files/resu_layout/doctor_checks.
- Route
control_file actions through control().
Notes
- This is a software-only epic (interface extraction + adapter pattern), no deep CFD needed.
- Prerequisite / natural first client: a fake-solver test stub — it becomes the second
adapter and unlocks integration tests without a real solver installed.
- Scope here is the abstraction + code_saturne adapter. Shipping a second real solver
(OpenFOAM, etc.) is a separate follow-up.
Acceptance criteria
Out of scope
A second production solver backend; per-solver UI customization; auto-detection of the solver.
Problem
csauto is tightly and diffusely coupled to code_saturne: ~450 references spread across
21 modules, with no abstraction layer. Solver knowledge (command building, log/residual
parsing, file conventions, environment checks) is mixed into generic orchestration code, so
supporting another solver would mean editing ~8 modules with no single entry point.
The good news: the boundary is conceptually clean — a large part of the codebase is already
solver-agnostic and would be reused as-is.
{placeholder}/IF templating engine, registry, config, CLI, web shell, scheduling (local/Slurm/Docker), locking, historytemplate.py,registry.py,config.py,cli.py,doe.py, webexecution.py,docker.pyrun_solver.log, residuals, probes, outcome)logs.py,residuals.py,probes.pysetup.xml,run.cfg,RESU/,checkpoint/)runner.py,template.pycode_saturnebinary, image, mounts, doctor)maintenance.py,execution.pyProposal
Extract a
SolverAdapterinterface that materializes this boundary. code_saturne becomesone implementation (
CodeSaturneAdapter), selected via config (solver = "code_saturne").Once in place, adding a solver = writing one class, with no changes to the core.
Approach (incremental, shippable at each step)
Refactor in place behind the interface — no behavior change,
CodeSaturneAdapterkeepscurrent behavior. Suggested order (each step stays green):
SolverAdapterprotocol +CodeSaturneAdapterskeleton; wire configsolverkey.execution.py/docker.py) behindbuild_run_command/build_gui_command.logs/residuals/probes) behinddetect_outcome/read_*.setup_files/resu_layout/doctor_checks.control_fileactions throughcontrol().Notes
adapter and unlocks integration tests without a real solver installed.
(OpenFOAM, etc.) is a separate follow-up.
Acceptance criteria
SolverAdapterprotocol defined; all solver-specific calls in the core go through it.CodeSaturneAdapterimplements it; existing behavior unchanged (tests still green).solverconfig key selects the adapter (defaults tocode_saturne).code_saturne/setup.xml/RESUreferences left in generic modules(registry, doe, cli, web routes).
StubAdapterused by integration tests.docs/architecture.mdupdated with the adapter boundary.Out of scope
A second production solver backend; per-solver UI customization; auto-detection of the solver.