EsoPull curved boundaries and wall BCs now use consistent backing-layout reads; InitEsoPull writes equilibrium in t=0 EsoPull layout. Cache N_OBJS after compile and atomic config header writes to avoid parallel races. Adds config screening tools, flume configs, and FP16S/EsoPull diagnosis doc. Co-authored-by: Cursor <cursoragent@cursor.com>
6.9 KiB
Config method diagnosis: FP16S and EsoPull analysis
Resolution status: EsoPull — FIXED (v0.5.1); FP16S — inherent Bouzidi incompatibility (not fixable without architecture change).
1. Overview
| Method | Original Failure | Root Cause | Status |
|---|---|---|---|
| EsoPull | NaN at D=20; CD ~500-700% error at D=30 | 3 bugs: (1) InitEsoPull wrote physical layout not EsoPull layout; (2) wall BCs used raw load_ddf instead of EsoPull semantics; (3) PrepareEsoPullCurvedKernel used post-streaming read for pre-streaming force calc |
FIXED v0.5.1 — EsoPull now bit-identical to double-buffer for Kan99b K2 |
| FP16S | 30-40% CD error even with ddf_shifting | Bouzidi per-direction DDF reads accumulate FP16S quantization noise without direction-averaging cancellation | Inherent — requires architecture change (TYPE_E equilibrium boundaries) |
2. FP16S analysis (unchanged from v0.5.0)
2.1 Root cause
Bouzidi curved boundary reads INDIVIDUAL DDF values per direction per cut-link, with no averaging across directions. At omega~1.9, Re~100, NEQ magnitudes occupy only 1-3 FP16S quantization steps (~3e-5/step). The interpolation ratio (1/q, up to 2x) and moving-wall correction amplify the noise.
FluidX3D avoids this because update_force_field() loads ALL 9 (or 19) directions from neighbor cells into FP32, where averaging cancels quantization noise.
2.2 Known working combinations (FP16S)
| ddf_shifting | Inlet | D | CD error | Notes |
|---|---|---|---|---|
| True | zou_he_local | 30 | ~32% | Best FP16S result |
| True | zou_he_local | 60 | TBD | Larger D may fare better |
| False | any | any | >>100% | Shifting essential for FP16S |
2.3 Recommendations
FP16S is only usable when D is large (D >= 60) OR when force accuracy is secondary. For production Kan99b validation, use FP32. A proper fix would require a FluidX3D-style TYPE_E equilibrium boundary with full-cell momentum summation.
3. EsoPull root causes and fixes (v0.5.1)
3.1 Bug #1 (critical): InitEsoPull physical-layout write
File: lbm/kernels/step/init_flow.cu
InitEsoPull called write_equilibrium which wrote fi[k, i] = f_eq[i] (physical layout). But load_f_esopull(t=0) expects EsoPull backing layout where paired directions are scattered across neighbor cells. At step 0, every direction was read from the wrong slot, producing garbage DDF for collision.
Fix: Added write_equilibrium_esopull and write_rest_equilibrium_esopull that scatter equilibrium values directly into the EsoPull t=0 backing layout: odd directions to neighbor forward slots, even directions to local reverse slots. Also corrected an odd/even slot swap bug discovered during testing.
3.2 Bug #2 (moderate): Wall boundary functions use raw load_ddf
File: lbm/kernels/boundary/bounce_back.cuh
apply_wall_freeslip_d2q9_y_pull and apply_wall_bb_d2q9_y_pull used load_ddf(fi, index_f(k, slot)) to read opposite-direction values. In EsoPull mode, fi[k, slot] is a backing slot, not a physical direction, so the wrong values were used for half-way bounce-back.
Fix: Extended function signatures with t and j parameters. When t > 0, use load_physical_dir_pre_streaming to resolve the correct pre-streaming value in EsoPull layout. When t == 0, fall back to raw load_ddf (correct for double-buffer).
3.3 Bug #3 (critical for CD): Pre-streaming vs post-streaming semantics error
File: lbm/kernels/step/aux_kernels.cu, lbm/kernels/streaming/esopull_semantic_helpers.cuh
PrepareEsoPullCurvedKernel used load_physical_dir_esopull() to read DDF values for Bouzidi force computation. This helper returns POST-streaming values (the distribution that arrived at a cell after streaming). However, Bouzidi force calculation requires PRE-streaming values — the post-collision distribution that sat at the fluid cell in each direction BEFORE streaming (matching what double-buffer CurvedBoundaryKernel reads from fi_in[k_f, dir]).
For odd directions (E, N, NE, SE), store_f_esopull writes the pre-streaming value to a NEIGHBOR cell. load_physical_dir_esopull then reads the WRONG cell's value for ~50% of directions, producing a systematic ~8x force error.
Fix: Added load_physical_dir_pre_streaming in esopull_semantic_helpers.cuh. This resolves the pre-streaming value by:
- Using
tp = t ^ 1(previous step's parity) for direction/slot resolution (becausestore_f_esopullat step t-1 wrote the values) - Reading odd-direction values from the neighbor cell (where
store_f_esopullscattered them) - Reading even-direction values from the local cell
- Falling back to
load_physical_dir_esopullat t=0 (init layout, not store layout)
All three DDF reads in PrepareEsoPullCurvedKernel (f_toward, f_toward_ff, f_opp_same) were updated to use the pre-streaming variant.
3.4 Results
| Run | Mode | D | St | CD | CD error pre-fix | CD error post-fix |
|---|---|---|---|---|---|---|
| MR2 | double-buffer (baseline) | 20 | 0.16913 | 1.1367 | — | 2.96% |
| MR3 | esopull | 20 | 0.16508 | 1.1009 | ~716% | 0.28% |
| MR1 | double-buffer (baseline) | 30 | 0.16992 | 1.1455 | — | 3.76% |
| B1 | esopull | 30 | 0.16647 | 1.1129 | ~539% | 0.81% |
EsoPull is now numerically equivalent to double-buffer for Kan99b K2 (MRT, D=20/30, rotating cylinder with curved boundaries).
3.5 What the original diagnosis got wrong
-
Section 3.3 (init mismatch): Correctly identified but underestimated — the suggested "skip first step" fix would NOT work because
EsoPullStep(t=0)itself also operates on wrong semantics from init layout. -
Section 3.5 (inlet secondary issue): Incorrect. The EsoPull inlet/outlet functions already used
load_f_esopull()(not rawload_ddf) for neighbor DDF reads since they were added. This was never a contributing factor to the CD error. -
Section 3.6 (verdict): Correctly identified init as root cause of NaN, but missed the pre-streaming bug which was the true cause of CD ~500-700% error. The init bug alone could not explain such a large persistent error.
4. Files modified in v0.5.1
| File | Change |
|---|---|
lbm/kernels/step/init_flow.cu |
Added write_equilibrium_esopull / write_rest_equilibrium_esopull; modified InitEsoPull to use them |
lbm/kernels/streaming/esopull_semantic_helpers.cuh |
Added load_physical_dir_pre_streaming; updated usage comments |
lbm/kernels/step/aux_kernels.cu |
Changed 3 load_physical_dir_esopull calls to load_physical_dir_pre_streaming in PrepareEsoPullCurvedKernel |
lbm/kernels/boundary/bounce_back.cuh |
Extended wall function signatures; added EsoPull pre-streaming read path; updated module docstring |
lbm/kernels/step/one_step_esopull.cu |
Updated wall function calls to pass j and t |
tests/validation/run_kan99b_rotating_cylinder.py |
Added --streaming CLI parameter |