Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion adr/ADR-002-fvm-first.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ total is unchanged after many timesteps, to floating-point tolerance.
the second.** `src/pyflow/engine/numerics/diffusion.py`'s
`CentralDifferenceDiffusion` computes a real per-face diffusive flux --
the face-normal gradient estimate central differencing gives, times the
diffusion coefficient (`NumericsConfig.diffusion_coefficient`). FVM's
diffusion coefficient (`NumericsConfig.diffusion_coefficient` at the
time; migrated to `FluidConfig.diffusion_coefficient` by TASK-041,
2026-08-28). FVM's
conservation property under diffusion is checked the same way advection's
was: `tests/features/central_difference_diffusion.feature`'s own
"Conservation under zero-flux boundaries" scenario confirms the
Expand Down
5 changes: 3 additions & 2 deletions docs/architecture/icds.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,8 +56,9 @@ shape every other configuration section does -- a dataclass field with a
`Literal[...]` type listing the valid choices by name, validated
immediately and explicitly in `validate()` rather than left to fail
wherever the value is first used (`rendering.backend`'s precedent).
`engine/numerics/assembly.py`'s `assemble_numerics(NumericsConfig) ->
AssembledNumerics` resolves each configured name to a live instance
`engine/numerics/assembly.py`'s `assemble_numerics(NumericsConfig,
diffusion_coefficient: float = 1.0) -> AssembledNumerics` resolves each
configured name to a live instance
through a registry keyed by name, populated by `register_*` calls rather
than a chain `assemble_numerics` itself branches on -- adding a name
requires no edit to that function's body (Stage 3 Completion
Expand Down
4 changes: 2 additions & 2 deletions docs/architecture/sequences.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ sequenceDiagram
bootstrap->>bootstrap: configure_logging(config.logging)
bootstrap->>Window: RenderWindow(config.rendering)
Window-->>bootstrap: window (canvas, renderer, scene, camera)
bootstrap->>Assembly: assemble_numerics(config.numerics)
bootstrap->>Assembly: assemble_numerics(config.numerics, config.fluid.diffusion_coefficient)
Assembly-->>bootstrap: AssembledNumerics (6 live instances)
bootstrap->>Window: window.assembled_numerics = ...
alt show_mesh or a field_display pattern is configured
Expand Down Expand Up @@ -206,7 +206,7 @@ sequenceDiagram
Note over Mesh: cell/face geometry, no field values
bootstrap->>Field: ScalarField(mesh, name, initial_value=...)
Note over Field: CollocatedField allocates a<br/>torch.float64 tensor, (num_cells, *component_shape)
bootstrap->>Numerics: assemble_numerics(config.numerics)
bootstrap->>Numerics: assemble_numerics(config.numerics, config.fluid.diffusion_coefficient)
Note over Numerics: 6 live scheme instances,<br/>constructed once, held for the run
bootstrap->>Window: window.scene.add(...), window.assembled_numerics = ...
Note over Window: process memory only --<br/>nothing here is written to disk
Expand Down
18 changes: 15 additions & 3 deletions docs/implementation/config-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,21 @@ simulation:
# Invalid: anything other than exactly two numbers.
velocity: [1.0, 0.0]

# Physical properties of the simulated fluid -- separate from numerics
# below, which selects numerical schemes and their solver tunables, not
# properties of the fluid those schemes act on.
fluid:
# Valid: a positive number -- momentum's own diffusion coefficient,
# distinct from diffusion_coefficient below (a transported scalar's own).
# Invalid: zero or negative.
viscosity: 1.0
# Valid: a positive number (a transported scalar's own diffusivity, Gamma)
# -- distinct from viscosity above. Invalid: zero or negative. Migrated
# here from numerics.diffusion_coefficient (TASK-041); a configuration
# still setting the old field is rejected with a named error, not silently
# defaulted.
diffusion_coefficient: 1.0

# Numerical scheme selection (adr/ADR-003-modular-numerical-strategies.md)
# plus the physical/solver parameters those schemes take.
numerics:
Expand All @@ -129,9 +144,6 @@ numerics:
# Valid: "central_difference" -- the only scheme PyFlow currently
# implements for this component. Invalid: any other string.
diffusion: central_difference
# Valid: a positive number (the physical diffusivity, Gamma). Invalid:
# zero or negative.
diffusion_coefficient: 1.0
# Valid: "rk4" -- the only scheme PyFlow currently implements for this
# component. Invalid: any other string.
time_integration: rk4
Expand Down
70 changes: 62 additions & 8 deletions docs/planning/roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ This paragraph previously said `make install` and `make test` were still
expected to fail, pending `uv.lock` and a test suite (B2/C1) -- stale
since 2026-08-16 and corrected 2026-08-19. Both now succeed: `uv.lock`
is committed (B2) and `make test` runs the suite with coverage
(C1a/C1b): **614 tests at 99% as of 2026-08-28**, having been 64 when
(C1a/C1b): **622 tests at 99% as of 2026-08-28**, having been 64 when
this paragraph was rewritten on 2026-08-19, 202 earlier the same day,
212 after TASK-014, 226 after TASK-015, 250 after TASK-016, 287 after
TASK-017, 297 after TASK-039, 315 after the Stage 2 exit audit and 337
Expand Down Expand Up @@ -395,8 +395,14 @@ suite (`tests/unit/test_generate_config_template.py`), eight tests, built
at a user's direct request for an annotated, always-current example
config -- again no Stage 4/5 task work involved, the same "count moves
for reasons having nothing to do with the fluid solver" pattern the
paragraph above already records. **54 of those 614 are Gherkin scenarios
rather than pytest functions**
paragraph above already records. 622 after TASK-041 (Fluid Configuration
Section, Stage 5's first task): four new Gherkin scenarios in
`tests/integration/test_fluid_configuration.py`
(`fluid_configuration.feature`) and four new `FluidConfig.viscosity`
load/reject tests in `test_configuration.py`, the same shape every prior
config-section addition in this run used -- this is Stage 5's own first
climb, not another Stage 4 audit finding. **58 of those 622 are Gherkin
scenarios rather than pytest functions**
(`adr/ADR-007-executable-acceptance-criteria.md`; up from fourteen with
`field_display.feature` gaining scenarios and `numerics_assembly.feature`
joining, TASK-021; to 24 with TASK-040's own
Expand Down Expand Up @@ -440,7 +446,13 @@ downstream at approximately the prescribed velocity over real elapsed
time, not only that rendered pixels changed; and to 54 with the Stage 4
exit audit's own addition to `first_order_upwind_advection.feature`,
above -- the only scenario in this list added by an audit rather than by
the task that owned the criterion).
the task that owned the criterion; and to 58 with TASK-041's own
`fluid_configuration.feature`, Stage 5's first task and its first four
scenarios: a fluid section loading both its fields, one field's default
surviving the other being set, the retired `numerics.diffusion_
coefficient` field rejected by name rather than silently defaulted, and
the Passive Scalar Transport golden demo still running through the real
CLI after its own config migrated to the new section).
**All** `make ci`
targets pass, verified via the Makefile itself, not only via `uv tool
run` in isolation -- that is `lint`, `typecheck`, `test`, `check-docs`,
Expand Down Expand Up @@ -6724,6 +6736,24 @@ drafted against these on 2026-08-28, not in place of them.

Fluid Configuration Section

**Status: Done, 2026-08-28, Stage 5's first task.** A real gap between
this task's own Dependencies section (below, "no engine dependency at
all") and the live repository was found while implementing, not
predicted in advance: `src/pyflow/engine/numerics/assembly.py`'s
`assemble_numerics` already read `NumericsConfig.diffusion_coefficient`
directly (TASK-024, 2026-08-27), so migrating the field out from under
it was a real, required engine-side change, not the zero-engine-impact
migration the Dependencies section below claims. Recorded honestly here
rather than silently fixed and left for a reader to rediscover
(`docs/CLAUDE.md`'s Integrity section): `assemble_numerics` gained a
second parameter, `diffusion_coefficient: float = 1.0` (the default
preserves every existing caller that only cares about scheme selection),
and `bootstrap.py`'s own call site now threads
`config.fluid.diffusion_coefficient` through explicitly. See
`src/pyflow/engine/numerics/assembly.py`'s own docstring and
`src/pyflow/engine/CLAUDE.md`'s `assembly.py` entry for the mechanical
detail.

**Added 2026-08-28, while auditing this stage's own readiness to start
-- not anticipated when Stage 5's criteria were drafted the same day.**
Design question four's answer (a new `fluid:` section, with
Expand Down Expand Up @@ -6755,9 +6785,17 @@ one property already misfiled there into it.

TASK-005 (the configuration framework), TASK-019 (`NumericsConfig` and
whole-configuration validation), TASK-039 (`pyflow generate-config`),
and the `make config-template` generator added 2026-08-28. No engine
dependency at all -- nothing in `src/pyflow/engine/` reads these fields
until TASK-031b consumes viscosity.
and the `make config-template` generator added 2026-08-28. **Wrong as
drafted, corrected once implementation found the gap (this task's own
Status note, above): `viscosity` alone has no engine dependency until
TASK-031b, but `diffusion_coefficient` already had one the moment this
task moved it out of `NumericsConfig` --
`src/pyflow/engine/numerics/assembly.py`'s `assemble_numerics` (TASK-024)
already read it off that section directly.** Closed in this task, not
deferred: `assemble_numerics` gained a `diffusion_coefficient: float`
parameter (defaulted to `1.0` so every scheme-selection-only caller is
unaffected) and `bootstrap.py` threads
`config.fluid.diffusion_coefficient` through it explicitly.

### Design decision, inherited rather than made here

Expand Down Expand Up @@ -6791,7 +6829,23 @@ pretends away.
hand-edited (`make config-template`).
- `examples/golden-demos/passive_scalar_transport.yaml` -- the one
committed config that sets the migrating field.
- `tests/features/fluid_configuration.feature` and its binding module.
- `tests/features/fluid_configuration.feature` and its binding module,
`tests/integration/test_fluid_configuration.py` -- `tests/integration/`,
not `tests/unit/`, because its own migration scenario needs a real CLI
subprocess run (`tests/CLAUDE.md`'s own split); not a golden demo
either, so it supplies its own local steps.
- **Not predicted when this task was drafted, found while implementing
(this task's own Status note, above):**
`src/pyflow/engine/numerics/assembly.py` -- `assemble_numerics` gained
a `diffusion_coefficient: float = 1.0` parameter, since it could no
longer read the field off `NumericsConfig`; `src/pyflow/bootstrap.py`
-- its one call site now threads `config.fluid.diffusion_coefficient`
through explicitly. `tests/unit/numerics/test_assembly.py`,
`tests/unit/test_main.py`, `tests/unit/test_generator.py` and
`tests/integration/test_cli.py` -- each asserted the old
`NumericsConfig`/`PyFlowConfig` shape directly (a `diffusion_coefficient`
field, or a fixed top-level key order) and needed updating to the new
one.

**Why this task has a `.feature` file when it computes nothing**, stated
rather than assumed: Stage 5 Criterion 7 says *every* task's acceptance
Expand Down
16 changes: 8 additions & 8 deletions docs/planning/status.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@ demand, not part of this file.

## Progress

**33/42 tasks complete (79%)** across 14 planned stages. For the full plan, including
**34/42 tasks complete (81%)** across 14 planned stages. For the full plan, including
stages below not yet broken into tasks: [roadmap.md](roadmap.md).

```mermaid
pie showData
title "Tasks across the roadmap"
"Done" : 33
"Not started" : 9
"Done" : 34
"Not started" : 8
```

### Milestones
Expand All @@ -35,13 +35,13 @@ pie showData

### Up next

**Stage 5 -- First Fluid Solver** is next, starting with TASK-041 (Fluid Configuration Section), 4 more not yet started in this stage.
**Stage 5 -- First Fluid Solver** is next, starting with TASK-031 (Velocity Field Support), 3 more not yet started in this stage.

## Live repository facts

- **45** `CLAUDE.md` files
- **614** tests collected
- **54** Gherkin scenarios (`tests/features/*.feature`)
- **622** tests collected
- **58** Gherkin scenarios (`tests/features/*.feature`)

## Stages

Expand Down Expand Up @@ -115,11 +115,11 @@ pie showData

### Stage 5 -- First Fluid Solver

**no status recorded** -- `░░░░░░░░░░` 0/5 tasks; 13 criteria defined, no status line yet
**no status recorded** -- `██░░░░░░░░` 1/5 tasks; 13 criteria defined, no status line yet

| Task | Status | Date | Artifact |
|------|--------|------|----------|
| TASK-041 -- Fluid Configuration Section | Not started | | |
| TASK-041 | Done | 2026-08-28 | `src/pyflow/engine/numerics/assembly.py` |
| TASK-031 -- Velocity Field Support | Not started | | |
| TASK-032 -- Pressure Field | Not started | | |
| TASK-033 -- Pressure Correction Loop | Not started | | |
Expand Down
4 changes: 3 additions & 1 deletion docs/repository-inventory.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ reading job and lives in the manifest. Test counts and coverage are
not here either -- those come from running the suite, not from
listing files.

**270 tracked files** across 45 directories;
**272 tracked files** across 45 directories;
4 are empty.

## (root)
Expand Down Expand Up @@ -308,6 +308,7 @@ listing files.
- `empty_window.feature`
- `field_display.feature`
- `first_order_upwind_advection.feature`
- `fluid_configuration.feature`
- `neumann_boundary.feature`
- `numerics_assembly.feature`
- `passive_scalar_transport.feature`
Expand Down Expand Up @@ -335,6 +336,7 @@ listing files.
- `test_bootstrap.py`
- `test_claude_hooks.py`
- `test_cli.py`
- `test_fluid_configuration.py`
- `test_import_order.py`
- `test_interactive_window.py`

Expand Down
19 changes: 15 additions & 4 deletions docs/repository-manifest.md
Original file line number Diff line number Diff line change
Expand Up @@ -838,9 +838,15 @@ is load-bearing rather than tidiness.
`integration/` holds `test_cli.py` (C1a), `test_bootstrap.py` (D4) --
the real subprocess versions -- `test_import_order.py`, a permanent
regression test for D4's circular import, `test_interactive_window.py`
(a real glfw window, skipped where no display exists), and
(a real glfw window, skipped where no display exists),
`test_claude_hooks.py` (2026-08-21: the `.claude/` hooks actually run,
and parse at an older interpreter floor). The repository-tooling tests
and parse at an older interpreter floor), and `test_fluid_configuration.py`
(TASK-041, 2026-08-28, Stage 5's first task: binds
`tests/features/fluid_configuration.feature` here rather than in
`tests/unit/`, since its own migration scenario needs a real CLI
subprocess run against the Passive Scalar Transport golden demo's own
committed config file -- not a golden demo itself, so no `conftest.py`
vocabulary reused). The repository-tooling tests
live in `unit/` alongside them: `test_check_docs.py`,
`test_check_claims.py`, `test_check_graph.py` and
`test_generate_docs_index.py`/`test_generate_dependency_tree.py`/
Expand All @@ -856,8 +862,13 @@ same not-a-golden-demo shape for Stage 4's first real numerical scheme --
same shape again for Stage 4's second -- `rk4_time_integration.feature`,
TASK-025, same day, the same shape again for Stage 4's fourth --
`conjugate_gradient_solver.feature`, TASK-026, same day, the same shape
again for Stage 4's fifth -- and `piso_pressure_coupling.feature`,
TASK-027, same day, the same shape again for Stage 4's sixth --
again for Stage 4's fifth -- `piso_pressure_coupling.feature`,
TASK-027, same day, the same shape again for Stage 4's sixth -- and
`fluid_configuration.feature`, TASK-041, 2026-08-28, Stage 5's first
task and the first not tied to Stage 4's numerics -- the same
not-a-golden-demo shape again, bound from `tests/integration/` instead
of `tests/unit/` since one of its own scenarios needs a real CLI
subprocess --
`adr/ADR-007-executable-acceptance-criteria.md`), which are criteria
rather than tests of criteria and are covered by a collective rule
above. `golden/` holds
Expand Down
4 changes: 3 additions & 1 deletion examples/golden-demos/passive_scalar_transport.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ mesh:

numerics:
timestep: 0.02
diffusion_coefficient: 0.02
boundary_conditions:
east:
type: periodic
Expand All @@ -42,6 +41,9 @@ simulation:
velocity_pattern: uniform
velocity: [1.0, 0.0]

fluid:
diffusion_coefficient: 0.02

field_display:
low_color: "#0a0a2a"
high_color: "#ff8c00"
Expand Down
8 changes: 7 additions & 1 deletion src/pyflow/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,13 @@ def bootstrap(
# does this, not only ones that need numerics for anything yet, since
# `NumericsConfig` always has a full section (defaulted or not) and
# assembly must not depend on whether a caller happens to care.
window.assembled_numerics = assemble_numerics(config.numerics)
# `config.fluid.diffusion_coefficient` (TASK-041, 2026-08-28) is
# threaded in explicitly -- it moved out of `NumericsConfig` into its
# own `fluid:` section, so `assemble_numerics` can no longer read it
# off `config.numerics` alone.
window.assembled_numerics = assemble_numerics(
config.numerics, config.fluid.diffusion_coefficient
)
logger.info("numerics assembled: %s", window.assembled_numerics.names)

show_fields = (
Expand Down
45 changes: 34 additions & 11 deletions src/pyflow/configuration/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,17 +250,40 @@ longer among them, since TASK-023/024), only a reference implementation
resolves it under `src/`. Completes the six-field `numerics` section
`adr/ADR-003-modular-numerical-strategies.md` names.

**`diffusion_coefficient`** (TASK-024, added 2026-08-27): follows
`timestep`'s own pattern -- a plain positive `float`, not a name from a
closed set, because this is Gamma
(`docs/handbook/numerical-methods/diffusion.md`), a physical property of
what's being transported, not a choice between schemes. `1.0` is an
arbitrary MVP default, same reasoning as `timestep`'s `0.01`; `validate()`
rejects `<= 0`. Threaded into `CentralDifferenceDiffusion`'s constructor
by `assembly.py`'s `register_diffusion_scheme`, the same "constructed
with it, not handed it after the fact" mechanism `boundary_conditions`
already established for advection/diffusion (TASK-040) -- see
`src/pyflow/engine/CLAUDE.md`'s `assembly.py` entry.
**`diffusion_coefficient`** (TASK-024, added 2026-08-27; **migrated to
`FluidConfig.diffusion_coefficient` by TASK-041, 2026-08-28 -- no longer
a `NumericsConfig` field**): originally followed `timestep`'s own
pattern here -- a plain positive `float`, not a name from a closed set,
because this is Gamma (`docs/handbook/numerical-methods/diffusion.md`),
a physical property of what's being transported, not a choice between
schemes -- which is exactly why it moved out of the section that selects
schemes. `loader.py`'s `_numerics_config_from_raw` rejects a
configuration still setting `numerics.diffusion_coefficient` with a
named error pointing at its new home, rather than silently ignoring it
or resolving it via `NumericsConfig`'s ordinary unknown-field path. See
`FluidConfig`, below, for where it lives now.

**`FluidConfig`** (`PyFlowConfig.fluid`, TASK-041, added 2026-08-28,
Stage 5's first task): `viscosity` and `diffusion_coefficient`, physical
properties of the simulated fluid, deliberately separated from
`NumericsConfig` -- a numerical scheme is a discretisation choice, a
fluid property is not, and the two do not belong in one section (Stage
5's design question four, `docs/planning/roadmap.md`). `diffusion_
coefficient` migrated here from `NumericsConfig` (previous entry, above);
`viscosity` is new, and nothing reads it yet -- momentum's own diffusion
coefficient, threaded into velocity's diffusive flux once TASK-031b
lands. Both default to `1.0`, `validate()` rejects `<= 0` for either, the
same plain-positive-number pattern `timestep`/`diffusion_coefficient`
already established. **A real, unplanned engine-side consequence of the
migration, not just a schema move**: `engine/numerics/assembly.py`'s
`assemble_numerics` already read `NumericsConfig.diffusion_coefficient`
directly (TASK-024) -- moving the field out from under it needed a real
signature change (`assemble_numerics` gained its own `diffusion_
coefficient: float = 1.0` parameter), not just a schema and a config
file. See `src/pyflow/engine/CLAUDE.md`'s `assembly.py` entry for the
mechanical detail, and `docs/planning/roadmap.md` TASK-041's own Status
note for why this task's own Dependencies section ("no engine dependency
at all") was wrong as drafted.

**`BoundaryFaceConfig`/`BoundaryConditionsConfig`** (TASK-019, added
2026-08-23): `NumericsConfig.boundary_conditions`, one
Expand Down
Loading
Loading