Skip to content

Function contracts and flow charts

Started 2026-09-29. The aim: every function in the solver core says what it takes, what it gives back, what it refuses, and what it does at its edges, and every edge it names has a test that fails when that edge breaks. A description alone does not make a result reliable; a stated rule that the code checks, and a test at each edge, does.

This page is the standard. The gate is tests/unit/test_function_contracts.py; the working rule is in CLAUDE.md ("Function contracts").

The four sections

A contract is part of the function's docstring, after the one-line summary. This one is solver/kernels.py::pipe_min_dt's, abridged:

Inputs:
    U_int: interior conservative state, shape (N, 3); any values (checked
        here, see Refuses).
    dx: cell width [m], > 0 (``Pipe`` guarantees it).
    cfl: Courant number, > 0; not clamped (the implicit drivers pass > 1).
Returns:
    the stable time step [s], > 0; 1e300 when no cell limits it.
Refuses:
    rho <= 0 or p <= 0 in any cell -> returns the 1e300 sentinel, and
    `pipe_min_dt_checked` raises with the pipe and cell named.
Edges:
    N == 0 -> 1e300 (`test_empty_pipe_has_no_dt_limit`).
    one bad cell among healthy ones is not skipped
    (`test_a_negative_pressure_cell_is_refused`).

Inputs. Every argument, with its unit in brackets, its shape for arrays, and the range it must be in. Say who guarantees the range: the function itself (then it is in Refuses) or the caller (name it). "Verify against call sites" (CLAUDE.md) applies: a range the callers guarantee is only true if every caller does.

Returns. What comes back, with unit and shape, and any sentinel value. State mutated on an argument (a Pipe's arrays, a report) belongs here too: a caller must be able to see every effect.

Refuses. Each input it will not accept and what happens: the exception type and what its message names, or the sentinel. none is allowed when the function accepts everything its type admits.

Edges. The cases this repo has shipped bugs in: zero, empty, NaN, negative, the boundary cell, a unit or basis mix-up (mass vs mole, K vs °C), a silent default. One line each, with the behaviour and the test that pins it in backticks. none only for a function with no edge worth a test (a plain accessor).

What the gate checks

  • Every function in the scope has all four sections. The count without one, MAX_WITHOUT_CONTRACT, may only fall and must be exact.
  • Every test a contract names exists.
  • Every function a flow chart names exists.

It cannot check that a contract is true. Writing one means reading the function and its callers, as a review-ledger sweep does, and fixing what does not match (CLAUDE.md: a found bug is fixed, not filed).

Scope and order

Every shipped function is in scope: all of src/aeolus1d/ and aeolus1d_gui/ (about 7,000 functions, 2026-09-29 to 2026-10-06, in six batches: the solver core first, then io/, analysis/ and control/, then the rest of the package, then the GUI shell and main window, then the rest of the GUI). A new function needs its contract in the same commit; the gate's count MAX_WITHOUT_CONTRACT is 0. For a Qt slot the Refuses: section also says what the slot does with an exception (a dialog, the status bar, the console, a log line, or "printed by Qt, nothing shown"), because an exception in a slot does not stop the event loop.

Flow charts

One chart per feature, not per function, under docs/flows/: what enters, which function decides what, where each refusal happens, and what comes out. Charts are Mermaid blocks, so they live beside the text and are reviewed in the same diff:

```mermaid
flowchart TD
    A[case TOML] --> B{load_case}
    B -- invalid --> X[CaseParseError]
    B --> C[build_network]
```

Each chart ends with a Functions list of the functions its boxes stand for, as ledger keys in backticks (`src/aeolus1d/io/build.py::build_network`). The gate checks each one exists, so a rename that is not carried into the chart fails the build instead of leaving a wrong picture. The charts are indexed at Flow charts.