diff --git a/adr/ADR-002-fvm-first.md b/adr/ADR-002-fvm-first.md index d44dc62..6ba6893 100644 --- a/adr/ADR-002-fvm-first.md +++ b/adr/ADR-002-fvm-first.md @@ -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 diff --git a/docs/architecture/icds.md b/docs/architecture/icds.md index bce5686..a410e8a 100644 --- a/docs/architecture/icds.md +++ b/docs/architecture/icds.md @@ -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 diff --git a/docs/architecture/sequences.md b/docs/architecture/sequences.md index e7ec9c2..816efd4 100644 --- a/docs/architecture/sequences.md +++ b/docs/architecture/sequences.md @@ -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 @@ -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
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,
constructed once, held for the run bootstrap->>Window: window.scene.add(...), window.assembled_numerics = ... Note over Window: process memory only --
nothing here is written to disk diff --git a/docs/implementation/config-template.yaml b/docs/implementation/config-template.yaml index aa4925a..c3051e4 100644 --- a/docs/implementation/config-template.yaml +++ b/docs/implementation/config-template.yaml @@ -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: @@ -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 diff --git a/docs/planning/roadmap.md b/docs/planning/roadmap.md index 0c9bb7e..9c2ab01 100644 --- a/docs/planning/roadmap.md +++ b/docs/planning/roadmap.md @@ -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 @@ -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 @@ -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`, @@ -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 @@ -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 @@ -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 diff --git a/docs/planning/status.md b/docs/planning/status.md index 51b53af..16d88b8 100644 --- a/docs/planning/status.md +++ b/docs/planning/status.md @@ -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 @@ -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 @@ -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 | | | diff --git a/docs/repository-inventory.md b/docs/repository-inventory.md index 6b7e4de..cc4cfaf 100644 --- a/docs/repository-inventory.md +++ b/docs/repository-inventory.md @@ -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) @@ -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` @@ -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` diff --git a/docs/repository-manifest.md b/docs/repository-manifest.md index 04ab0e8..970debd 100644 --- a/docs/repository-manifest.md +++ b/docs/repository-manifest.md @@ -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`/ @@ -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 diff --git a/examples/golden-demos/passive_scalar_transport.yaml b/examples/golden-demos/passive_scalar_transport.yaml index 44f9dde..821ba3f 100644 --- a/examples/golden-demos/passive_scalar_transport.yaml +++ b/examples/golden-demos/passive_scalar_transport.yaml @@ -24,7 +24,6 @@ mesh: numerics: timestep: 0.02 - diffusion_coefficient: 0.02 boundary_conditions: east: type: periodic @@ -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" diff --git a/src/pyflow/bootstrap.py b/src/pyflow/bootstrap.py index 3d8bd17..7db51dc 100644 --- a/src/pyflow/bootstrap.py +++ b/src/pyflow/bootstrap.py @@ -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 = ( diff --git a/src/pyflow/configuration/CLAUDE.md b/src/pyflow/configuration/CLAUDE.md index 157a424..5e3a584 100644 --- a/src/pyflow/configuration/CLAUDE.md +++ b/src/pyflow/configuration/CLAUDE.md @@ -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 diff --git a/src/pyflow/configuration/loader.py b/src/pyflow/configuration/loader.py index accd9a1..3f4b4f7 100644 --- a/src/pyflow/configuration/loader.py +++ b/src/pyflow/configuration/loader.py @@ -18,6 +18,7 @@ BoundaryConditionsConfig, BoundaryFaceConfig, FieldDisplayConfig, + FluidConfig, LoggingConfig, MeshConfig, NumericsConfig, @@ -48,6 +49,11 @@ def _boundary_conditions_config_from_raw(raw: object) -> BoundaryConditionsConfi def _numerics_config_from_raw(raw: dict[str, Any]) -> NumericsConfig: raw = dict(raw) + if "diffusion_coefficient" in raw: + raise ValueError( + "numerics.diffusion_coefficient has moved to fluid.diffusion_coefficient " + "(TASK-041) -- update your configuration file" + ) boundary_conditions_raw = raw.pop("boundary_conditions", {}) return NumericsConfig( boundary_conditions=_boundary_conditions_config_from_raw(boundary_conditions_raw), @@ -104,6 +110,7 @@ def load_config(path: str | Path | None = None) -> PyFlowConfig: mesh=MeshConfig(**raw.get("mesh", {})), field_display=FieldDisplayConfig(**raw.get("field_display", {})), simulation=SimulationConfig(**raw.get("simulation", {})), + fluid=FluidConfig(**raw.get("fluid", {})), numerics=_numerics_config_from_raw(raw.get("numerics", {})), ) config.validate() diff --git a/src/pyflow/configuration/schema.py b/src/pyflow/configuration/schema.py index 3f091b6..182886a 100644 --- a/src/pyflow/configuration/schema.py +++ b/src/pyflow/configuration/schema.py @@ -389,6 +389,39 @@ def validate(self) -> None: ) +@dataclass +class FluidConfig: + """Physical properties of the simulated fluid (TASK-041, added + 2026-08-28) -- separate from `NumericsConfig` below, which selects + numerical *schemes* and their solver tunables, not properties of the + fluid those schemes act on (Stage 5's design question four, + `docs/planning/roadmap.md`). + + `diffusion_coefficient` migrates here from + `NumericsConfig.diffusion_coefficient` (TASK-024's original field) -- + Gamma, a transported scalar's own diffusivity. `viscosity` is new: + momentum's own diffusion coefficient, distinct from a scalar's + (TASK-031b threads it into the diffusive flux `assemble_numerics` + builds for velocity's own components). Both default to `1.0`, the + same arbitrary-MVP-scaffolding reasoning `NumericsConfig.timestep`'s + `0.01` already carries -- no golden demo or handbook page names a + specific value for either yet. + """ + + viscosity: float = 1.0 + diffusion_coefficient: float = 1.0 + + def validate(self) -> None: + _require_number(self.viscosity, "fluid.viscosity") + if self.viscosity <= 0: + raise ValueError(f"fluid.viscosity must be > 0, got {self.viscosity!r}") + _require_number(self.diffusion_coefficient, "fluid.diffusion_coefficient") + if self.diffusion_coefficient <= 0: + raise ValueError( + f"fluid.diffusion_coefficient must be > 0, got {self.diffusion_coefficient!r}" + ) + + AdvectionSchemeName = Literal["first_order_upwind"] DiffusionSchemeName = Literal["central_difference"] TimeIntegrationSchemeName = Literal["rk4"] @@ -581,20 +614,21 @@ class NumericsConfig: specific ones yet; rejecting each at `<= 0` is the one acceptance criterion each field carries on its own. - `diffusion_coefficient` (TASK-024, added 2026-08-27) follows the same - plain-positive-number pattern as `timestep`: this is Gamma - (`docs/handbook/numerical-methods/diffusion.md`), a physical - property of what's being transported, not a scheme choice, so it's - a number, not a name from a closed set. Threaded into - `CentralDifferenceDiffusion`'s constructor by `assembly.py`, the - same mechanism TASK-040 used to thread `boundary_conditions` into - the advection/diffusion factories. Its default of `1.0` is - arbitrary MVP scaffolding, same reasoning as `timestep`'s `0.01`. + `diffusion_coefficient` (TASK-024, added 2026-08-27; **migrated to + `FluidConfig.diffusion_coefficient` by TASK-041, 2026-08-28**) used + to live here, following the same plain-positive-number pattern as + `timestep`: Gamma (`docs/handbook/numerical-methods/diffusion.md`), + a physical property of what's being transported, not a scheme + choice -- which is exactly why it moved: a fluid property has no + business in the section that selects numerical schemes (Stage 5's + design question four, `docs/planning/roadmap.md`). A config still + setting `numerics.diffusion_coefficient` is rejected with a named + error pointing at its new home (`loader.py`'s + `_numerics_config_from_raw`), not silently ignored. """ advection: AdvectionSchemeName = "first_order_upwind" diffusion: DiffusionSchemeName = "central_difference" - diffusion_coefficient: float = 1.0 time_integration: TimeIntegrationSchemeName = "rk4" timestep: float = 0.01 linear_solver: LinearSolverName = "conjugate_gradient" @@ -614,10 +648,6 @@ def validate(self) -> None: f"numerics.diffusion must be one of {sorted(_VALID_DIFFUSION_SCHEMES)}, " f"got {self.diffusion!r}" ) - if self.diffusion_coefficient <= 0: - raise ValueError( - f"numerics.diffusion_coefficient must be > 0, got {self.diffusion_coefficient!r}" - ) if self.time_integration not in _VALID_TIME_INTEGRATION_SCHEMES: raise ValueError( f"numerics.time_integration must be one of " @@ -657,6 +687,7 @@ class PyFlowConfig: mesh: MeshConfig = field(default_factory=MeshConfig) field_display: FieldDisplayConfig = field(default_factory=FieldDisplayConfig) simulation: SimulationConfig = field(default_factory=SimulationConfig) + fluid: FluidConfig = field(default_factory=FluidConfig) numerics: NumericsConfig = field(default_factory=NumericsConfig) def validate(self) -> None: @@ -665,5 +696,6 @@ def validate(self) -> None: self.mesh.validate() self.field_display.validate() self.simulation.validate() + self.fluid.validate() self.numerics.validate() _validate_boundary_conditions_jointly(self.mesh, self.numerics.boundary_conditions) diff --git a/src/pyflow/engine/CLAUDE.md b/src/pyflow/engine/CLAUDE.md index 0236105..f924a98 100644 --- a/src/pyflow/engine/CLAUDE.md +++ b/src/pyflow/engine/CLAUDE.md @@ -945,8 +945,9 @@ type annotation there and `from __future__ import annotations` already makes that lazy -- costs nothing at runtime, and `accumulate_flux_to_cells` itself did not need to move. -**`assembly.py`** (TASK-021) is `assemble_numerics(NumericsConfig) -> -AssembledNumerics` and the six independent registries +**`assembly.py`** (TASK-021) is `assemble_numerics(NumericsConfig, +diffusion_coefficient: float = 1.0) -> AssembledNumerics` and the six +independent registries (`register_advection_scheme` and five siblings) it resolves a configured name through -- Stage 3 Completion Criterion 3's mechanism: adding a name means calling a `register_*` function, never editing @@ -1029,6 +1030,24 @@ helper this module used to route advection through, `_resolve_with_argument`, is deleted in the same change as genuinely dead code -- its only caller moved to the two-argument helper. +**TASK-041 (2026-08-28, Stage 5's first task) widens `assemble_numerics` +a third time, but for the opposite reason -- a field left, not arrived.** +`FluidConfig.diffusion_coefficient` (`src/pyflow/configuration/ +schema.py`) replaces `NumericsConfig.diffusion_coefficient`, so +`assemble_numerics` can no longer read Gamma off its own `config` +parameter -- found while implementing TASK-041, not anticipated by that +task's own drafted Dependencies section (`docs/planning/roadmap.md` +TASK-041's own Status note records the gap). `assemble_numerics` gained +a second parameter, `diffusion_coefficient: float = 1.0` -- the default +matches `FluidConfig`'s own, so every existing caller that only cares +about scheme selection (nearly all of `tests/unit/numerics/ +test_assembly.py`) keeps working unedited; `bootstrap.py`'s one real +call site threads `config.fluid.diffusion_coefficient` through +explicitly. `register_diffusion_scheme`'s own factory shape is +unchanged -- a concrete diffusion scheme still receives +`diffusion_coefficient` as its third constructor argument, now sourced +from this new parameter instead of `config.diffusion_coefficient`. + **Registration refuses to overwrite a different factory** (`DuplicateSchemeError`, added 2026-08-24). The registries are module-level and filled by import side effect, so "last import wins" diff --git a/src/pyflow/engine/numerics/assembly.py b/src/pyflow/engine/numerics/assembly.py index 118f1f7..cb135df 100644 --- a/src/pyflow/engine/numerics/assembly.py +++ b/src/pyflow/engine/numerics/assembly.py @@ -195,11 +195,13 @@ def register_diffusion_scheme( diffusion_coefficient)` in future `assemble_numerics` calls -- `boundary_conditions`/`periodic_pairs` the same as `register_advection_scheme`'s own, `diffusion_coefficient` is - `NumericsConfig.diffusion_coefficient` (TASK-024's own Design - decision, `docs/planning/roadmap.md`): a concrete diffusion scheme is - constructed with the physical coefficient (Gamma) it needs, the same - "constructed with it, not handed it after the fact" reasoning - `boundary_conditions` already established. + `assemble_numerics`'s own `diffusion_coefficient` parameter + (`FluidConfig.diffusion_coefficient` since TASK-041, 2026-08-28 -- + `NumericsConfig.diffusion_coefficient` before it, TASK-024's original + field): a concrete diffusion scheme is constructed with the physical + coefficient (Gamma) it needs, the same "constructed with it, not + handed it after the fact" reasoning `boundary_conditions` already + established. """ _register(_diffusion_registry, name, factory, "diffusion") @@ -297,9 +299,22 @@ def _resolve_with_three_arguments[T, A, B, C]( return factory(argument_a, argument_b, argument_c) -def assemble_numerics(config: NumericsConfig) -> AssembledNumerics: +def assemble_numerics( + config: NumericsConfig, diffusion_coefficient: float = 1.0 +) -> AssembledNumerics: """Resolve every name in `config` to a live instance. + `diffusion_coefficient` is `FluidConfig.diffusion_coefficient` + (`src/pyflow/configuration/schema.py`) -- passed in separately, not + read off `config`, because it moved out of `NumericsConfig` in + TASK-041 (2026-08-28): a fluid property, not a scheme choice, so it + no longer lives in the section this function's own `config` + parameter is typed against. Defaults to `1.0`, `FluidConfig`'s own + default, so every existing caller that only cares about scheme + selection keeps working unchanged; a real run threads + `config.fluid.diffusion_coefficient` through explicitly + (`bootstrap.py`). + Reads `config` once; the returned `AssembledNumerics` holds instances, not a reference back to `config` -- mutating `config` afterwards changes nothing about what was already assembled (Stage 3 @@ -343,7 +358,7 @@ def assemble_numerics(config: NumericsConfig) -> AssembledNumerics: config.diffusion, boundary_conditions, periodic_pairs, - config.diffusion_coefficient, + diffusion_coefficient, "diffusion", ) time_integration = _resolve( diff --git a/src/pyflow/engine/numerics/diffusion.py b/src/pyflow/engine/numerics/diffusion.py index c5abe83..5415c4f 100644 --- a/src/pyflow/engine/numerics/diffusion.py +++ b/src/pyflow/engine/numerics/diffusion.py @@ -66,8 +66,10 @@ class CentralDifferenceDiffusion(DiffusionScheme): Boundary-aware by construction, the same pattern `FirstOrderUpwindAdvection` establishes (TASK-040's own Design decision): holds the boundary conditions and the diffusion - coefficient (Gamma, `NumericsConfig.diffusion_coefficient`) it needs, - rather than the orchestrator substituting either in afterward. + coefficient (Gamma, `FluidConfig.diffusion_coefficient` -- migrated + from `NumericsConfig.diffusion_coefficient` by TASK-041, 2026-08-28) + it needs, rather than the orchestrator substituting either in + afterward. **Periodic-aware the same way `FirstOrderUpwindAdvection` is (TASK-030).** At a face named in `periodic_pairs`, `flux` substitutes diff --git a/tests/features/fluid_configuration.feature b/tests/features/fluid_configuration.feature new file mode 100644 index 0000000..4e62295 --- /dev/null +++ b/tests/features/fluid_configuration.feature @@ -0,0 +1,42 @@ +# The acceptance criteria for Fluid Configuration Section (TASK-041, +# Stage 5's first task). Not a golden demo of its own -- no new config +# file under `examples/golden-demos/`, reusing the existing Passive +# Scalar Transport demo for the migration scenario instead -- but its +# own claims are genuinely user-observable (loading a configuration +# file), so `tests/integration/test_fluid_configuration.py` binds these +# scenarios, crossing the real CLI subprocess boundary the last scenario +# needs (`tests/CLAUDE.md`'s own split between `unit/` and +# `integration/`), rather than living in `tests/unit/`. +# +# `numerics.diffusion_coefficient` moves into a new `fluid:` section +# alongside a new `fluid.viscosity`. The criterion that matters is that +# the break is complete and visible, not partial and silent +# (`docs/planning/roadmap.md` TASK-041's own Intent): a config still +# setting the old field must fail loudly, never run with a silently +# substituted default. Per-field type/range rejection (a non-numeric or +# non-positive value) is not repeated here -- it stays in +# `tests/unit/test_configuration.py`, where every other field's own +# rejection tests already live; these scenarios carry the migration's +# own claims only. + +Feature: Fluid Configuration Section + + Scenario: A fluid configuration section loads both of its fields + Given a configuration file setting fluid.viscosity and fluid.diffusion_coefficient + When the configuration is loaded + Then both values arrive on the loaded configuration's fluid section + + Scenario: Setting one fluid field leaves the other at its own default + Given a configuration file setting only fluid.viscosity + When the configuration is loaded + Then fluid.diffusion_coefficient is still its own default value + + Scenario: A configuration still setting the retired numerics field is rejected, not silently defaulted + Given a configuration file setting numerics.diffusion_coefficient + When the configuration is loaded + Then loading is rejected with a named error saying the field moved to fluid.diffusion_coefficient + + Scenario: The Passive Scalar Transport golden demo still runs through the real CLI after the migration + Given the Passive Scalar Transport golden demo's own committed configuration file + When the demo is run through the real CLI as a subprocess + Then the process exits successfully diff --git a/tests/integration/test_cli.py b/tests/integration/test_cli.py index ecc69b5..3175b51 100644 --- a/tests/integration/test_cli.py +++ b/tests/integration/test_cli.py @@ -65,6 +65,7 @@ def test_generate_config_prints_valid_yaml_to_stdout() -> None: "mesh", "field_display", "simulation", + "fluid", "numerics", ] @@ -98,6 +99,7 @@ def test_generate_config_output_writes_file_and_round_trips_through_run( "mesh", "field_display", "simulation", + "fluid", "numerics", ] diff --git a/tests/integration/test_fluid_configuration.py b/tests/integration/test_fluid_configuration.py new file mode 100644 index 0000000..8f2b0c5 --- /dev/null +++ b/tests/integration/test_fluid_configuration.py @@ -0,0 +1,138 @@ +"""Binds `tests/features/fluid_configuration.feature` (TASK-041) -- +Stage 5's first task, moving `numerics.diffusion_coefficient` into a new +`fluid:` section alongside a new `fluid.viscosity`. Lives here, not +`tests/unit/`, because its own last scenario needs a real CLI subprocess +run (`tests/CLAUDE.md`'s own unit/integration split); not a golden demo +either -- no new config file under `examples/golden-demos/`, reusing the +existing Passive Scalar Transport demo's own committed file for the +migration scenario instead of a fixture copy, so a real drift between +that file and the schema is exactly what this scenario would catch. +Supplies its own local steps rather than drawing on +`tests/golden/conftest.py`'s demo-running vocabulary, which a +`conftest.py`'s own directory scoping could not reach from here anyway. +""" + +from __future__ import annotations + +import subprocess +import sys +from dataclasses import dataclass +from pathlib import Path + +from pytest_bdd import given, scenarios, then, when + +from pyflow.configuration import PyFlowConfig, load_config + +scenarios("fluid_configuration.feature") + +_REPO_ROOT = Path(__file__).resolve().parents[2] + + +@dataclass +class _Context: + config_path: Path + loaded: PyFlowConfig | None = None + error: ValueError | None = None + process: subprocess.CompletedProcess[str] | None = None + + +# -- Given ------------------------------------------------------------- + + +@given( + "a configuration file setting fluid.viscosity and fluid.diffusion_coefficient", + target_fixture="ctx", +) +def _given_both_fluid_fields(tmp_path: Path) -> _Context: + config_path = tmp_path / "config.yaml" + config_path.write_text("fluid:\n viscosity: 2.5\n diffusion_coefficient: 3.5\n") + return _Context(config_path=config_path) + + +@given("a configuration file setting only fluid.viscosity", target_fixture="ctx") +def _given_only_viscosity(tmp_path: Path) -> _Context: + config_path = tmp_path / "config.yaml" + config_path.write_text("fluid:\n viscosity: 2.5\n") + return _Context(config_path=config_path) + + +@given("a configuration file setting numerics.diffusion_coefficient", target_fixture="ctx") +def _given_stale_numerics_field(tmp_path: Path) -> _Context: + config_path = tmp_path / "config.yaml" + config_path.write_text("numerics:\n diffusion_coefficient: 3.5\n") + return _Context(config_path=config_path) + + +@given( + "the Passive Scalar Transport golden demo's own committed configuration file", + target_fixture="ctx", +) +def _given_the_real_demo_config() -> _Context: + config_path = _REPO_ROOT / "examples" / "golden-demos" / "passive_scalar_transport.yaml" + assert config_path.is_file(), f"expected the real demo config at {config_path}" + return _Context(config_path=config_path) + + +# -- When ---------------------------------------------------------------- + + +@when("the configuration is loaded") +def _when_loaded(ctx: _Context) -> None: + try: + ctx.loaded = load_config(ctx.config_path) + except ValueError as exc: + ctx.error = exc + + +@when("the demo is run through the real CLI as a subprocess") +def _when_run_through_the_cli(ctx: _Context) -> None: + ctx.process = subprocess.run( + [ + sys.executable, + "-m", + "pyflow", + "run", + "--config", + str(ctx.config_path), + "--backend", + "offscreen", + "--max-frames", + "2", + ], + capture_output=True, + text=True, + check=False, + ) + + +# -- Then ------------------------------------------------------------------ + + +@then("both values arrive on the loaded configuration's fluid section") +def _then_both_values_arrive(ctx: _Context) -> None: + assert ctx.loaded is not None, ctx.error + assert ctx.loaded.fluid.viscosity == 2.5 + assert ctx.loaded.fluid.diffusion_coefficient == 3.5 + + +@then("fluid.diffusion_coefficient is still its own default value") +def _then_diffusion_coefficient_stays_default(ctx: _Context) -> None: + assert ctx.loaded is not None, ctx.error + assert ctx.loaded.fluid.viscosity == 2.5 + assert ctx.loaded.fluid.diffusion_coefficient == PyFlowConfig().fluid.diffusion_coefficient + + +@then( + "loading is rejected with a named error saying the field moved to fluid.diffusion_coefficient" +) +def _then_rejected_naming_the_new_home(ctx: _Context) -> None: + assert ctx.error is not None, "expected load_config to reject the retired field" + message = str(ctx.error) + assert "numerics.diffusion_coefficient" in message + assert "fluid.diffusion_coefficient" in message + + +@then("the process exits successfully") +def _then_the_process_exits_successfully(ctx: _Context) -> None: + assert ctx.process is not None + assert ctx.process.returncode == 0, ctx.process.stderr diff --git a/tests/unit/numerics/test_assembly.py b/tests/unit/numerics/test_assembly.py index 27c0266..7368c22 100644 --- a/tests/unit/numerics/test_assembly.py +++ b/tests/unit/numerics/test_assembly.py @@ -308,19 +308,20 @@ def test_diffusion_factory_receives_the_resolved_boundary_conditions_and_coeffic # Every other test-only scheme in this module discards its # constructor arguments -- this is the one that proves # `assemble_numerics` actually threads the resolved boundary - # conditions *and* `config.diffusion_coefficient` into the diffusion - # factory, not stale or default ones. + # conditions *and* its own `diffusion_coefficient` parameter into the + # diffusion factory, not stale or default ones. `diffusion_coefficient` + # is `assemble_numerics`'s own second parameter, not a `NumericsConfig` + # field, since TASK-041 (2026-08-28) moved it to `FluidConfig`. name = "test_only_capturing_diffusion_for_assembly_test" register_diffusion_scheme(name, _CapturingDiffusion) config = NumericsConfig( diffusion=name, # type: ignore[arg-type] - diffusion_coefficient=3.5, boundary_conditions=BoundaryConditionsConfig( north=BoundaryFaceConfig(type="dirichlet", velocity=2.5, pressure=None), ), ) - assembled = assemble_numerics(config) + assembled = assemble_numerics(config, diffusion_coefficient=3.5) assert isinstance(assembled.diffusion, _CapturingDiffusion) assert assembled.diffusion.received_boundary_conditions == assembled.boundary_conditions diff --git a/tests/unit/test_configuration.py b/tests/unit/test_configuration.py index 129fa87..a145946 100644 --- a/tests/unit/test_configuration.py +++ b/tests/unit/test_configuration.py @@ -35,9 +35,10 @@ def test_defaults_are_valid() -> None: assert config.simulation.scalar_pattern is None assert config.simulation.velocity_pattern is None assert config.simulation.velocity == (1.0, 0.0) + assert config.fluid.viscosity == 1.0 + assert config.fluid.diffusion_coefficient == 1.0 assert config.numerics.advection == "first_order_upwind" assert config.numerics.diffusion == "central_difference" - assert config.numerics.diffusion_coefficient == 1.0 assert config.numerics.time_integration == "rk4" assert config.numerics.timestep == 0.01 assert config.numerics.linear_solver == "conjugate_gradient" @@ -549,26 +550,62 @@ def test_load_config_rejects_an_unknown_diffusion_scheme(tmp_path: Path) -> None def test_load_config_reads_diffusion_coefficient(tmp_path: Path) -> None: config_file = tmp_path / "config.yaml" - config_file.write_text("numerics:\n diffusion_coefficient: 2.5\n") + config_file.write_text("fluid:\n diffusion_coefficient: 2.5\n") config = load_config(config_file) - assert config.numerics.diffusion_coefficient == 2.5 + assert config.fluid.diffusion_coefficient == 2.5 def test_load_config_rejects_a_non_positive_diffusion_coefficient(tmp_path: Path) -> None: config_file = tmp_path / "config.yaml" - config_file.write_text("numerics:\n diffusion_coefficient: 0.0\n") + config_file.write_text("fluid:\n diffusion_coefficient: 0.0\n") - with pytest.raises(ValueError, match="numerics.diffusion_coefficient"): + with pytest.raises(ValueError, match="fluid.diffusion_coefficient"): load_config(config_file) def test_load_config_rejects_a_negative_diffusion_coefficient(tmp_path: Path) -> None: config_file = tmp_path / "config.yaml" - config_file.write_text("numerics:\n diffusion_coefficient: -1.0\n") + config_file.write_text("fluid:\n diffusion_coefficient: -1.0\n") - with pytest.raises(ValueError, match="numerics.diffusion_coefficient"): + with pytest.raises(ValueError, match="fluid.diffusion_coefficient"): + load_config(config_file) + + +# -- FluidConfig (TASK-041) ------------------------------------------------ + + +def test_load_config_reads_viscosity(tmp_path: Path) -> None: + config_file = tmp_path / "config.yaml" + config_file.write_text("fluid:\n viscosity: 3.0\n") + + config = load_config(config_file) + + assert config.fluid.viscosity == 3.0 + + +def test_load_config_rejects_a_non_positive_viscosity(tmp_path: Path) -> None: + config_file = tmp_path / "config.yaml" + config_file.write_text("fluid:\n viscosity: 0.0\n") + + with pytest.raises(ValueError, match="fluid.viscosity"): + load_config(config_file) + + +def test_load_config_rejects_a_negative_viscosity(tmp_path: Path) -> None: + config_file = tmp_path / "config.yaml" + config_file.write_text("fluid:\n viscosity: -1.0\n") + + with pytest.raises(ValueError, match="fluid.viscosity"): + load_config(config_file) + + +def test_load_config_rejects_a_non_numeric_viscosity(tmp_path: Path) -> None: + config_file = tmp_path / "config.yaml" + config_file.write_text("fluid:\n viscosity: not-a-number\n") + + with pytest.raises(ValueError, match="fluid.viscosity"): load_config(config_file) diff --git a/tests/unit/test_generator.py b/tests/unit/test_generator.py index 6c16810..5c35c28 100644 --- a/tests/unit/test_generator.py +++ b/tests/unit/test_generator.py @@ -140,8 +140,8 @@ def test_top_level_key_order_matches_pyflowconfig_field_order() -> None: schema's own declared field order -- it only proves the dict iteration order was preserved. Check the parsed keys directly against `PyFlowConfig`'s declared order (`logging`, `rendering`, - `mesh`, `field_display`, `simulation`, `numerics`), not assumed from - the dumper flag. + `mesh`, `field_display`, `simulation`, `fluid`, `numerics`), not + assumed from the dumper flag. """ text = generate_config_yaml(PyFlowConfig()) @@ -153,5 +153,6 @@ def test_top_level_key_order_matches_pyflowconfig_field_order() -> None: "mesh", "field_display", "simulation", + "fluid", "numerics", ] diff --git a/tests/unit/test_main.py b/tests/unit/test_main.py index dd90b8f..23a5418 100644 --- a/tests/unit/test_main.py +++ b/tests/unit/test_main.py @@ -120,10 +120,13 @@ def test_generate_config_with_no_output_prints_to_stdout( "velocity_pattern": None, "velocity": [1.0, 0.0], }, + "fluid": { + "viscosity": 1.0, + "diffusion_coefficient": 1.0, + }, "numerics": { "advection": "first_order_upwind", "diffusion": "central_difference", - "diffusion_coefficient": 1.0, "time_integration": "rk4", "timestep": 0.01, "linear_solver": "conjugate_gradient", @@ -180,6 +183,7 @@ def test_generate_config_with_output_writes_file_and_prints_nothing( "mesh", "field_display", "simulation", + "fluid", "numerics", ] assert written["mesh"]["extent"] == list(PyFlowConfig().mesh.extent) diff --git a/tools/generators/generate_config_template.py b/tools/generators/generate_config_template.py index 8453ad2..17d9d80 100644 --- a/tools/generators/generate_config_template.py +++ b/tools/generators/generate_config_template.py @@ -75,6 +75,12 @@ "Live, repeatedly-stepped simulation seeding -- distinct from " "field_display above, which renders one static frame." ), + "fluid": ( + "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." + ), "numerics": ( "Numerical scheme selection (adr/ADR-003-modular-numerical-" "strategies.md) plus the physical/solver parameters those " @@ -185,6 +191,19 @@ "solves for velocity). Invalid: anything other than exactly two " "numbers." ), + "fluid.viscosity": ( + "Valid: a positive number -- momentum's own diffusion " + "coefficient, distinct from diffusion_coefficient below (a " + "transported scalar's own). Invalid: zero or negative." + ), + "fluid.diffusion_coefficient": ( + "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." + ), "numerics.advection": ( 'Valid: "first_order_upwind" -- the only scheme PyFlow currently ' "implements for this component. Invalid: any other string." @@ -193,9 +212,6 @@ 'Valid: "central_difference" -- the only scheme PyFlow currently ' "implements for this component. Invalid: any other string." ), - "numerics.diffusion_coefficient": ( - "Valid: a positive number (the physical diffusivity, Gamma). Invalid: zero or negative." - ), "numerics.time_integration": ( 'Valid: "rk4" -- the only scheme PyFlow currently implements ' "for this component. Invalid: any other string." diff --git a/tools/validators/check_references.py b/tools/validators/check_references.py index 2e474b9..18e6060 100644 --- a/tools/validators/check_references.py +++ b/tools/validators/check_references.py @@ -110,7 +110,6 @@ # change is what that Stage's own design question one decides, and a # guess here would be the speculation P-016 refuses. PLANNED: dict[str, str] = { - "tests/features/fluid_configuration.feature": "TASK-041", "tests/features/velocity_field_support.feature": "TASK-031", "tests/features/pressure_field.feature": "TASK-032", "tests/features/pressure_correction_loop.feature": "TASK-033",