docs: draft S0 contract for moving circles (not frozen)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b2034f87e0
commit
b89acd784a
1 file changed
+178
@@ -0,0 +1,178 @@
|
|||||||
|
# Moving circles on the uniform dense grid — S0 DRAFT contract
|
||||||
|
|
||||||
|
Status: **DRAFT for review, not frozen.** No implementation exists. Base: main
|
||||||
|
`b2034f87e03656c5f67ab88562a70d6c338b9918`. Scope approved by Frank (via TRON,
|
||||||
|
thread C0C78UFKGRM/1793335557.460509): uniform-grid prescribed translation and
|
||||||
|
single-cylinder free transverse VIV. Out of scope: refinement/blocked path,
|
||||||
|
pinball assembly, EsoPull, shifted storage, D3Q19, DynamisLab, manuscript.
|
||||||
|
|
||||||
|
Items marked **OPEN** are undecided; items marked **CANDIDATE** are proposals.
|
||||||
|
|
||||||
|
## 1. Working standards
|
||||||
|
|
||||||
|
- Lean, readable kernels in the existing idiom (`kernels/step/*.cu`, shared
|
||||||
|
`apply_bouzidi_link`); Python manages state and validation. No framework,
|
||||||
|
no compatibility layer, no new dependency.
|
||||||
|
- The existing stationary/rotating path (`CurvedBoundaryKernel`, host link
|
||||||
|
packing, `sync_bodies`) stays byte-identical. Moving circles are a separate,
|
||||||
|
explicitly enabled path with its own numerical contract (this file).
|
||||||
|
- One authority per state (section 4). No per-step full-domain download,
|
||||||
|
rebuild or recompile; local swept-band device updates are in scope.
|
||||||
|
- Small slices (S1 host spec, S2 kernels without motion, S3 motion + refill,
|
||||||
|
S4 oscillator); each reviewed SHA is frozen before targeted runs.
|
||||||
|
- Tolerances, benchmark parameters and the force composition used in the
|
||||||
|
structural RHS are declared before the runs that judge them. Fresh vs carried
|
||||||
|
evidence and existing baseline failures are recorded; smoke, convergence and
|
||||||
|
physical validation are reported separately. No undeclared force filtering.
|
||||||
|
- External code (waLBerla GPLv3, FluidX3D source-available, VIVSIM) is a
|
||||||
|
concept reference only; nothing is copied.
|
||||||
|
|
||||||
|
## 2. Current update order (main b2034f87, dense double buffer)
|
||||||
|
|
||||||
|
`Simulation.run(n)`: upload host action (if `upload_act`) → zero obs (if
|
||||||
|
`zero_obs`) → `DoubleBufferStepper.step(n)` → download obs → synchronize.
|
||||||
|
|
||||||
|
Per step (`lbm/stepper.py` `step`):
|
||||||
|
|
||||||
|
1. `CurvedBoundaryKernel` over the packed cut links: reads post-collision
|
||||||
|
`G^{n-1}` at `k_f` (and `k_f - c` for q < 1/2), adds the moving-wall term
|
||||||
|
(`Uw = -omega*ry, Vw = omega*rx` + optional per-link `surface_velocity`),
|
||||||
|
writes the reflected value into slot `opp` of the solid node `k_s`, and
|
||||||
|
accumulates `c(f_toward + f_reflected)` and torque into `obs`.
|
||||||
|
2. `OneStep` pull/collide `ddf → temp` (collides FLUID cells; obstacle
|
||||||
|
interiors flagged `SOLID|OBSTACLE|BC_CURVED`), then buffer swap.
|
||||||
|
3. `ForceRegionKernel`, `SensorKernel`.
|
||||||
|
|
||||||
|
Raw `obs` force is the force **on the body** (link derivation in section 6;
|
||||||
|
DFG docs report `Cd > 0` from it). Translation `vx, vy` in the action slots is
|
||||||
|
currently stored but not read by any curved kernel.
|
||||||
|
|
||||||
|
## 3. Proposed update order (moving circles)
|
||||||
|
|
||||||
|
Per step n, before the existing step 1:
|
||||||
|
|
||||||
|
- **M1 `MovingCircleCells`** (one thread per swept-band cell): classify with the
|
||||||
|
analytic predicates `in_old = inside(x, c^{n-1})`, `in_new = inside(x, c^n)`;
|
||||||
|
write `flag = in_new ? SOLID|OBSTACLE|BC_CURVED : FLUID`; covered cells
|
||||||
|
(`!in_old && in_new`) accumulate `node_cover`; fresh cells
|
||||||
|
(`in_old && !in_new`) are refilled (section 5) and accumulate `node_uncover`.
|
||||||
|
- **M2 `MovingCircleLinks`** (one thread per band cell × direction): compute
|
||||||
|
q and hit point at `c^n` (FP64, same formula as `find_circle_intersection`,
|
||||||
|
same clamp [0.01, 1] and fallback classes as `CircleGeometry.build_cut_links`),
|
||||||
|
skip non-cut slots, call `apply_bouzidi_link` with
|
||||||
|
`u_w = (vx^n, vy^n) + omega × (hit - c^n)`, accumulate the link terms
|
||||||
|
(section 6).
|
||||||
|
|
||||||
|
Then the unchanged steps 1–3. M2 must follow M1 because a q < 1/2 link may
|
||||||
|
read `k_f - c` at a fresh cell.
|
||||||
|
|
||||||
|
## 4. State, masks and authority
|
||||||
|
|
||||||
|
- Host `ObjectState` is the single authority for `offset = (dx, dy)`,
|
||||||
|
`velocity = (vx, vy)` and `omega`. Velocity is supplied, never differenced
|
||||||
|
from offsets.
|
||||||
|
- Per body a small device **motion record** `{c^{n-1}, c^n, u^n}` is uploaded
|
||||||
|
with the action buffer; action-slot meaning is not overloaded. (CANDIDATE
|
||||||
|
layout; OPEN.)
|
||||||
|
- Membership is never read from a flag that M1 is overwriting. The host
|
||||||
|
verifies at `add_body` that every swept-band cell has base flag FLUID, so the
|
||||||
|
band's base state is known without reading `flag_gpu`.
|
||||||
|
- `flag_gpu` is the device authority for band cells while a moving body
|
||||||
|
exists; the host flag mirror is stale for those cells and is not used.
|
||||||
|
|
||||||
|
## 5. Refill of fresh cells (CANDIDATE)
|
||||||
|
|
||||||
|
- Stored time level: everything in `ddf` at M1 is post-collision `G^{n-1}`; the
|
||||||
|
refill writes all 9 slots at that same level.
|
||||||
|
- `G = f_eq(rho_bar, u_w(x)) + G_neq(x_d)` where `rho_bar` = mean density of
|
||||||
|
valid neighbours and `x_d` = the valid neighbour best aligned with the outward
|
||||||
|
normal `(x - c^n)/|x - c^n|`; `G_neq(x_d) = G(x_d) - f_eq(rho(x_d), u(x_d))`.
|
||||||
|
- Valid donor: inside the domain, base FLUID, `!in_old && !in_new`.
|
||||||
|
- No valid donor for the NEQ part → equilibrium only (counted). No valid
|
||||||
|
neighbour at all → cannot happen when the per-step move is < 1 cell
|
||||||
|
(validated on host); counted anyway.
|
||||||
|
- Fresh cells only write, old-fluid cells only read: no race.
|
||||||
|
|
||||||
|
## 6. Discrete momentum ledger (to review; not a selection)
|
||||||
|
|
||||||
|
Notation per step n: `F^n` = fluid set after M1; links L = (k_f, dir) at
|
||||||
|
`c^n`; `f_t = G_dir(k_f)`; `f_r` = value M2 writes into `(k_s, opp)`.
|
||||||
|
Fluid momentum before streaming:
|
||||||
|
|
||||||
|
P_pre = P(G^{n-1} on F^{n-1}) - sum_cover sum_q c_q G_q + sum_fresh sum_q c_q G_q^refill
|
||||||
|
|
||||||
|
During the pull, every population entering a fluid cell comes from a fluid
|
||||||
|
cell except the BFL-written slots; `f_t` leaves into solid and is never pulled
|
||||||
|
by a fluid cell. Collision conserves momentum for SRT/MRT without forcing.
|
||||||
|
Hence, in exact arithmetic,
|
||||||
|
|
||||||
|
P_fluid^n - P_fluid^{n-1} = - sum_L c (f_t + f_r) - I_cover + I_uncover + B^n + S^n
|
||||||
|
|
||||||
|
with `B^n` external-boundary momentum (inlet/outlet/walls) and `S^n` forcing.
|
||||||
|
So **`F_CME + node_cover - node_uncover`** is, by construction, the body
|
||||||
|
momentum that balances the fluid ledger of *this* storage/update order.
|
||||||
|
To be checked analytically in review: q < 1/2 branch (reads `k_f - c`),
|
||||||
|
halfway fallback, and that every fluid pull from a solid slot is BFL-written.
|
||||||
|
|
||||||
|
The Wen 2014 relative-velocity link sum adds `-u_w (f_t - f_r) = u_w dm` per
|
||||||
|
link (`dm = f_r - f_t`, the BFL mass defect). That term has no counterpart in
|
||||||
|
the fluid ledger above. Peng et al. (PRE 2016 submitted, §II.C, Eqs. 20–21)
|
||||||
|
show for a straight wall that the bounce-back error accumulated before a node
|
||||||
|
switch cancels with the switching momentum. Whether `sum u_w dm` approximates
|
||||||
|
`I_cover - I_uncover` here (implying double counting if both are used) is
|
||||||
|
**NOT demonstrated** for this implementation: it needs a bounded experiment
|
||||||
|
(prescribed slow translation; cumulative `sum u_w dm` vs cumulative node
|
||||||
|
impulse, per crossing cycle). Momentum conservation is also distinct from
|
||||||
|
Galilean invariance, which G4 tests.
|
||||||
|
|
||||||
|
Accumulators (separate, per body, per step window; all reported raw):
|
||||||
|
`link_cme`, `link_wen_correction` (= `sum u_w dm`), `node_cover`,
|
||||||
|
`node_uncover`, plus counters of crossings and refill fallbacks, so force
|
||||||
|
spikes can be correlated with crossing events.
|
||||||
|
|
||||||
|
**OPEN — structural RHS candidates (not selected):**
|
||||||
|
(a) `link_cme + node_cover - node_uncover`; (b) `link_cme + link_wen_correction`
|
||||||
|
(no node impulse). Selection follows the ledger review and the bounded
|
||||||
|
experiment above.
|
||||||
|
|
||||||
|
## 7. Rejection semantics
|
||||||
|
|
||||||
|
- Before any mutation (`ValueError`, no device write, host state unchanged):
|
||||||
|
moving body with EsoPull, `ddf_shifting`, refinement/blocked stepper, D3Q19,
|
||||||
|
Jets or surface actuation on that body, `sync_bodies`/checkpoint while a
|
||||||
|
moving body exists; swept band touching domain boundaries, other bodies,
|
||||||
|
sensors or force regions; `set_body` offset outside the declared box,
|
||||||
|
per-step move ≥ 1 cell, speed above the declared bound.
|
||||||
|
- Host state is committed only after a successful upload (same contract as Jet
|
||||||
|
amplitude updates); an uncertain device-write failure makes the simulation
|
||||||
|
unusable.
|
||||||
|
- Device-side soft diagnostics (refill fallbacks) are counters, not errors.
|
||||||
|
|
||||||
|
## 8. Coupling and units (CANDIDATE)
|
||||||
|
|
||||||
|
Lattice units, rho0 = 1, per unit span. Step n uses `c^n`, `u^n` and yields
|
||||||
|
`F^n`. Weak staggered update
|
||||||
|
`v^{n+1} = v^n + (F^n - c v^n - k y^n)/m`, `y^{n+1} = y^n + v^{n+1}`
|
||||||
|
(Newmark is an alternative; OPEN). Mass, natural frequency, damping and `U_r`
|
||||||
|
follow the chosen benchmark's own definitions (Leontini et al. JFS 2006 under
|
||||||
|
verification; vacuum vs in-fluid natural frequency must not be mapped
|
||||||
|
silently). Stability/convergence is gated by a halved-time-step run, not
|
||||||
|
assumed from the mass ratio.
|
||||||
|
|
||||||
|
## 9. Gates (map; tolerances OPEN, declared before runs)
|
||||||
|
|
||||||
|
G0 zero-motion regression (stationary path unchanged; moving path at rest
|
||||||
|
reproduces stationary link set and DDF) · G1 raw force sign (`Cd > 0`) ·
|
||||||
|
G2/G3 prescribed translation incl. ledger accumulators · G4 matched
|
||||||
|
relative-flow (moving body in quiescent fluid vs fixed body in uniform
|
||||||
|
stream), optional boost, co-moving sanity · G5 Dütsch et al. 1998 Re 100,
|
||||||
|
KC 5 in-line (needs 2-component offset) · G6 Leontini free transverse VIV
|
||||||
|
(m* = 10, ζ = 0.01, Re = 200, definitions pending) · G7 time-level contract
|
||||||
|
frozen before the oscillator.
|
||||||
|
|
||||||
|
## 10. Remaining decisions
|
||||||
|
|
||||||
|
Structural RHS (section 6); motion-record layout; refill details; coupling
|
||||||
|
scheme; benchmark definitions and digitized reference data; tolerances;
|
||||||
|
execution/resource feasibility (Astra, pending); whether a quiescent domain
|
||||||
|
(zero inlet velocity) runs cleanly in the current config.
|
||||||
Reference in new issue
Block a user