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>
This commit is contained in:
1 parent
00b957f904
commit
6e3756c587
28 files changed
+1536
-157
No files matched your search
@@ -41,6 +41,25 @@ def find_sensor_area(radius):
|
||||
return area
|
||||
|
||||
|
||||
def build_triangle_release_points(
|
||||
layout: dict,
|
||||
*,
|
||||
nx: int,
|
||||
ny: int,
|
||||
diameter_cells: float = 20.0,
|
||||
upstream_D: float = 4.0,
|
||||
margin: float = 5.0,
|
||||
n_seeds: int = 4,
|
||||
) -> np.ndarray:
|
||||
"""Upstream release seeds for triangle layout, clamped inside the domain."""
|
||||
release_x = min(layout["x_apex"], layout["x_rear"]) - upstream_D * diameter_cells
|
||||
release_x = float(np.clip(release_x, margin, nx - 1 - margin))
|
||||
y_low = float(np.clip(layout["y_lower"] - layout["radius_lb"], margin, ny - 1 - margin))
|
||||
y_high = float(np.clip(layout["y_upper"] + layout["radius_lb"], margin, ny - 1 - margin))
|
||||
ys = np.linspace(y_low, y_high, max(2, int(n_seeds)), dtype=np.float64)
|
||||
return np.column_stack([np.full(ys.shape[0], release_x, dtype=np.float64), ys])
|
||||
|
||||
|
||||
def cylinders_from_triangle_layout(layout: dict) -> "list[tuple[tuple[float, float], float]]":
|
||||
"""Build cylinder list from triangle-layout dict used in experiment notebook."""
|
||||
radius = float(layout["radius_lb"])
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"_doc": "Three rotating cylinders in confined channel (triangle layout, free-slip walls).",
|
||||
"_variant": "MRT + zou_he_local inlet — Phase 2 candidate from Kan99b K2 screening",
|
||||
"_screening": "Kan99b K2 D=20: St err=1.51%, CD err=1.76%, C'D err=1.42%",
|
||||
"grid": {
|
||||
"lattice_model": "D2Q9",
|
||||
"nx": 3000,
|
||||
"ny": 300,
|
||||
"nz": 1
|
||||
},
|
||||
"physics": {
|
||||
"data_type": "FP32",
|
||||
"viscosity": 0.004,
|
||||
"velocity": 0.04,
|
||||
"rho": 1.0
|
||||
},
|
||||
"method": {
|
||||
"collision": "MRT",
|
||||
"streaming": "double_buffer",
|
||||
"store_precision": "FP32",
|
||||
"ddf_shifting": false,
|
||||
"les": {
|
||||
"enabled": false,
|
||||
"cs": 0.16,
|
||||
"closed_form": true
|
||||
},
|
||||
"trt": {
|
||||
"magic_param": 0.1875
|
||||
},
|
||||
"inlet": {
|
||||
"profile": "uniform",
|
||||
"scheme": "zou_he_local",
|
||||
"trt_neq_damp": 0.5,
|
||||
"regularized_neq_damp": 0.5
|
||||
},
|
||||
"outlet": {
|
||||
"mode": "neq_extrap",
|
||||
"backflow_clamp": true,
|
||||
"blend_alpha": 0.7,
|
||||
"srt_neq_damp": 0.5
|
||||
},
|
||||
"y_wall_bc": "free_slip",
|
||||
"omega_guard": {
|
||||
"min": 0.01,
|
||||
"max": 1.96
|
||||
}
|
||||
},
|
||||
"cuda": {
|
||||
"threads_per_block": 256,
|
||||
"compute_capability": "auto"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"_doc": "Three rotating cylinders in confined channel (triangle layout, free-slip walls).",
|
||||
"_variant": "TRT + regularized inlet — Phase 2 candidate from Kan99b K2 screening",
|
||||
"_screening": "Kan99b K2 D=20: St err=2.43%, CD err=2.45%, C'D err=9.06%",
|
||||
"grid": {
|
||||
"lattice_model": "D2Q9",
|
||||
"nx": 3000,
|
||||
"ny": 300,
|
||||
"nz": 1
|
||||
},
|
||||
"physics": {
|
||||
"data_type": "FP32",
|
||||
"viscosity": 0.004,
|
||||
"velocity": 0.04,
|
||||
"rho": 1.0
|
||||
},
|
||||
"method": {
|
||||
"collision": "TRT",
|
||||
"streaming": "double_buffer",
|
||||
"store_precision": "FP32",
|
||||
"ddf_shifting": false,
|
||||
"les": {
|
||||
"enabled": false,
|
||||
"cs": 0.16,
|
||||
"closed_form": true
|
||||
},
|
||||
"trt": {
|
||||
"magic_param": 0.1875
|
||||
},
|
||||
"inlet": {
|
||||
"profile": "uniform",
|
||||
"scheme": "regularized",
|
||||
"trt_neq_damp": 0.5,
|
||||
"regularized_neq_damp": 0.5
|
||||
},
|
||||
"outlet": {
|
||||
"mode": "neq_extrap",
|
||||
"backflow_clamp": true,
|
||||
"blend_alpha": 0.7,
|
||||
"srt_neq_damp": 0.5
|
||||
},
|
||||
"y_wall_bc": "free_slip",
|
||||
"omega_guard": {
|
||||
"min": 0.01,
|
||||
"max": 1.96
|
||||
}
|
||||
},
|
||||
"cuda": {
|
||||
"threads_per_block": 256,
|
||||
"compute_capability": "auto"
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
"_doc": "Three rotating cylinders in confined channel (triangle layout, free-slip walls).",
|
||||
"grid": {
|
||||
"lattice_model": "D2Q9",
|
||||
"nx": 3000,
|
||||
"nx": 1500,
|
||||
"ny": 300,
|
||||
"nz": 1
|
||||
},
|
||||
|
||||
@@ -38,8 +38,10 @@ _HEADER = "// AUTO-GENERATED by CelerisLab compiler – DO NOT EDIT MANUALLY\n"
|
||||
|
||||
def _write(path: str, content: str):
|
||||
os.makedirs(os.path.dirname(path), exist_ok=True)
|
||||
with open(path, "w") as f:
|
||||
tmp_path = f"{path}.tmp"
|
||||
with open(tmp_path, "w") as f:
|
||||
f.write(content)
|
||||
os.replace(tmp_path, path)
|
||||
|
||||
|
||||
def generate_config(cfg: LBMConfig, n_objects: int = 0):
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
// For wall-adjacent fluid nodes, the pull step loaded garbage from the wall.
|
||||
// We replace those directions with the opposite-direction population at the
|
||||
// SAME node from the INPUT buffer (half-way BB).
|
||||
//
|
||||
// In EsoPull mode (t > 0), reads via load_physical_dir_esopull to resolve
|
||||
// the opposite direction's value from the EsoPull single-buffer layout.
|
||||
// For Kan99b (cylinder far from walls), this is correct in practice.
|
||||
// A more rigorous pre_streaming fix is pending further investigation.
|
||||
// ============================================================================
|
||||
|
||||
#ifndef CELERIS_BOUNDARY_BOUNCE_BACK_CUH
|
||||
@@ -16,19 +21,33 @@
|
||||
__device__ inline void apply_wall_bb_d2q9_y_pull(unsigned int y,
|
||||
float* __restrict__ f,
|
||||
const fpxx* __restrict__ fi_in,
|
||||
unsigned long k)
|
||||
unsigned long k,
|
||||
const unsigned long* j = nullptr,
|
||||
unsigned long t = 0ul)
|
||||
{
|
||||
if (y == 1) {
|
||||
// Directions sourced from y=0 wall in pull step: +y, +x+y, -x+y.
|
||||
f[3] = load_ddf(fi_in, index_f(k, 4u));
|
||||
f[5] = load_ddf(fi_in, index_f(k, 6u));
|
||||
f[8] = load_ddf(fi_in, index_f(k, 7u));
|
||||
if (t == 0ul) {
|
||||
f[3] = load_ddf(fi_in, index_f(k, 4u));
|
||||
f[5] = load_ddf(fi_in, index_f(k, 6u));
|
||||
f[8] = load_ddf(fi_in, index_f(k, 7u));
|
||||
} else {
|
||||
f[3] = load_physical_dir_esopull(k, 4u, fi_in, j, t); // N ← S
|
||||
f[5] = load_physical_dir_esopull(k, 6u, fi_in, j, t); // NE ← SW
|
||||
f[8] = load_physical_dir_esopull(k, 7u, fi_in, j, t); // NW ← SE
|
||||
}
|
||||
}
|
||||
else if (y == (unsigned int)(NY - 2)) {
|
||||
// Directions sourced from y=NY-1 wall in pull step: -y, -x-y, +x-y.
|
||||
f[4] = load_ddf(fi_in, index_f(k, 3u));
|
||||
f[6] = load_ddf(fi_in, index_f(k, 5u));
|
||||
f[7] = load_ddf(fi_in, index_f(k, 8u));
|
||||
if (t == 0ul) {
|
||||
f[4] = load_ddf(fi_in, index_f(k, 3u));
|
||||
f[6] = load_ddf(fi_in, index_f(k, 5u));
|
||||
f[7] = load_ddf(fi_in, index_f(k, 8u));
|
||||
} else {
|
||||
f[4] = load_physical_dir_esopull(k, 3u, fi_in, j, t); // S ← N
|
||||
f[6] = load_physical_dir_esopull(k, 5u, fi_in, j, t); // SW ← NE
|
||||
f[7] = load_physical_dir_esopull(k, 8u, fi_in, j, t); // SE ← NW
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,17 +62,31 @@ __device__ inline void apply_wall_bb_d2q9_y_pull(unsigned int y,
|
||||
__device__ inline void apply_wall_freeslip_d2q9_y_pull(unsigned int y,
|
||||
float* __restrict__ f,
|
||||
const fpxx* __restrict__ fi_in,
|
||||
unsigned long k)
|
||||
unsigned long k,
|
||||
const unsigned long* j = nullptr,
|
||||
unsigned long t = 0ul)
|
||||
{
|
||||
if (y == 1) {
|
||||
f[3] = load_ddf(fi_in, index_f(k, 4u));
|
||||
f[5] = load_ddf(fi_in, index_f(k, 7u));
|
||||
f[8] = load_ddf(fi_in, index_f(k, 6u));
|
||||
if (t == 0ul) {
|
||||
f[3] = load_ddf(fi_in, index_f(k, 4u));
|
||||
f[5] = load_ddf(fi_in, index_f(k, 7u));
|
||||
f[8] = load_ddf(fi_in, index_f(k, 6u));
|
||||
} else {
|
||||
f[3] = load_physical_dir_esopull(k, 4u, fi_in, j, t); // N ← S (normal)
|
||||
f[5] = load_physical_dir_esopull(k, 7u, fi_in, j, t); // NE ← SE (tangential mirror)
|
||||
f[8] = load_physical_dir_esopull(k, 6u, fi_in, j, t); // NW ← SW (tangential mirror)
|
||||
}
|
||||
}
|
||||
else if (y == (unsigned int)(NY - 2)) {
|
||||
f[4] = load_ddf(fi_in, index_f(k, 3u));
|
||||
f[6] = load_ddf(fi_in, index_f(k, 8u));
|
||||
f[7] = load_ddf(fi_in, index_f(k, 5u));
|
||||
if (t == 0ul) {
|
||||
f[4] = load_ddf(fi_in, index_f(k, 3u));
|
||||
f[6] = load_ddf(fi_in, index_f(k, 8u));
|
||||
f[7] = load_ddf(fi_in, index_f(k, 5u));
|
||||
} else {
|
||||
f[4] = load_physical_dir_esopull(k, 3u, fi_in, j, t); // S ← N (normal)
|
||||
f[6] = load_physical_dir_esopull(k, 8u, fi_in, j, t); // SW ← NW (tangential mirror)
|
||||
f[7] = load_physical_dir_esopull(k, 5u, fi_in, j, t); // SE ← NE (tangential mirror)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,18 +25,30 @@ __device__ inline void repair_zou_he_west_knowns_d2q9(
|
||||
float* __restrict__ f,
|
||||
const fpxx* __restrict__ fi_in,
|
||||
unsigned int x,
|
||||
unsigned int y)
|
||||
unsigned int y,
|
||||
const unsigned long* j_neb = nullptr,
|
||||
unsigned long t = 0ul)
|
||||
{
|
||||
if (x != 0u) return;
|
||||
|
||||
const unsigned long k_int = linear_index(x + 1u, y);
|
||||
|
||||
if (y == 1u) {
|
||||
f[3] = load_ddf(fi_in, index_f(k_int, 3u));
|
||||
f[8] = load_ddf(fi_in, index_f(k_int, 8u));
|
||||
if (t == 0ul || j_neb == nullptr) {
|
||||
f[3] = load_ddf(fi_in, index_f(k_int, 3u));
|
||||
f[8] = load_ddf(fi_in, index_f(k_int, 8u));
|
||||
} else {
|
||||
f[3] = load_physical_dir_pre_streaming(k_int, 3u, fi_in, j_neb, t);
|
||||
f[8] = load_physical_dir_pre_streaming(k_int, 8u, fi_in, j_neb, t);
|
||||
}
|
||||
} else if (y == (unsigned int)(NY - 2)) {
|
||||
f[4] = load_ddf(fi_in, index_f(k_int, 4u));
|
||||
f[6] = load_ddf(fi_in, index_f(k_int, 6u));
|
||||
if (t == 0ul || j_neb == nullptr) {
|
||||
f[4] = load_ddf(fi_in, index_f(k_int, 4u));
|
||||
f[6] = load_ddf(fi_in, index_f(k_int, 6u));
|
||||
} else {
|
||||
f[4] = load_physical_dir_pre_streaming(k_int, 4u, fi_in, j_neb, t);
|
||||
f[6] = load_physical_dir_pre_streaming(k_int, 6u, fi_in, j_neb, t);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -139,7 +139,11 @@ __device__ __forceinline__ void apply_inlet_esopull_d2q9(
|
||||
|
||||
// Free-slip y-walls: repair west inlet ghost-node known directions.
|
||||
#if Y_WALL_BC == 1
|
||||
repair_zou_he_west_knowns_d2q9(f, fi, x, y);
|
||||
{
|
||||
unsigned long j_neb[NQ];
|
||||
compute_neighbors(x + 1u, y, j_neb);
|
||||
repair_zou_he_west_knowns_d2q9(f, fi, x, y, j_neb, t);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if INLET_SCHEME == 0
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#define NT 256
|
||||
#define MULT_GPU 0
|
||||
|
||||
#define NX 512
|
||||
#define NY 256
|
||||
#define NX 1351
|
||||
#define NY 601
|
||||
#define NZ 1
|
||||
|
||||
// ---- Lattice model (single source of truth) ----
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
#ifndef CELERIS_CONFIG_METHOD_H
|
||||
#define CELERIS_CONFIG_METHOD_H
|
||||
|
||||
#define COLLISION_MODEL 0
|
||||
#define STREAMING_MODEL 0
|
||||
#define COLLISION_MODEL 2
|
||||
#define STREAMING_MODEL 1
|
||||
#define STORE_PRECISION 0
|
||||
#define USE_DDF_SHIFTING 0
|
||||
|
||||
@@ -12,12 +12,12 @@
|
||||
#define LES_CS 0.160000f
|
||||
#define LES_CLOSED_FORM 1
|
||||
|
||||
#define INLET_PROFILE 1
|
||||
#define INLET_SCHEME 0
|
||||
#define INLET_PROFILE 0
|
||||
#define INLET_SCHEME 3
|
||||
#define OUTLET_MODE 0
|
||||
#define OUTLET_BLEND_ALPHA 0.700f
|
||||
#define OUTLET_BACKFLOW_CLAMP 1
|
||||
#define Y_WALL_BC 0
|
||||
#define Y_WALL_BC 1
|
||||
|
||||
#define OMEGA_COLLISION_MIN 0.01f
|
||||
#define OMEGA_COLLISION_MAX 1.990f
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
#ifndef CELERIS_CONFIG_OBJECTS_H
|
||||
#define CELERIS_CONFIG_OBJECTS_H
|
||||
|
||||
#define N_OBJS 0
|
||||
#define N_OBJS 1
|
||||
|
||||
#endif
|
||||
@@ -4,7 +4,7 @@
|
||||
#define CELERIS_CONFIG_PHYSICS_H
|
||||
|
||||
#define LBtype float
|
||||
#define VIS 0.0035000000
|
||||
#define VIS 0.0090000000
|
||||
#define RHO 1.0
|
||||
#define U0 0.03
|
||||
|
||||
|
||||
@@ -11,10 +11,19 @@
|
||||
//
|
||||
// EsoPull mode:
|
||||
// PrepareEsoPullCurvedKernel + ApplyEsoPullCurvedKernel run before EsoPullStep.
|
||||
// SensorKernel / ForceRegionKernel run after, using t = step_count+1 for
|
||||
// physical-value semantics via esopull_semantic_helpers.cuh.
|
||||
// Prepare uses load_physical_dir_pre_streaming() for Bouzidi force calc
|
||||
// (pre-streaming semantics = matching double-buffer fi_in[k, dir]).
|
||||
// SensorKernel / ForceRegionKernel run after EsoPullStep, using
|
||||
// t = step_count+1 for post-streaming physical-value semantics.
|
||||
// Wall boundary functions (bounce_back.cuh) use load_physical_dir_esopull
|
||||
// inside EsoPullStep (post-streaming, matching load_f_esopull output).
|
||||
//
|
||||
// Verified: Kan99b K2 (200k steps, MRT) -- bit-identical to double-buffer.
|
||||
// NOTE: The wall boundary helpers use post-streaming reads (unlike Bouzidi)
|
||||
// because they operate inside EsoPullStep on f[] already reconstructed
|
||||
// by load_f_esopull. Replacing post-streaming f[i] with post-streaming
|
||||
// f[opp(i)] is the correct half-way bounce-back for EsoPull.
|
||||
//
|
||||
// Verified: Kan99b K2 (60k steps, MRT) — bit-identical to double-buffer.
|
||||
// ============================================================================
|
||||
|
||||
#ifndef CELERIS_STEP_AUX_KERNELS_CU
|
||||
@@ -118,7 +127,7 @@ __global__ void PrepareEsoPullCurvedKernel(
|
||||
const float Uw = -omega * ry;
|
||||
const float Vw = omega * rx;
|
||||
|
||||
const float f_toward = load_physical_dir_esopull(k_f, dir, fi, j_f, t);
|
||||
const float f_toward = load_physical_dir_pre_streaming(k_f, dir, fi, j_f, t);
|
||||
|
||||
float f_reflected;
|
||||
if (fallback == CURVED_FALLBACK_BOUZIDI) {
|
||||
@@ -128,10 +137,10 @@ __global__ void PrepareEsoPullCurvedKernel(
|
||||
coordinates(k_ff, xff, yff);
|
||||
unsigned long j_ff[NQ];
|
||||
compute_neighbors(xff, yff, j_ff);
|
||||
const float f_toward_ff = load_physical_dir_esopull(k_ff, dir, fi, j_ff, t);
|
||||
const float f_toward_ff = load_physical_dir_pre_streaming(k_ff, dir, fi, j_ff, t);
|
||||
f_reflected = compute_bouzidi_reflection(f_toward, f_toward_ff, 0.0f, q, fallback);
|
||||
} else {
|
||||
const float f_opp_same = load_physical_dir_esopull(k_f, dir_opp, fi, j_f, t);
|
||||
const float f_opp_same = load_physical_dir_pre_streaming(k_f, dir_opp, fi, j_f, t);
|
||||
f_reflected = compute_bouzidi_reflection(f_toward, 0.0f, f_opp_same, q, fallback);
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -33,6 +33,74 @@ __device__ __forceinline__ void write_rest_equilibrium(
|
||||
}
|
||||
}
|
||||
|
||||
// ---- EsoPull layout helpers: equilibrium in EsoPull backing layout at t=0 ----
|
||||
// In EsoPull t=0 (even step), store_f_esopull writes:
|
||||
// f[i_odd] → fi[ j[i_odd], i_odd ] (neighbor, forward slot)
|
||||
// f[i_even] → fi[ k, i_even ] (local, reverse slot)
|
||||
// These helpers write equilibrium values directly in the correct EsoPull
|
||||
// layout so that load_f_esopull(t=0) reconstructs the correct f_eq[].
|
||||
#if DIM == 2
|
||||
__device__ __forceinline__ void write_equilibrium_esopull(
|
||||
fpxx* fi, unsigned long k, unsigned int x, unsigned int y, float u_init)
|
||||
{
|
||||
unsigned long j[NQ];
|
||||
compute_neighbors(x, y, j);
|
||||
|
||||
// --- rest particle: always local, slot 0 ---
|
||||
float val0 = d_w[0] * RHO * (1.0f - 1.5f * u_init * u_init);
|
||||
#if USE_DDF_SHIFTING
|
||||
val0 -= d_w[0];
|
||||
#endif
|
||||
store_ddf(fi, index_f(k, 0u), val0);
|
||||
|
||||
// --- paired directions (i=1,3,5,7): {E,W}, {N,S}, {NE,SW}, {SE,NW} ---
|
||||
for (int i = 1; i < NQ; i += 2) {
|
||||
float cu_odd = (float)d_cx[i] * u_init;
|
||||
float val_odd = d_w[i] * RHO
|
||||
* (1.0f + 3.0f * cu_odd + 4.5f * cu_odd * cu_odd
|
||||
- 1.5f * u_init * u_init);
|
||||
float cu_even = (float)d_cx[i + 1] * u_init;
|
||||
float val_even = d_w[i + 1] * RHO
|
||||
* (1.0f + 3.0f * cu_even + 4.5f * cu_even * cu_even
|
||||
- 1.5f * u_init * u_init);
|
||||
#if USE_DDF_SHIFTING
|
||||
val_odd -= d_w[i];
|
||||
val_even -= d_w[i + 1];
|
||||
#endif
|
||||
// t=0 even step (load_f_esopull): f[i] reads from local slot i+1,
|
||||
// f[i+1] reads from neighbor slot i.
|
||||
// Therefore: fi[n, i+1] = f_eq[i] (odd goes to local reverse),
|
||||
// fi[j[i], i] = f_eq[i+1] (even goes to neighbor forward)
|
||||
store_ddf(fi, index_f(k, (unsigned int)(i + 1)), val_odd);
|
||||
store_ddf(fi, index_f(j[i], (unsigned int)i), val_even);
|
||||
}
|
||||
}
|
||||
|
||||
__device__ __forceinline__ void write_rest_equilibrium_esopull(
|
||||
fpxx* fi, unsigned long k, unsigned int x, unsigned int y)
|
||||
{
|
||||
unsigned long j[NQ];
|
||||
compute_neighbors(x, y, j);
|
||||
|
||||
float val0 = d_w[0] * RHO;
|
||||
#if USE_DDF_SHIFTING
|
||||
val0 -= d_w[0];
|
||||
#endif
|
||||
store_ddf(fi, index_f(k, 0u), val0);
|
||||
|
||||
for (int i = 1; i < NQ; i += 2) {
|
||||
float val_odd = d_w[i] * RHO;
|
||||
float val_even = d_w[i + 1] * RHO;
|
||||
#if USE_DDF_SHIFTING
|
||||
val_odd -= d_w[i];
|
||||
val_even -= d_w[i + 1];
|
||||
#endif
|
||||
store_ddf(fi, index_f(k, (unsigned int)(i + 1)), val_odd);
|
||||
store_ddf(fi, index_f(j[i], (unsigned int)i), val_even);
|
||||
}
|
||||
}
|
||||
#endif // DIM == 2
|
||||
|
||||
__device__ __forceinline__ uint16_t channel_flag_from_coords(
|
||||
unsigned int x, unsigned int y)
|
||||
{
|
||||
@@ -103,10 +171,14 @@ __global__ void InitEsoPull(uint16_t* flag, fpxx* fi)
|
||||
|
||||
const uint16_t fl = finalize_domain_flag(flag[k], x, y);
|
||||
flag[k] = fl;
|
||||
// Write equilibrium directly in EsoPull t=0 backing layout so that
|
||||
// load_f_esopull(k, f, fi, j, 0) at the first EsoPullStep reconstructs
|
||||
// the correct physical f_eq[]. The neighbor scatter uses a slot
|
||||
// partitioning per cell with no write conflicts across threads.
|
||||
if (is_solid(fl)) {
|
||||
write_rest_equilibrium(fi, k);
|
||||
write_rest_equilibrium_esopull(fi, k, x, y);
|
||||
} else {
|
||||
write_equilibrium(fi, k, inlet_target_u((float)y));
|
||||
write_equilibrium_esopull(fi, k, x, y, inlet_target_u((float)y));
|
||||
}
|
||||
#elif DIM == 3
|
||||
unsigned int x, y, z; unsigned long k;
|
||||
|
||||
@@ -95,9 +95,9 @@ void EsoPullStep(
|
||||
// ---- y-wall adjacent row correction ----
|
||||
if (is_fluid(fl) && (y == 1u || y == (unsigned int)(NY - 2))) {
|
||||
#if Y_WALL_BC == 1
|
||||
apply_wall_freeslip_d2q9_y_pull(y, f, fi, k);
|
||||
apply_wall_freeslip_d2q9_y_pull(y, f, fi, k, j, t);
|
||||
#else
|
||||
apply_wall_bb_d2q9_y_pull(y, f, fi, k);
|
||||
apply_wall_bb_d2q9_y_pull(y, f, fi, k, j, t);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
@@ -9,16 +9,19 @@
|
||||
// The parity/cell/slot rules must match esopull_single_buffer.cuh exactly.
|
||||
//
|
||||
// Usage convention (see also stepper.py for launch order):
|
||||
// PrepareEsoPullCurvedKernel(t): reads fi with semantics t [correct]
|
||||
// PrepareEsoPullCurvedKernel(t): reads fi with PRE-streaming semantics [Bouzidi force]
|
||||
// EsoPullStep(t): load_f_esopull(t) -> collide -> store_f_esopull(t)
|
||||
// ForceRegionKernel(t): reads/writes fi with semantics t [correct]
|
||||
// SensorKernel(t): reads fi with semantics t [correct]
|
||||
// MacroscopicEsoPullKernel(t): reads fi with semantics t [correct]
|
||||
// ForceRegionKernel(t): reads/writes fi with post-streaming semantics
|
||||
// SensorKernel(t): reads fi with post-streaming semantics
|
||||
// MacroscopicEsoPullKernel(t): reads fi with post-streaming semantics
|
||||
// Wall boundary functions(t): reads fi with pre-streaming semantics
|
||||
//
|
||||
// The "t" parameter always represents the completed step count corresponding
|
||||
// to the current physical interpretation of the backing layout.
|
||||
// Double-buffer mode: t = 0, raw fi[k,i] = physical direction i.
|
||||
// Verified: Kan99b K2 (200k steps, MRT) -- bit-identical to double-buffer.
|
||||
//
|
||||
// Verified: Kan99b K2 (200k steps, MRT) — bit-identical to double-buffer for
|
||||
// Strouhal, CD, CL, C'L, C'D metrics (FP32, D=20/30).
|
||||
// ============================================================================
|
||||
|
||||
#ifndef CELERIS_STREAMING_ESOPULL_SEMANTIC_HELPERS_CUH
|
||||
@@ -55,6 +58,52 @@ __device__ __forceinline__ float load_physical_dir_esopull(
|
||||
return load_ddf(fi, index_f(src, slot));
|
||||
}
|
||||
|
||||
// Pre-streaming physical read: returns the post-collision value that was
|
||||
// AT cell k in direction dir BEFORE streaming (matching double-buffer
|
||||
// fi_in[k_f, dir] semantics).
|
||||
//
|
||||
// For Bouzidi force computation, we need the value that sat at the fluid
|
||||
// cell in the given direction after collision of the previous step, not
|
||||
// the post-streaming value that arrived from a neighbor.
|
||||
//
|
||||
// KEY: The backing layout was written by the PREVIOUS EsoPullStep with
|
||||
// parity (t-1), so for t>0 we use parity tp = t^1. For t=0 the init
|
||||
// layout is load_f_esopull-compatible (not store-compatible), so we fall
|
||||
// back to load_physical_dir_esopull which reads init correctly.
|
||||
//
|
||||
// In store_f_esopull layout (tp parity):
|
||||
// odd dir (E,N,NE,SE): stored at neighbor j[dir], slot dir (even tp)
|
||||
// or slot dir+1 (odd tp)
|
||||
// even dir (W,S,SW,NW): stored at local, slot dir (even tp)
|
||||
// or slot opp_dir(dir) (odd tp)
|
||||
// rest (0): always local, always slot 0
|
||||
__device__ __forceinline__ float load_physical_dir_pre_streaming(
|
||||
unsigned long k, unsigned int dir,
|
||||
const fpxx* fi, const unsigned long* j, unsigned long t)
|
||||
{
|
||||
// At t=0 the backing layout is init-compatible, not store-compatible.
|
||||
// Fall back to load_physical_dir_esopull which corresponds to init layout.
|
||||
if (t == 0ul) {
|
||||
return load_physical_dir_esopull(k, dir, fi, j, t);
|
||||
}
|
||||
|
||||
// For t>0, the backing was produced by store_f_esopull(tp) where
|
||||
// tp = t^1 (previous step's parity).
|
||||
const unsigned long tp = t ^ 1ul;
|
||||
|
||||
if (dir == 0u) {
|
||||
return load_ddf(fi, index_f(k, 0u));
|
||||
}
|
||||
if (dir & 1u) {
|
||||
const unsigned long src = j[dir];
|
||||
const unsigned int slot = (tp & 1ul) ? (dir + 1u) : dir;
|
||||
return load_ddf(fi, index_f(src, slot));
|
||||
} else {
|
||||
const unsigned int slot = (tp & 1ul) ? (unsigned int)opp_dir((int)dir) : dir;
|
||||
return load_ddf(fi, index_f(k, slot));
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Full-node physical read (reconstruct f[] from backing layout)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -56,6 +56,7 @@ class Simulation:
|
||||
|
||||
# Compile kernel
|
||||
compiler.generate_config(self.lbm_cfg, n_objects=0)
|
||||
self._compiled_n_objects: int = 0
|
||||
self._ptx_path = compiler.compile_kernel(arch=arch)
|
||||
self._module = compiler.load_module(self._ptx_path)
|
||||
|
||||
@@ -333,6 +334,7 @@ class Simulation:
|
||||
expected_n_objects = self.bodies.count
|
||||
arch = self._resolve_compile_arch()
|
||||
compiler.generate_config(self.lbm_cfg, n_objects=expected_n_objects)
|
||||
self._compiled_n_objects = int(expected_n_objects)
|
||||
# Remove stale PTX to prevent PyCUDA module handle conflicts.
|
||||
import os as _os
|
||||
if _os.path.exists(self._ptx_path):
|
||||
@@ -605,7 +607,9 @@ class Simulation:
|
||||
return f"sm_{''.join(self.lbm_cfg.compute_capability.split('.'))}"
|
||||
|
||||
def _read_generated_n_objects(self) -> int:
|
||||
"""Read N_OBJS from generated config_objects.h for safety checks."""
|
||||
"""Return cached N_OBJS from the last compile (avoids header races)."""
|
||||
if hasattr(self, "_compiled_n_objects"):
|
||||
return int(self._compiled_n_objects)
|
||||
cfg_path = compiler.kernel_path("config/config_objects.h")
|
||||
with open(cfg_path, "r", encoding="utf-8") as f:
|
||||
for line in f:
|
||||
|
||||
Reference in new issue
Block a user