Skip to content

TASK-031: Velocity Field Support (Stage 5, all four subtasks) - #44

Merged
AdamClemens merged 1 commit into
mainfrom
feat/task-031-velocity-field-support
Aug 29, 2026
Merged

TASK-031: Velocity Field Support (Stage 5, all four subtasks)#44
AdamClemens merged 1 commit into
mainfrom
feat/task-031-velocity-field-support

Conversation

@AdamClemens

Copy link
Copy Markdown
Owner

Summary

Stage 5's second task, built in one branch per the roadmap's own instruction ("meant to be done in one session"). Velocity is now something simulation.step() transports through the same AdvectionScheme/DiffusionScheme/TimeIntegrator path any scalar uses, rather than a fixed constant supplied from configuration -- the field-centric claim Stage 6 (four more transported fields) depends on being real.

Subtask (a) -- Velocity as component fields. VectorField.decompose()/.assemble() (vector_field.py): one real ScalarField per component and back, named by a fixed convention (component_name). Reassembly rejects a bad component count/mixed meshes with two new named errors. IncompatibleVelocityFieldError moved from advection.py to vector_field.py -- assemble()'s own rejection needed the same class _check_velocity already raises, and the reverse import would have been circular.

Subtask (b) -- Viscosity, distinct from a scalar's diffusivity. CentralDifferenceDiffusion/assemble_numerics gain a coefficient_overrides parameter: a momentum component is diffused with fluid.viscosity, an ordinary scalar keeps fluid.diffusion_coefficient -- dispatched by field.name inside the scheme, with which names get an override decided by bootstrap.py (the one place that legitimately knows a run's velocity field is called "velocity"), not baked into the scheme or the assembler.

Subtask (c) -- Per-field boundary values at one wall. DirichletBoundaryCondition/NeumannBoundaryCondition gain an overrides parameter, and BoundaryFaceConfig gains field_values/field_gradients: two fields transported in one run can each get their own prescribed value at the same wall (u = U, v = 0 at a moving lid). Exercised in the tests by two ordinary scalars, not a velocity pair -- the mechanism is general. Every existing call site passing only a single value is unaffected.

Subtask (d) -- Velocity advanced by step. bootstrap.py decomposes velocity around each step() call when simulation.velocity_solved is set, and reassembles it after. simulation.py itself needed no change -- velocity's own components are just more entries in the fields mapping it already treats uniformly; a scenario asserts the orchestrator's own source still carries no "velocity" string literal, isinstance(..., VectorField) check, or hardcoded component-name pair.

velocity_solved is a separate SimulationConfig field, not a widened velocity_pattern -- the same switch-vs-configured-thing mistake this project already made once with RenderingConfig.show_mesh/grid_color and didn't want to repeat.

A real gap found while implementing (recorded honestly in the roadmap, not silently narrowed): bootstrap.py's own live-loop wiring supports velocity_solved only alongside a configured scalar_pattern -- a velocity-only live run has no rendering path yet (no per-frame vector-arrow display exists). The mechanism itself is proven directly against simulation.step() regardless; TASK-034's Lid-Driven Cavity is the likely first consumer of velocity-only live rendering.

Test plan

  • make ci green: 642 tests passing, mypy --strict clean, all doc/graph/scenario/status/config-template checks pass.
  • TDD throughout: tests/features/velocity_field_support.feature (13 scenarios, grouped by subtask) written and confirmed red before each subtask's implementation.
  • Self-advection scenario checked against a hand-derived result on a small non-square mesh with non-unit spacing, not just plausibility.
  • test_bootstrap.py gained a real test proving the live bootstrap() loop (not only simulation.step() called directly) actually decomposes/steps/reassembles velocity -- found necessary by its own coverage report.
  • make check-references/make check-manifest/make check-scenarios all clean against the new files.

🤖 Generated with Claude Code

… (Stage 5)

Stage 5's second task, built in one branch per the roadmap's own
instruction. Velocity is now something simulation.step() transports
through the same Advection/Diffusion/TimeIntegrator path any scalar
uses, rather than a fixed constant supplied from configuration.

- (a) VectorField.decompose()/.assemble() (vector_field.py): one real
  ScalarField per component and back, named by a fixed convention
  (component_name). IncompatibleVelocityFieldError moved here from
  advection.py to avoid a circular import assemble()'s own rejection
  otherwise needed.
- (b) CentralDifferenceDiffusion/assemble_numerics gain a
  coefficient_overrides parameter: a momentum component is diffused
  with fluid.viscosity, an ordinary scalar keeps fluid.diffusion_
  coefficient -- dispatched by field.name, decided by bootstrap.py
  (the one place that legitimately knows a field is called "velocity"),
  not baked into the scheme or the assembler.
- (c) DirichletBoundaryCondition/NeumannBoundaryCondition gain an
  overrides parameter, and BoundaryFaceConfig gains field_values/
  field_gradients: two fields transported in one run can each get their
  own prescribed value at the same wall (u = U, v = 0 at a moving lid).
  Every existing call site is unaffected.
- (d) bootstrap.py decomposes velocity around each step() call when
  simulation.velocity_solved is set, and reassembles it after --
  simulation.py itself needed no change, since velocity's own
  components are just more entries in the fields mapping it already
  treats uniformly.

velocity_solved is a separate SimulationConfig field, not a widened
velocity_pattern -- the same switch-vs-configured-thing mistake this
project already made once with show_mesh/grid_color and didn't want to
repeat.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@AdamClemens
AdamClemens merged commit 1a104c1 into main Aug 29, 2026
2 checks passed
@AdamClemens
AdamClemens deleted the feat/task-031-velocity-field-support branch August 29, 2026 09:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant