Files
ClaudeandClaude Opus 5.5 48ee0982c9 tests/docs: unique-name driver loading; moving-path lifecycle and settings notes; VIV predeclare renamed
Tests load research drivers via importlib under unique module names
instead of a bare 'import run' (shared-process collision between the
Dütsch and VIV drivers). lifecycle.md qualifies immutable geometry and the
host-only get_flags for translating circles; supported_settings.md points to
the dense-only moving-circle feature; leontini_viv_predeclare.md is renamed
viv_re150_predeclare.md (Re 150 Kolahdouz/Qin case) with references updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-30 16:12:05 +00:00

4.3 KiB

Device state lifecycle (uniform Simulation, FixedPatchStepper)

State is grouped by what a change invalidates. This file describes the code as it is; it adds no framework. Line references are to candidate 0f8fdbc3.

Class Contents How it changes Owner rule
1. Module and its globals Compile-time (visible to the compiler, fixed per module): config.h macros incl. NX/NY, COLLISION_MODEL, BLOCK_ROOT_BC, WALL_*. Runtime per-module globals (not visible to the compiler; written by host code): __constant__ d_params, d_w, d_cx, d_cy, d_block_channel, d_block_wall; plus bound kernel handles Globals are written once by the owner right after load_module. Uniform grid: Simulation._recompile (construction, body-topology sync) loads a new module and re-uploads; update_runtime_params rewrites d_params (omega, force, n_objects) between runs. Steppers: never rewritten after construction, so they are immutable for the instance's lifetime although they are runtime values. The blocked slot/reservoir root reads its slot from the d_block_wall constant (the former dense route used WALL_* macros; both were bit-identical in the parity gates). Every owner loads its own module (compiler.load_module = module_from_file, no cache). A loaded module is never shared between live owners: globals are per module, so sharing would share mutable d_params.
2. Geometry, layout, tables flags, curved-link tables, block layouts and halo pairs, transfer (explode/coalesce) tables, block kinds Immutable for the life of an instance, except translating circles on the uniform grid: their swept-band flags and cut links are rewritten on the device every advancing step (MovingCircleCells/MovingCircleLinks, docs/moving_bodies.md); the host flag mirror is then stale for those cells. Uniform grid otherwise: rebuilt by body sync. FixedPatchStepper: a geometry change is a new instance (remap_finest), committed only after a successful transfer. No in-place table edits.
3. Controls and dependent boundary columns Jet amplitude -> per-link surface_velocity; body actions (uniform grid) Between steps, without a table or module rebuild (set_jet_amplitude). Input (index, level, amplitude) is validated before any device write; invalid input changes nothing and the instance stays usable. The host copy commits only after the upload returned. Any error after validation leaves the device column undefined and fails the instance: FixedPatchStepper status failed, Simulation._failed; every later device-touching public method (Simulation: stream, set_jet_amplitude, initialize, sync_bodies, run/step, read_surface_exchange, read_body(s), get_macroscopic, get_ddf, update_runtime_params, snapshot, restore, save/load checkpoint) raises; cached host readers and host-side staging (e.g. get_flags without translating circles, add_body/remove_body before a sync) do not touch the device and stay callable; with translating circles get_flags reads the device flags back and refuses after a failure. Translating-circle targets follow the same rule: set_body validates before mutation, the applied offset commits only after a successful synchronized step, and a failed moving step fails the instance (docs/moving_bodies.md). close() still releases resources and stays retryable.
4. Per-step state population double buffers and their parity, raw-exchange ledgers, observations Device-only during step(n) / run(n); buffer swaps are host-handle swaps. Valid on the host only after the single wait at the end of the call.

Storage routes are separate concerns:

  • Original uniform grid (uniform.cu, Simulation): whole-domain NX/NY.
  • Blocked levels (blocked.cu; every FixedPatchStepper level, including the single level of refinements=[]): NX/NY are one block window including halo.
  • Dense refinement levels (refinement.cu) were removed: level_blocks is required and every refinement level is blocked.

A single-rectangle refinement (FixedPatch2D) is stored in its own fine window frame (FixedPatchStepper.__init__, BlockLayout2D.build(*pair.fine_shape, ...)). Only a rectangle set (FixedPatchSet2D) uses the level-wide frame (2*ny, 2*nx), whose fine_layout requires that full grid to be divisible by the tile (refinement.py:240-252).