# 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 (because `store_f_esopull` at step t-1 wrote the values) - Reading odd-direction values from the neighbor cell (where `store_f_esopull` scattered them) - Reading even-direction values from the local cell - Falling back to `load_physical_dir_esopull` at 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 1. **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. 2. **Section 3.5 (inlet secondary issue)**: Incorrect. The EsoPull inlet/outlet functions already used `load_f_esopull()` (not raw `load_ddf`) for neighbor DDF reads since they were added. This was never a contributing factor to the CD error. 3. **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 |