CelerisLab/docs/audit/config_diagnosis.md
Frank14f 6e3756c587 fix(esopull): correct init layout and pre-streaming semantics (v0.5.1)
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>
2026-06-27 22:32:01 +08:00

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 (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