D3Q19 SRT
This commit is contained in:
parent
d3b32c8be3
commit
f3e2e557d4
3
.gitignore
vendored
3
.gitignore
vendored
@ -66,3 +66,6 @@ venv.bak/
|
|||||||
*.tmp
|
*.tmp
|
||||||
*.bak
|
*.bak
|
||||||
*.log
|
*.log
|
||||||
|
|
||||||
|
# reference:
|
||||||
|
ref/
|
||||||
BIN
output/cylinder_d2q9.png
Normal file
BIN
output/cylinder_d2q9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 513 KiB |
BIN
output/cylinder_d3q19_re100.png
Normal file
BIN
output/cylinder_d3q19_re100.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 633 KiB |
BIN
output/cylinder_d3q19_re20.png
Normal file
BIN
output/cylinder_d3q19_re20.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 581 KiB |
BIN
output/poiseuille_d2q9.png
Normal file
BIN
output/poiseuille_d2q9.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 191 KiB |
@ -51,6 +51,19 @@ def compile_kernel():
|
|||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def compile_kernel_v2():
|
||||||
|
"""Compile the new modular kernel (kernel_v2.cu → kernel_v2.ptx)."""
|
||||||
|
subprocess.run(
|
||||||
|
[
|
||||||
|
"nvcc",
|
||||||
|
"-ptx",
|
||||||
|
kernel_path("kernel_v2.cu"),
|
||||||
|
"-o",
|
||||||
|
kernel_path("kernel_v2.ptx"),
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
def config_kernal(config_cuda: CudaConfig, config_field: FlowFieldConfig):
|
def config_kernal(config_cuda: CudaConfig, config_field: FlowFieldConfig):
|
||||||
lines = read_lines(kernel_path("macros.h"))
|
lines = read_lines(kernel_path("macros.h"))
|
||||||
lines = modify_macro(lines, "MULT_GPU", config_cuda.multi_gpu)
|
lines = modify_macro(lines, "MULT_GPU", config_cuda.multi_gpu)
|
||||||
@ -77,6 +90,31 @@ def config_kernal(config_cuda: CudaConfig, config_field: FlowFieldConfig):
|
|||||||
|
|
||||||
write_lines(kernel_path("macros.h"), lines)
|
write_lines(kernel_path("macros.h"), lines)
|
||||||
|
|
||||||
|
|
||||||
|
def config_kernal_v2(config_cuda: CudaConfig, config_field: FlowFieldConfig,
|
||||||
|
collision_model: int = 2,
|
||||||
|
streaming_model: int = 0,
|
||||||
|
store_precision: int = 0,
|
||||||
|
use_ddf_shifting: int = 0):
|
||||||
|
"""Configure macros.h for the new modular kernel architecture.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
collision_model: 0=SRT, 1=TRT, 2=MRT (default)
|
||||||
|
streaming_model: 0=double-buffer (default), 1=Esoteric-Pull
|
||||||
|
store_precision: 0=FP32 (default), 1=FP16S, 2=FP16C
|
||||||
|
use_ddf_shifting: 0=off (default), 1=on
|
||||||
|
"""
|
||||||
|
# First apply legacy config
|
||||||
|
config_kernal(config_cuda, config_field)
|
||||||
|
|
||||||
|
# Then apply new architecture macros
|
||||||
|
lines = read_lines(kernel_path("macros.h"))
|
||||||
|
lines = modify_macro(lines, "COLLISION_MODEL", collision_model)
|
||||||
|
lines = modify_macro(lines, "STREAMING_MODEL", streaming_model)
|
||||||
|
lines = modify_macro(lines, "STORE_PRECISION", store_precision)
|
||||||
|
lines = modify_macro(lines, "USE_DDF_SHIFTING", use_ddf_shifting)
|
||||||
|
write_lines(kernel_path("macros.h"), lines)
|
||||||
|
|
||||||
def config_object(n_obj: int):
|
def config_object(n_obj: int):
|
||||||
lines = read_lines(kernel_path("macros.h"))
|
lines = read_lines(kernel_path("macros.h"))
|
||||||
lines = modify_macro(lines, "N_OBJS", n_obj)
|
lines = modify_macro(lines, "N_OBJS", n_obj)
|
||||||
|
|||||||
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
131
src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh
Normal file
131
src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
// CelerisLab – boundary/bounce_back.cuh
|
||||||
|
// Full-way bounce-back for grid-aligned solid walls.
|
||||||
|
//
|
||||||
|
// For nodes adjacent to a solid wall, the incoming population from the
|
||||||
|
// wall direction is reflected:
|
||||||
|
// f[i] = f[opp(i)] (+ wall velocity correction for moving walls)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_BOUNDARY_BOUNCE_BACK_CUH
|
||||||
|
#define CELERIS_BOUNDARY_BOUNCE_BACK_CUH
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Simple bounce-back: replace f[i] with f[opp(i)] for solid neighbors
|
||||||
|
// This version checks the flag of each neighbor.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void apply_bounce_back(float* __restrict__ f,
|
||||||
|
const unsigned long* __restrict__ j,
|
||||||
|
const uint8_t* __restrict__ flags_arr)
|
||||||
|
{
|
||||||
|
// For each direction pair (i, i+1):
|
||||||
|
// If the neighbor in direction i is SOLID → f[i] should bounce
|
||||||
|
// from the opposite direction.
|
||||||
|
//
|
||||||
|
// With paired ordering, opp(i) = i+1 and opp(i+1) = i.
|
||||||
|
// In pull streaming, f[i] was loaded from j[opp(i)] = j[i+1].
|
||||||
|
// If j[i+1] is solid, we need bounce-back.
|
||||||
|
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
// Direction i: check if source (j[i+1]) is solid
|
||||||
|
if (flags_arr[j[i + 1]] & LEGACY_SOLID) {
|
||||||
|
// Bounce: f[i] = f_post[opp(i)] at current node = pre-collision f[i+1]
|
||||||
|
// But in pull-streaming context, we already loaded from neighbor.
|
||||||
|
// For simple BB on a flat wall, just swap f[i] and f[i+1]:
|
||||||
|
float temp = f[i];
|
||||||
|
f[i] = f[i + 1];
|
||||||
|
f[i + 1] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Top/bottom wall bounce-back (specialized for channel flow)
|
||||||
|
// For nodes at y=1 (adjacent to y=0 wall) or y=NY-2 (adjacent to y=NY-1 wall)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 9
|
||||||
|
__device__ inline void apply_wall_bb_d2q9(unsigned int y,
|
||||||
|
float* __restrict__ f)
|
||||||
|
{
|
||||||
|
if (y == 1) {
|
||||||
|
float temp;
|
||||||
|
temp = f[3]; f[3] = f[4]; f[4] = temp; // ±y swap
|
||||||
|
temp = f[5]; f[5] = f[6]; f[6] = temp; // ±(x+y) swap
|
||||||
|
temp = f[7]; f[7] = f[8]; f[8] = temp; // ±(x-y) swap
|
||||||
|
}
|
||||||
|
else if (y == NY - 2) {
|
||||||
|
float temp;
|
||||||
|
temp = f[3]; f[3] = f[4]; f[4] = temp;
|
||||||
|
temp = f[5]; f[5] = f[6]; f[6] = temp;
|
||||||
|
temp = f[7]; f[7] = f[8]; f[8] = temp;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D3Q19 wall bounce-back on y=0/NY-1 (channel walls)
|
||||||
|
// Pairs with non-zero cy: (3,4)±y, (7,8)±(x+y), (11,12)±(y+z),
|
||||||
|
// (13,14)±(x-y), (17,18)±(y-z)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D3Q19 y-wall half-way bounce-back for pull double-buffer streaming.
|
||||||
|
//
|
||||||
|
// Instead of swapping f[i] <-> f[opp(i)] (which corrupts the toward-wall
|
||||||
|
// direction with garbage from the wall node), we read the opposite direction
|
||||||
|
// at the SAME node from the INPUT buffer. That value is the post-collision
|
||||||
|
// population from the previous time step — exactly the correct half-way BB.
|
||||||
|
//
|
||||||
|
// At y=1 (wall at y=0): directions that arrive FROM y=0 have cy_src = -1.
|
||||||
|
// The pull loaded them from the wall (garbage). Replace with fi_in[k, opp].
|
||||||
|
//
|
||||||
|
// At y=NY-2 (wall at y=NY-1): directions from y=NY-1, cy_src = +1.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 19
|
||||||
|
__device__ inline void apply_wall_bb_d3q19_y_pull(unsigned int y,
|
||||||
|
float* __restrict__ f,
|
||||||
|
const fpxx* __restrict__ fi_in,
|
||||||
|
unsigned long k)
|
||||||
|
{
|
||||||
|
if (y == 1) {
|
||||||
|
// Directions whose pull-source is at y=0 (wall):
|
||||||
|
// f[3] (+y) ← source j[4] = (x, 0, z)
|
||||||
|
// f[7] (+x+y) ← source j[8] = (x-1, 0, z)
|
||||||
|
// f[11] (+y+z) ← source j[12] = (x, 0, z-1)
|
||||||
|
// f[14] (-x+y) ← source j[13] = (x+1, 0, z)
|
||||||
|
// f[17] (+y-z) ← source j[18] = (x, 0, z+1)
|
||||||
|
f[3] = load_ddf(fi_in, index_f(k, 4u));
|
||||||
|
f[7] = load_ddf(fi_in, index_f(k, 8u));
|
||||||
|
f[11] = load_ddf(fi_in, index_f(k, 12u));
|
||||||
|
f[14] = load_ddf(fi_in, index_f(k, 13u));
|
||||||
|
f[17] = load_ddf(fi_in, index_f(k, 18u));
|
||||||
|
}
|
||||||
|
else if (y == (unsigned int)(NY - 2)) {
|
||||||
|
// Directions whose pull-source is at y=NY-1 (wall):
|
||||||
|
// f[4] (-y) ← source j[3] = (x, NY-1, z)
|
||||||
|
// f[8] (-x-y) ← source j[7] = (x+1, NY-1, z)
|
||||||
|
// f[12] (-y-z) ← source j[11] = (x, NY-1, z+1)
|
||||||
|
// f[13] (+x-y) ← source j[14] = (x-1, NY-1, z)
|
||||||
|
// f[18] (-y+z) ← source j[17] = (x, NY-1, z-1)
|
||||||
|
f[4] = load_ddf(fi_in, index_f(k, 3u));
|
||||||
|
f[8] = load_ddf(fi_in, index_f(k, 7u));
|
||||||
|
f[12] = load_ddf(fi_in, index_f(k, 11u));
|
||||||
|
f[13] = load_ddf(fi_in, index_f(k, 14u));
|
||||||
|
f[18] = load_ddf(fi_in, index_f(k, 17u));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keep the old swap version for backward compatibility
|
||||||
|
__device__ inline void apply_wall_bb_d3q19_y(unsigned int y,
|
||||||
|
float* __restrict__ f)
|
||||||
|
{
|
||||||
|
if (y == 1 || y == NY - 2) {
|
||||||
|
float temp;
|
||||||
|
temp = f[3]; f[3] = f[4]; f[4] = temp; // ±y
|
||||||
|
temp = f[7]; f[7] = f[8]; f[8] = temp; // ±(x+y)
|
||||||
|
temp = f[11]; f[11] = f[12]; f[12] = temp; // ±(y+z)
|
||||||
|
temp = f[13]; f[13] = f[14]; f[14] = temp; // ±(x-y)
|
||||||
|
temp = f[17]; f[17] = f[18]; f[18] = temp; // ±(y-z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#endif // CELERIS_BOUNDARY_BOUNCE_BACK_CUH
|
||||||
111
src/CelerisLab/lbm/kernels/boundary/curved_boundary.cuh
Normal file
111
src/CelerisLab/lbm/kernels/boundary/curved_boundary.cuh
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// CelerisLab – boundary/curved_boundary.cuh
|
||||||
|
// Interpolated curved-boundary bounce-back.
|
||||||
|
//
|
||||||
|
// For curved solid surfaces that don't align with the grid, the
|
||||||
|
// reflected population is interpolated using the fractional distance q
|
||||||
|
// between the fluid node and the wall intersection.
|
||||||
|
//
|
||||||
|
// Migrated from existing kernel.cu + ref/main.cu.
|
||||||
|
//
|
||||||
|
// Delta pool layout per curved-boundary node (11 floats for D2Q9):
|
||||||
|
// delta[offset + 0] : encoded object id (bitcast int → float)
|
||||||
|
// delta[offset + 1..NQ-1] : q values for each direction (0 if no wall hit)
|
||||||
|
// delta[offset + NQ] : normal_y (wall normal y-component / R)
|
||||||
|
// delta[offset + NQ+1]: normal_x (wall normal x-component / R)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH
|
||||||
|
#define CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH
|
||||||
|
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// apply_curved_boundary:
|
||||||
|
// Interpolated bounce-back with wall velocity correction.
|
||||||
|
// Operates on a SOLID+INTERFACE node's neighbors.
|
||||||
|
//
|
||||||
|
// Parameters:
|
||||||
|
// n – current node (solid + interface)
|
||||||
|
// x, y – coordinates of current node
|
||||||
|
// f_out – the output DDF buffer (double-buffer scheme) being modified
|
||||||
|
// delta – parameter pool
|
||||||
|
// id_off – offset into delta for this node (= indx[n])
|
||||||
|
// Uw, Vw – wall velocity at this node
|
||||||
|
// obs_fx, obs_fy – accumulated force observation (atomicAdd targets)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void apply_curved_boundary(
|
||||||
|
unsigned long n, unsigned int x, unsigned int y,
|
||||||
|
fpxx* __restrict__ f_out,
|
||||||
|
const float* __restrict__ delta,
|
||||||
|
int id_off,
|
||||||
|
float Uw, float Vw,
|
||||||
|
float* __restrict__ obs_fx,
|
||||||
|
float* __restrict__ obs_fy)
|
||||||
|
{
|
||||||
|
// New paired direction ordering:
|
||||||
|
// cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
|
||||||
|
// cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
|
||||||
|
|
||||||
|
for (int i = 1; i < NQ; i++) {
|
||||||
|
int x_neb = x + d_cx[i];
|
||||||
|
int y_neb = y + d_cy[i];
|
||||||
|
|
||||||
|
// Check bounds (skip if neighbor is outside domain)
|
||||||
|
if (x_neb < 0 || x_neb >= NX || y_neb < 0 || y_neb >= NY) continue;
|
||||||
|
|
||||||
|
unsigned long k_neb = linear_index((unsigned int)x_neb, (unsigned int)y_neb);
|
||||||
|
|
||||||
|
// Only process if neighbor is FLUID
|
||||||
|
// (We read the flag from global memory – this is a boundary kernel,
|
||||||
|
// called infrequently per node, so the extra read is acceptable.)
|
||||||
|
// The caller should ensure this node IS solid+interface.
|
||||||
|
|
||||||
|
float q = delta[id_off + i]; // fractional distance (0 if no wall hit)
|
||||||
|
if (q <= 0.0f) continue; // no wall intersection in this direction
|
||||||
|
|
||||||
|
int oi = opp_dir(i);
|
||||||
|
float ci_dot_uw = (float)d_cx[i] * Uw + (float)d_cy[i] * Vw;
|
||||||
|
float wall_term = 6.0f * d_w[i] * ci_dot_uw;
|
||||||
|
|
||||||
|
// Second neighbor for quadratic interpolation
|
||||||
|
int x_neb2 = x + 2 * d_cx[i];
|
||||||
|
int y_neb2 = y + 2 * d_cy[i];
|
||||||
|
// Clamp to domain (simple, could be improved)
|
||||||
|
x_neb2 = max(0, min(NX - 1, x_neb2));
|
||||||
|
y_neb2 = max(0, min(NY - 1, y_neb2));
|
||||||
|
unsigned long k_neb2 = linear_index((unsigned int)x_neb2, (unsigned int)y_neb2);
|
||||||
|
|
||||||
|
// Read current DDF values from output buffer
|
||||||
|
float f_self_opp = load_ddf(f_out, index_f(n, (unsigned int)oi));
|
||||||
|
float f_neb_opp = load_ddf(f_out, index_f(k_neb, (unsigned int)oi));
|
||||||
|
float f_neb2_fwd = load_ddf(f_out, index_f(k_neb2, (unsigned int)i));
|
||||||
|
|
||||||
|
// Interpolated bounce-back (Yu, Mei, Shyy, 2003)
|
||||||
|
float f_reflected = (q * f_self_opp + (1.0f - q) * f_neb_opp
|
||||||
|
+ q * f_neb2_fwd + wall_term) / (1.0f + q);
|
||||||
|
|
||||||
|
store_ddf(f_out, index_f(k_neb, (unsigned int)i), f_reflected);
|
||||||
|
|
||||||
|
// Force observation (momentum exchange)
|
||||||
|
float f_neb_fwd = load_ddf(f_out, index_f(k_neb, (unsigned int)i));
|
||||||
|
float f_self_fwd = load_ddf(f_out, index_f(n, (unsigned int)i));
|
||||||
|
float f_sum = f_neb_fwd + f_self_opp;
|
||||||
|
|
||||||
|
int x_back = x - d_cx[i];
|
||||||
|
int y_back = y - d_cy[i];
|
||||||
|
x_back = max(0, min(NX - 1, x_back));
|
||||||
|
y_back = max(0, min(NY - 1, y_back));
|
||||||
|
|
||||||
|
// Accumulate force
|
||||||
|
if (obs_fx != nullptr) {
|
||||||
|
atomicAdd(obs_fx, -f_sum * (float)d_cx[i] + f_self_fwd);
|
||||||
|
atomicAdd(obs_fy, -f_sum * (float)d_cy[i] +
|
||||||
|
load_ddf(f_out, index_f(linear_index((unsigned int)x_back, (unsigned int)y_back),
|
||||||
|
(unsigned int)i)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ == 9
|
||||||
|
|
||||||
|
#endif // CELERIS_BOUNDARY_CURVED_BOUNDARY_CUH
|
||||||
174
src/CelerisLab/lbm/kernels/boundary/ibm_kernels.cuh
Normal file
174
src/CelerisLab/lbm/kernels/boundary/ibm_kernels.cuh
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
// CelerisLab – boundary/ibm_kernels.cuh
|
||||||
|
// Immersed Boundary Method (IBM) kernels: Euler↔Lagrangian coupling.
|
||||||
|
// Migrated from ref/main.cu: Eul2Lag, Lag2Eul, Update_Lagrangian.
|
||||||
|
//
|
||||||
|
// Uses Peskin's 4-point delta function for interpolation/spreading.
|
||||||
|
//
|
||||||
|
// Data layout (per rigid body):
|
||||||
|
// RigidBodyState2D: {x, y, theta, vx, vy, omega}
|
||||||
|
// Lagrangian points: motionL[NT_LAG * 4]
|
||||||
|
// motionL[4*k + 0] = target vx at point k
|
||||||
|
// motionL[4*k + 1] = target vy at point k
|
||||||
|
// motionL[4*k + 2] = XL (x-position)
|
||||||
|
// motionL[4*k + 3] = YL (y-position)
|
||||||
|
// forceL[NT_LAG * 2] = Lagrangian force (fx, fy) per point
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_BOUNDARY_IBM_KERNELS_CUH
|
||||||
|
#define CELERIS_BOUNDARY_IBM_KERNELS_CUH
|
||||||
|
|
||||||
|
#include "../core/params.cuh" // RigidBodyState2D, RigidBodyControl2D
|
||||||
|
|
||||||
|
// IBM is currently 2D only. Guard all kernels.
|
||||||
|
#if DIM == 2
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Peskin 4-point delta function (1D component)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ float peskin_delta_1d(float r) {
|
||||||
|
float ar = fabsf(r);
|
||||||
|
if (ar < 1.0f)
|
||||||
|
return 0.125f * (3.0f - 2.0f * ar + sqrtf(1.0f + 4.0f * ar - 4.0f * ar * ar));
|
||||||
|
else if (ar < 2.0f)
|
||||||
|
return 0.125f * (5.0f - 2.0f * ar - sqrtf(-7.0f + 12.0f * ar - 4.0f * ar * ar));
|
||||||
|
else
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Peskin 4-point delta function (2D = product of 1D components)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ float peskin_delta_2d(float dx, float dy) {
|
||||||
|
return peskin_delta_1d(dx) * peskin_delta_1d(dy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// update_lagrangian_control:
|
||||||
|
// Update Lagrangian point positions using RigidBodyState2D.
|
||||||
|
// Supports circular body (points on circumference).
|
||||||
|
//
|
||||||
|
// Thread k ∈ [0, NT_LAG) — one thread per Lagrangian point.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void update_lagrangian_control(
|
||||||
|
float* __restrict__ motionL, // [NT_LAG * 4]
|
||||||
|
const RigidBodyState2D* state, // single rigid body state
|
||||||
|
float radius, // body radius
|
||||||
|
int NT_LAG) // number of Lagrangian points
|
||||||
|
{
|
||||||
|
int k = threadIdx.x + blockIdx.x * blockDim.x;
|
||||||
|
if (k >= NT_LAG) return;
|
||||||
|
|
||||||
|
float angle = state->theta + (float)k * 2.0f * 3.14159265358979323846f / (float)NT_LAG;
|
||||||
|
|
||||||
|
// Target velocity at this Lagrangian point (rigid-body kinematics)
|
||||||
|
float r_sin = radius * sinf(angle);
|
||||||
|
float r_cos = radius * cosf(angle);
|
||||||
|
|
||||||
|
motionL[4 * k + 0] = state->vx - state->omega * r_sin; // target vx
|
||||||
|
motionL[4 * k + 1] = state->vy + state->omega * r_cos; // target vy
|
||||||
|
motionL[4 * k + 2] = state->x + r_cos; // XL
|
||||||
|
motionL[4 * k + 3] = state->y + r_sin; // YL
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// euler_to_lagrangian (Eul2Lag):
|
||||||
|
// Interpolate Eulerian velocity to Lagrangian points.
|
||||||
|
// Compute Lagrangian force = penalty * (target_vel - interpolated_vel) * dL.
|
||||||
|
//
|
||||||
|
// Thread k ∈ [0, NT_LAG)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void euler_to_lagrangian(
|
||||||
|
float* __restrict__ forceL, // [NT_LAG * 2] output forces
|
||||||
|
const float* __restrict__ motionL, // [NT_LAG * 4]
|
||||||
|
const float* __restrict__ u_field, // [DIM * N] SoA velocity field
|
||||||
|
int NT_LAG)
|
||||||
|
{
|
||||||
|
int k = threadIdx.x + blockIdx.x * blockDim.x;
|
||||||
|
if (k >= NT_LAG) return;
|
||||||
|
|
||||||
|
float XL = motionL[4 * k + 2];
|
||||||
|
float YL = motionL[4 * k + 3];
|
||||||
|
int ix = (int)XL;
|
||||||
|
int iy = (int)YL;
|
||||||
|
|
||||||
|
// Arc-length element dL (central difference)
|
||||||
|
float dL;
|
||||||
|
if (k > 0 && k < NT_LAG - 1) {
|
||||||
|
float dx1 = motionL[4*k+2] - motionL[4*(k-1)+2];
|
||||||
|
float dy1 = motionL[4*k+3] - motionL[4*(k-1)+3];
|
||||||
|
float dx2 = motionL[4*(k+1)+2] - motionL[4*k+2];
|
||||||
|
float dy2 = motionL[4*(k+1)+3] - motionL[4*k+3];
|
||||||
|
dL = 0.5f * (sqrtf(dx1*dx1 + dy1*dy1) + sqrtf(dx2*dx2 + dy2*dy2));
|
||||||
|
} else if (k == 0) {
|
||||||
|
float dx1 = motionL[4*1+2] - motionL[4*0+2];
|
||||||
|
float dy1 = motionL[4*1+3] - motionL[4*0+3];
|
||||||
|
dL = sqrtf(dx1*dx1 + dy1*dy1);
|
||||||
|
} else {
|
||||||
|
float dx1 = motionL[4*k+2] - motionL[4*(k-1)+2];
|
||||||
|
float dy1 = motionL[4*k+3] - motionL[4*(k-1)+3];
|
||||||
|
dL = sqrtf(dx1*dx1 + dy1*dy1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interpolate velocity from Euler grid using Peskin 4-point delta
|
||||||
|
float UL = 0.0f, VL = 0.0f;
|
||||||
|
for (int dy = -2; dy <= 2; dy++) {
|
||||||
|
for (int dx = -2; dx <= 2; dx++) {
|
||||||
|
int ex = ix + dx;
|
||||||
|
int ey = iy + dy;
|
||||||
|
if (ex < 0 || ex >= NX || ey < 0 || ey >= NY) continue;
|
||||||
|
|
||||||
|
float ww = peskin_delta_2d((float)ex - XL, (float)ey - YL);
|
||||||
|
unsigned long en = (unsigned long)ey * NX + (unsigned long)ex;
|
||||||
|
UL += u_field[en] * ww; // ux
|
||||||
|
VL += u_field[TOTAL_CELLS + en] * ww; // uy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Force = (target_vel - interpolated_vel) * dL
|
||||||
|
forceL[2 * k + 0] = 2.0f * (motionL[4*k+0] - UL) * dL; // fx
|
||||||
|
forceL[2 * k + 1] = 2.0f * (motionL[4*k+1] - VL) * dL; // fy
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// lagrangian_to_euler (Lag2Eul):
|
||||||
|
// Spread Lagrangian forces back to the Eulerian grid.
|
||||||
|
//
|
||||||
|
// Thread (x, y) — one thread per Euler node.
|
||||||
|
// Only processes nodes near the body (bounding box check).
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void lagrangian_to_euler(
|
||||||
|
float* __restrict__ forceE, // [DIM * N] output Euler force field
|
||||||
|
const float* __restrict__ forceL, // [NT_LAG * 2]
|
||||||
|
const float* __restrict__ motionL, // [NT_LAG * 4]
|
||||||
|
float xc, float yc, float L0, // body center and extent
|
||||||
|
int NT_LAG)
|
||||||
|
{
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
#if DIM == 2
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
|
||||||
|
// Bounding box check
|
||||||
|
if ((float)x < xc - L0 || (float)x > xc + L0 ||
|
||||||
|
(float)y < yc - L0 || (float)y > yc + L0) return;
|
||||||
|
|
||||||
|
float forcex = 0.0f, forcey = 0.0f;
|
||||||
|
|
||||||
|
for (int kl = 0; kl < NT_LAG; kl++) {
|
||||||
|
float XL = motionL[4 * kl + 2];
|
||||||
|
float YL = motionL[4 * kl + 3];
|
||||||
|
float ww = peskin_delta_2d((float)x - XL, (float)y - YL);
|
||||||
|
forcex += forceL[2 * kl + 0] * ww;
|
||||||
|
forcey += forceL[2 * kl + 1] * ww;
|
||||||
|
}
|
||||||
|
|
||||||
|
forceE[k] = forcex;
|
||||||
|
forceE[TOTAL_CELLS + k] = forcey;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DIM == 2
|
||||||
|
|
||||||
|
#endif // CELERIS_BOUNDARY_IBM_KERNELS_CUH
|
||||||
173
src/CelerisLab/lbm/kernels/boundary/inlet_outlet.cuh
Normal file
173
src/CelerisLab/lbm/kernels/boundary/inlet_outlet.cuh
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
// CelerisLab – boundary/inlet_outlet.cuh
|
||||||
|
// Inlet and outlet boundary conditions (D2Q9).
|
||||||
|
//
|
||||||
|
// Parabolic inlet (non-equilibrium extrapolation, Zou-He style):
|
||||||
|
// Left wall (x=0): reconstruct cx>0 populations (i=1,5,7)
|
||||||
|
//
|
||||||
|
// Pressure outlet (non-equilibrium extrapolation):
|
||||||
|
// Right wall (x=NX-1): reconstruct cx<0 populations (i=2,6,8)
|
||||||
|
//
|
||||||
|
// New paired D2Q9 ordering:
|
||||||
|
// cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
|
||||||
|
// cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_BOUNDARY_INLET_OUTLET_CUH
|
||||||
|
#define CELERIS_BOUNDARY_INLET_OUTLET_CUH
|
||||||
|
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Parabolic inlet (x = 0, non-equilibrium extrapolation)
|
||||||
|
//
|
||||||
|
// f, f_neb are local DDF arrays:
|
||||||
|
// f = populations at the boundary node (x=0)
|
||||||
|
// f_neb = populations at the interior neighbor (x=1)
|
||||||
|
// y = y-coordinate of the boundary node
|
||||||
|
//
|
||||||
|
// Reconstructs f[1], f[5], f[7] (cx > 0 directions in new ordering)
|
||||||
|
// using: f_bc[i] = f_neb[i] - feq(rho_neb, u_neb)[i] + feq(rho_neb, u_target)[i]
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void apply_parabolic_inlet(float* __restrict__ f,
|
||||||
|
const float* __restrict__ f_neb,
|
||||||
|
float y_coord)
|
||||||
|
{
|
||||||
|
// Neighbor macros
|
||||||
|
float p_neb = (f_neb[0]+f_neb[1]+f_neb[2]+f_neb[3]+f_neb[4]
|
||||||
|
+f_neb[5]+f_neb[6]+f_neb[7]+f_neb[8]) / 3.0f;
|
||||||
|
|
||||||
|
// Target velocity (parabolic profile)
|
||||||
|
float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
|
||||||
|
float u_target = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
|
||||||
|
float v_target = 0.0f;
|
||||||
|
|
||||||
|
// Neighbor velocity
|
||||||
|
float u_neb = (f_neb[1]-f_neb[2]+f_neb[5]-f_neb[6]+f_neb[7]-f_neb[8]) / RHO;
|
||||||
|
float v_neb = (f_neb[3]-f_neb[4]+f_neb[5]-f_neb[6]-f_neb[7]+f_neb[8]) / RHO;
|
||||||
|
|
||||||
|
// feq for direction i=1 (cx=1, cy=0), w=1/9:
|
||||||
|
// feq = (2p + RHO*(2u² + 2u - v²)) / 6
|
||||||
|
float feq1_target = (2.0f*p_neb + RHO*(2.0f*u_target*u_target + 2.0f*u_target - v_target*v_target)) / 6.0f;
|
||||||
|
float feq1_neb = (2.0f*p_neb + RHO*(2.0f*u_neb*u_neb + 2.0f*u_neb - v_neb*v_neb)) / 6.0f;
|
||||||
|
|
||||||
|
// feq for direction i=5 (cx=1, cy=1), w=1/36:
|
||||||
|
// feq = (p + RHO*(u² + 3uv + u + v² + v)) / 12
|
||||||
|
float feq5_target = (p_neb + RHO*(u_target*u_target + 3.0f*u_target*v_target + u_target + v_target*v_target + v_target)) / 12.0f;
|
||||||
|
float feq5_neb = (p_neb + RHO*(u_neb*u_neb + 3.0f*u_neb*v_neb + u_neb + v_neb*v_neb + v_neb)) / 12.0f;
|
||||||
|
|
||||||
|
// feq for direction i=7 (cx=1, cy=-1), w=1/36:
|
||||||
|
// feq = (p + RHO*(u² - 3uv + u + v² - v)) / 12
|
||||||
|
float feq7_target = (p_neb + RHO*(u_target*u_target - 3.0f*u_target*v_target + u_target + v_target*v_target - v_target)) / 12.0f;
|
||||||
|
float feq7_neb = (p_neb + RHO*(u_neb*u_neb - 3.0f*u_neb*v_neb + u_neb + v_neb*v_neb - v_neb)) / 12.0f;
|
||||||
|
|
||||||
|
// Non-equilibrium extrapolation
|
||||||
|
f[1] = f_neb[1] - feq1_neb + feq1_target;
|
||||||
|
f[5] = f_neb[5] - feq5_neb + feq5_target;
|
||||||
|
f[7] = f_neb[7] - feq7_neb + feq7_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Pressure outlet (x = NX-1, non-equilibrium extrapolation)
|
||||||
|
//
|
||||||
|
// Reconstructs f[2], f[6], f[8] (cx < 0 directions in new ordering)
|
||||||
|
// p_out = 0 (gauge pressure), uses velocity from neighbor.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void apply_pressure_outlet(float* __restrict__ f,
|
||||||
|
const float* __restrict__ f_neb,
|
||||||
|
float y_coord)
|
||||||
|
{
|
||||||
|
float p_out = 0.0f;
|
||||||
|
|
||||||
|
// Target velocity (parabolic, same as inlet for consistency)
|
||||||
|
float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
|
||||||
|
float u_target = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
|
||||||
|
float v_target = 0.0f;
|
||||||
|
|
||||||
|
// Neighbor velocity
|
||||||
|
float u_neb = (f_neb[1]-f_neb[2]+f_neb[5]-f_neb[6]+f_neb[7]-f_neb[8]) / RHO;
|
||||||
|
float v_neb = (f_neb[3]-f_neb[4]+f_neb[5]-f_neb[6]-f_neb[7]+f_neb[8]) / RHO;
|
||||||
|
|
||||||
|
// feq for direction i=2 (cx=-1, cy=0), w=1/9:
|
||||||
|
// feq = (2p - RHO*(-2u² + 2u + v²)) / 6
|
||||||
|
float feq2_target = (2.0f*p_out - RHO*(-2.0f*u_target*u_target + 2.0f*u_target + v_target*v_target)) / 6.0f;
|
||||||
|
float feq2_neb = (2.0f*p_out - RHO*(-2.0f*u_neb*u_neb + 2.0f*u_neb + v_neb*v_neb)) / 6.0f;
|
||||||
|
|
||||||
|
// feq for direction i=8 (cx=-1, cy=1), w=1/36:
|
||||||
|
// feq = (p + RHO*(u² - 3uv - u + v² + v)) / 12
|
||||||
|
float feq8_target = (p_out + RHO*(u_target*u_target - 3.0f*u_target*v_target - u_target + v_target*v_target + v_target)) / 12.0f;
|
||||||
|
float feq8_neb = (p_out + RHO*(u_neb*u_neb - 3.0f*u_neb*v_neb - u_neb + v_neb*v_neb + v_neb)) / 12.0f;
|
||||||
|
|
||||||
|
// feq for direction i=6 (cx=-1, cy=-1), w=1/36:
|
||||||
|
// feq = (p + RHO*(u² + 3uv - u + v² - v)) / 12
|
||||||
|
float feq6_target = (p_out + RHO*(u_target*u_target + 3.0f*u_target*v_target - u_target + v_target*v_target - v_target)) / 12.0f;
|
||||||
|
float feq6_neb = (p_out + RHO*(u_neb*u_neb + 3.0f*u_neb*v_neb - u_neb + v_neb*v_neb - v_neb)) / 12.0f;
|
||||||
|
|
||||||
|
f[2] = f_neb[2] - feq2_neb + feq2_target;
|
||||||
|
f[8] = f_neb[8] - feq8_neb + feq8_target;
|
||||||
|
f[6] = f_neb[6] - feq6_neb + feq6_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ == 9
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// D3Q19 inlet / outlet (non-equilibrium extrapolation)
|
||||||
|
//
|
||||||
|
// Parabolic inlet (x=0): reconstruct cx>0 populations i=1,7,9,13,15
|
||||||
|
// Pressure outlet (x=NX-1): reconstruct cx<0 populations i=2,8,10,14,16
|
||||||
|
//
|
||||||
|
// Uses generic feq computation from macro.cuh to avoid hand-expanded formulas.
|
||||||
|
// ============================================================================
|
||||||
|
#if NQ == 19
|
||||||
|
|
||||||
|
__device__ inline void apply_parabolic_inlet_3d(float* __restrict__ f,
|
||||||
|
const float* __restrict__ f_neb,
|
||||||
|
float y_coord)
|
||||||
|
{
|
||||||
|
// Neighbor macros
|
||||||
|
float rho_neb, un, vn, wn;
|
||||||
|
compute_rho_u(f_neb, rho_neb, un, vn, wn);
|
||||||
|
|
||||||
|
// Target velocity (parabolic in y, uniform in z)
|
||||||
|
float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
|
||||||
|
float u_tar = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
|
||||||
|
|
||||||
|
// feq arrays
|
||||||
|
float feq_tar[19], feq_neb[19];
|
||||||
|
compute_feq(rho_neb, u_tar, 0.0f, 0.0f, feq_tar);
|
||||||
|
compute_feq(rho_neb, un, vn, wn, feq_neb);
|
||||||
|
|
||||||
|
// Reconstruct cx>0 directions: i = 1, 7, 9, 13, 15
|
||||||
|
f[1] = f_neb[1] - feq_neb[1] + feq_tar[1];
|
||||||
|
f[7] = f_neb[7] - feq_neb[7] + feq_tar[7];
|
||||||
|
f[9] = f_neb[9] - feq_neb[9] + feq_tar[9];
|
||||||
|
f[13] = f_neb[13] - feq_neb[13] + feq_tar[13];
|
||||||
|
f[15] = f_neb[15] - feq_neb[15] + feq_tar[15];
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ inline void apply_pressure_outlet_3d(float* __restrict__ f,
|
||||||
|
const float* __restrict__ f_neb,
|
||||||
|
float y_coord)
|
||||||
|
{
|
||||||
|
// Neighbor macros
|
||||||
|
float rho_neb, un, vn, wn;
|
||||||
|
compute_rho_u(f_neb, rho_neb, un, vn, wn);
|
||||||
|
|
||||||
|
// Target: p_out = 0 gauge → rho = 1.0 (using neighbor velocity)
|
||||||
|
float yy = (y_coord - 0.5f * (NY - 1)) / (NY - 2.0f);
|
||||||
|
float u_tar = U0 * 1.5f * (1.0f - 4.0f * yy * yy);
|
||||||
|
|
||||||
|
float feq_tar[19], feq_neb[19];
|
||||||
|
compute_feq(RHO, u_tar, 0.0f, 0.0f, feq_tar);
|
||||||
|
compute_feq(RHO, un, vn, wn, feq_neb);
|
||||||
|
|
||||||
|
// Reconstruct cx<0 directions: i = 2, 8, 10, 14, 16
|
||||||
|
f[2] = f_neb[2] - feq_neb[2] + feq_tar[2];
|
||||||
|
f[8] = f_neb[8] - feq_neb[8] + feq_tar[8];
|
||||||
|
f[10] = f_neb[10] - feq_neb[10] + feq_tar[10];
|
||||||
|
f[14] = f_neb[14] - feq_neb[14] + feq_tar[14];
|
||||||
|
f[16] = f_neb[16] - feq_neb[16] + feq_tar[16];
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ == 19
|
||||||
|
|
||||||
|
#endif // CELERIS_BOUNDARY_INLET_OUTLET_CUH
|
||||||
75
src/CelerisLab/lbm/kernels/core/descriptors.cuh
Normal file
75
src/CelerisLab/lbm/kernels/core/descriptors.cuh
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
// CelerisLab – core/descriptors.cuh
|
||||||
|
// Lattice descriptor constants.
|
||||||
|
// Direction ordering: paired for Esoteric-Pull compatibility.
|
||||||
|
// i=0 rest; for i>=1, (i, i^1|1) are opposite pairs:
|
||||||
|
// opp(0) = 0
|
||||||
|
// opp(i) = ((i-1) ^ 1) + 1 for i >= 1
|
||||||
|
//
|
||||||
|
// D2Q9 : (0)rest (1,2)±x (3,4)±y (5,6)±(x+y) (7,8)±(x-y)
|
||||||
|
// D3Q19: (0)rest (1,2)±x (3,4)±y (5,6)±z
|
||||||
|
// (7,8)±(x+y) (9,10)±(x+z) (11,12)±(y+z)
|
||||||
|
// (13,14)±(x-y) (15,16)±(x-z) (17,18)±(y-z)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_CORE_DESCRIPTORS_CUH
|
||||||
|
#define CELERIS_CORE_DESCRIPTORS_CUH
|
||||||
|
|
||||||
|
// NQ and DIM come from macros.h (included before this header)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D2Q9
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
__constant__ int d_cx[9] = { 0, 1, -1, 0, 0, 1, -1, 1, -1};
|
||||||
|
__constant__ int d_cy[9] = { 0, 0, 0, 1, -1, 1, -1, -1, 1};
|
||||||
|
|
||||||
|
__constant__ float d_w[9] = {
|
||||||
|
4.0f/9.0f,
|
||||||
|
1.0f/9.0f, 1.0f/9.0f, 1.0f/9.0f, 1.0f/9.0f,
|
||||||
|
1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
// Named weight macros (avoid __constant__ reads in hot paths)
|
||||||
|
#define W0_VAL (4.0f/9.0f)
|
||||||
|
#define WS_VAL (1.0f/9.0f)
|
||||||
|
#define WE_VAL (1.0f/36.0f)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D3Q19
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#elif NQ == 19
|
||||||
|
|
||||||
|
__constant__ int d_cx[19] = { 0, 1,-1, 0, 0, 0, 0, 1,-1, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0};
|
||||||
|
__constant__ int d_cy[19] = { 0, 0, 0, 1,-1, 0, 0, 1,-1, 0, 0, 1,-1, -1, 1, 0, 0, 1,-1};
|
||||||
|
__constant__ int d_cz[19] = { 0, 0, 0, 0, 0, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0, -1, 1, -1, 1};
|
||||||
|
|
||||||
|
__constant__ float d_w[19] = {
|
||||||
|
1.0f/3.0f,
|
||||||
|
1.0f/18.0f, 1.0f/18.0f, 1.0f/18.0f, 1.0f/18.0f, 1.0f/18.0f, 1.0f/18.0f,
|
||||||
|
1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f,
|
||||||
|
1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f, 1.0f/36.0f
|
||||||
|
};
|
||||||
|
|
||||||
|
#define W0_VAL (1.0f/3.0f)
|
||||||
|
#define WS_VAL (1.0f/18.0f)
|
||||||
|
#define WE_VAL (1.0f/36.0f)
|
||||||
|
|
||||||
|
#else
|
||||||
|
#error "Unsupported NQ. Use 9 (D2Q9) or 19 (D3Q19)."
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Lattice sound speed
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define CS2 (1.0f / 3.0f)
|
||||||
|
#define CS2_INV (3.0f)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Opposite direction (works for any paired-ordered lattice)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ int opp_dir(int i) {
|
||||||
|
return (i == 0) ? 0 : (((i - 1) ^ 1) + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_CORE_DESCRIPTORS_CUH
|
||||||
86
src/CelerisLab/lbm/kernels/core/flags.cuh
Normal file
86
src/CelerisLab/lbm/kernels/core/flags.cuh
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
// CelerisLab – core/flags.cuh
|
||||||
|
// Cell flag definitions (uint32_t, layered bytes) and helper functions.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_CORE_FLAGS_CUH
|
||||||
|
#define CELERIS_CORE_FLAGS_CUH
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Byte 0 [0:7] – basic cell type
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define FLAG_FLUID 0x01u
|
||||||
|
#define FLAG_SOLID 0x02u
|
||||||
|
#define FLAG_GAS 0x04u
|
||||||
|
#define FLAG_INTERFACE 0x08u
|
||||||
|
#define FLAG_SENSOR 0x10u
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Byte 1 [8:15] – boundary condition type
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define FLAG_BB (0x01u << 8) // full-way bounce-back
|
||||||
|
#define FLAG_EQ_BC (0x02u << 8) // equilibrium (Dirichlet) boundary
|
||||||
|
#define FLAG_CURVED (0x04u << 8) // curved-boundary interpolation
|
||||||
|
#define FLAG_IBM (0x08u << 8) // immersed boundary marker
|
||||||
|
#define FLAG_MOVING (0x10u << 8) // moving-wall velocity
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Byte 2 [16:23] – multi-GPU / AMR / ghost
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define FLAG_GHOST (0x01u << 16)
|
||||||
|
#define FLAG_HALO (0x02u << 16)
|
||||||
|
#define FLAG_AMR_FINE (0x04u << 16)
|
||||||
|
#define FLAG_AMR_COARSE (0x08u << 16)
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Byte 3 [24:31] – reserved for extensions (phase-field, particles …)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Masks
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define MASK_CELL_TYPE 0x000000FFu
|
||||||
|
#define MASK_BC_TYPE 0x0000FF00u
|
||||||
|
#define MASK_MULTIGPU 0x00FF0000u
|
||||||
|
#define MASK_EXTENSION 0xFF000000u
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Legacy compatibility (current driver.py uses uint8 with these bits)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define LEGACY_FLUID 0x01
|
||||||
|
#define LEGACY_SOLID 0x02
|
||||||
|
#define LEGACY_GAS 0x04
|
||||||
|
#define LEGACY_OBSTACLE 0x04 // obstacle / immersed body (triggers BB at adjacent fluid)
|
||||||
|
#define LEGACY_INTERFACE 0x08
|
||||||
|
#define LEGACY_SENSOR 0x10
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Device helper functions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ bool is_fluid(uint32_t f) { return (f & FLAG_FLUID) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_solid(uint32_t f) { return (f & FLAG_SOLID) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_gas(uint32_t f) { return (f & FLAG_GAS) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_interface(uint32_t f) { return (f & FLAG_INTERFACE) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_sensor(uint32_t f) { return (f & FLAG_SENSOR) != 0; }
|
||||||
|
|
||||||
|
__device__ __forceinline__ bool is_bb(uint32_t f) { return (f & FLAG_BB) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_curved(uint32_t f) { return (f & FLAG_CURVED) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_ibm(uint32_t f) { return (f & FLAG_IBM) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_moving(uint32_t f) { return (f & FLAG_MOVING) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_eq_bc(uint32_t f) { return (f & FLAG_EQ_BC) != 0; }
|
||||||
|
|
||||||
|
__device__ __forceinline__ bool is_boundary(uint32_t f) { return (f & MASK_BC_TYPE) != 0; }
|
||||||
|
__device__ __forceinline__ bool is_wall(uint32_t f) { return (f & (FLAG_BB | FLAG_CURVED | FLAG_MOVING)) != 0; }
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Legacy flag promotion (uint8 flags → uint32 with inferred BC bits)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ uint32_t promote_legacy_flag(uint8_t legacy) {
|
||||||
|
uint32_t f = (uint32_t)legacy & 0x1Fu; // copy low 5 bits
|
||||||
|
if ((f & FLAG_SOLID) && (f & FLAG_INTERFACE)) // SOLID + INTERFACE
|
||||||
|
f |= FLAG_CURVED; // → mark as curved BC
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_CORE_FLAGS_CUH
|
||||||
155
src/CelerisLab/lbm/kernels/core/layout.cuh
Normal file
155
src/CelerisLab/lbm/kernels/core/layout.cuh
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
// CelerisLab – core/layout.cuh
|
||||||
|
// SoA memory layout: index_f, coordinates, and neighbor computation.
|
||||||
|
// Depends on: NQ, NX, NY, NZ from macros.h; descriptors.cuh for d_cx/d_cy.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_CORE_LAYOUT_CUH
|
||||||
|
#define CELERIS_CORE_LAYOUT_CUH
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Total cell count (compile-time)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#define TOTAL_CELLS ((unsigned long)(NX) * (unsigned long)(NY) * (unsigned long)(NZ))
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// SoA index: fi[ direction * N + node ] ("Q-major")
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ unsigned long index_f(unsigned long n, unsigned int i) {
|
||||||
|
return (unsigned long)i * TOTAL_CELLS + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// SoA index for DIM-component field: u[ component * N + node ]
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ unsigned long index_u(unsigned long n, unsigned int d) {
|
||||||
|
return (unsigned long)d * TOTAL_CELLS + n;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// 2D linear index → (x, y) and (x, y) → linear
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if DIM == 2
|
||||||
|
|
||||||
|
__device__ __forceinline__ void coordinates(unsigned long n,
|
||||||
|
unsigned int& x,
|
||||||
|
unsigned int& y)
|
||||||
|
{
|
||||||
|
x = (unsigned int)(n % NX);
|
||||||
|
y = (unsigned int)(n / NX);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ unsigned long linear_index(unsigned int x,
|
||||||
|
unsigned int y)
|
||||||
|
{
|
||||||
|
return (unsigned long)y * NX + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ void index_from_thread(unsigned int& x,
|
||||||
|
unsigned int& y,
|
||||||
|
unsigned long& k)
|
||||||
|
{
|
||||||
|
x = threadIdx.x + NT * blockIdx.x;
|
||||||
|
y = blockIdx.y;
|
||||||
|
k = (unsigned long)y * NX + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Neighbor list (periodic BCs via modular arithmetic)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void compute_neighbors(unsigned long n, unsigned long* j) {
|
||||||
|
unsigned int x, y;
|
||||||
|
coordinates(n, x, y);
|
||||||
|
|
||||||
|
unsigned int xp = (x + 1u) % NX;
|
||||||
|
unsigned int xm = (x + NX - 1u) % NX;
|
||||||
|
unsigned int yp = (y + 1u) % NY;
|
||||||
|
unsigned int ym = (y + NY - 1u) % NY;
|
||||||
|
|
||||||
|
// i=0: self
|
||||||
|
j[0] = n;
|
||||||
|
|
||||||
|
#if NQ == 9
|
||||||
|
// paired: (1,2)±x (3,4)±y (5,6)±(x+y) (7,8)±(x-y)
|
||||||
|
j[1] = linear_index(xp, y ); // +x
|
||||||
|
j[2] = linear_index(xm, y ); // -x
|
||||||
|
j[3] = linear_index(x , yp); // +y
|
||||||
|
j[4] = linear_index(x , ym); // -y
|
||||||
|
j[5] = linear_index(xp, yp); // +x +y
|
||||||
|
j[6] = linear_index(xm, ym); // -x -y
|
||||||
|
j[7] = linear_index(xp, ym); // +x -y
|
||||||
|
j[8] = linear_index(xm, yp); // -x +y
|
||||||
|
#elif NQ == 19
|
||||||
|
#error "D3Q19 requires DIM == 3. Set DIM=3 in macros.h."
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif DIM == 3
|
||||||
|
|
||||||
|
__device__ __forceinline__ void coordinates(unsigned long n,
|
||||||
|
unsigned int& x,
|
||||||
|
unsigned int& y,
|
||||||
|
unsigned int& z)
|
||||||
|
{
|
||||||
|
x = (unsigned int)(n % NX);
|
||||||
|
y = (unsigned int)(n / NX % NY);
|
||||||
|
z = (unsigned int)(n / ((unsigned long)NX * NY));
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ unsigned long linear_index(unsigned int x,
|
||||||
|
unsigned int y,
|
||||||
|
unsigned int z)
|
||||||
|
{
|
||||||
|
return (unsigned long)z * NY * NX + (unsigned long)y * NX + x;
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ void index_from_thread(unsigned int& x,
|
||||||
|
unsigned int& y,
|
||||||
|
unsigned int& z,
|
||||||
|
unsigned long& k)
|
||||||
|
{
|
||||||
|
x = threadIdx.x + NT * blockIdx.x;
|
||||||
|
y = blockIdx.y;
|
||||||
|
z = blockIdx.z;
|
||||||
|
k = linear_index(x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ inline void compute_neighbors(unsigned long n, unsigned long* j) {
|
||||||
|
unsigned int x, y, z;
|
||||||
|
coordinates(n, x, y, z);
|
||||||
|
|
||||||
|
unsigned int xp = (x + 1u) % NX;
|
||||||
|
unsigned int xm = (x + NX - 1u) % NX;
|
||||||
|
unsigned int yp = (y + 1u) % NY;
|
||||||
|
unsigned int ym = (y + NY - 1u) % NY;
|
||||||
|
unsigned int zp = (z + 1u) % NZ;
|
||||||
|
unsigned int zm = (z + NZ - 1u) % NZ;
|
||||||
|
|
||||||
|
j[0] = n;
|
||||||
|
|
||||||
|
#if NQ == 19
|
||||||
|
// ±x, ±y, ±z (straight)
|
||||||
|
j[1] = linear_index(xp, y, z ); // +x
|
||||||
|
j[2] = linear_index(xm, y, z ); // -x
|
||||||
|
j[3] = linear_index(x, yp, z ); // +y
|
||||||
|
j[4] = linear_index(x, ym, z ); // -y
|
||||||
|
j[5] = linear_index(x, y, zp); // +z
|
||||||
|
j[6] = linear_index(x, y, zm); // -z
|
||||||
|
// diagonal
|
||||||
|
j[7] = linear_index(xp, yp, z ); // +x+y
|
||||||
|
j[8] = linear_index(xm, ym, z ); // -x-y
|
||||||
|
j[9] = linear_index(xp, y, zp); // +x+z
|
||||||
|
j[10] = linear_index(xm, y, zm); // -x-z
|
||||||
|
j[11] = linear_index(x, yp, zp); // +y+z
|
||||||
|
j[12] = linear_index(x, ym, zm); // -y-z
|
||||||
|
j[13] = linear_index(xp, ym, z ); // +x-y
|
||||||
|
j[14] = linear_index(xm, yp, z ); // -x+y
|
||||||
|
j[15] = linear_index(xp, y, zm); // +x-z
|
||||||
|
j[16] = linear_index(xm, y, zp); // -x+z
|
||||||
|
j[17] = linear_index(x, yp, zm); // +y-z
|
||||||
|
j[18] = linear_index(x, ym, zp); // -y+z
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // DIM == 3
|
||||||
|
|
||||||
|
#endif // CELERIS_CORE_LAYOUT_CUH
|
||||||
62
src/CelerisLab/lbm/kernels/core/params.cuh
Normal file
62
src/CelerisLab/lbm/kernels/core/params.cuh
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
// CelerisLab – core/params.cuh
|
||||||
|
// Runtime parameter structures transported via __constant__ memory.
|
||||||
|
// Includes: LBMParams, RigidBodyState2D, RigidBodyControl2D.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_CORE_PARAMS_CUH
|
||||||
|
#define CELERIS_CORE_PARAMS_CUH
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// LBM runtime parameters (uploaded once per run or on parameter change)
|
||||||
|
// ============================================================================
|
||||||
|
struct LBMParams {
|
||||||
|
//--- grid ---
|
||||||
|
unsigned int Nx, Ny, Nz;
|
||||||
|
unsigned long N; // Nx * Ny * Nz
|
||||||
|
|
||||||
|
//--- relaxation ---
|
||||||
|
float omega; // SRT/TRT主松弛率 w = 1/(3ν + 0.5)
|
||||||
|
float omega_bulk; // MRT bulk 松弛率 (s_e / s_eps)
|
||||||
|
|
||||||
|
//--- external body force ---
|
||||||
|
float fx, fy, fz;
|
||||||
|
|
||||||
|
//--- reference quantities ---
|
||||||
|
float rho_ref; // 参考密度 (通常 1.0)
|
||||||
|
float u_inlet; // 入口参考速度
|
||||||
|
|
||||||
|
//--- diagnostics ---
|
||||||
|
unsigned int n_objects; // 观测对象数量
|
||||||
|
};
|
||||||
|
|
||||||
|
__constant__ LBMParams d_params;
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Rigid-body state / control (2D, per architecture §17.3)
|
||||||
|
// ============================================================================
|
||||||
|
struct RigidBodyState2D {
|
||||||
|
float x, y, theta; // 位姿 (position + orientation)
|
||||||
|
float vx, vy, omega; // 速度 (translational + rotational)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RigidBodyControl2D {
|
||||||
|
float ax_cmd, ay_cmd; // 目标加速度(或目标速度/位移, 取决于 mode)
|
||||||
|
float alpha_cmd; // 目标角加速度 / 角速度
|
||||||
|
int control_mode; // 0=力控, 1=速度控, 2=位移控
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Halo plan (multi-GPU / AMR 预留)
|
||||||
|
// ============================================================================
|
||||||
|
struct HaloPlan {
|
||||||
|
int face_id;
|
||||||
|
int peer_gpu;
|
||||||
|
int level; // AMR level (0 = base)
|
||||||
|
size_t count;
|
||||||
|
int* send_idx;
|
||||||
|
int* recv_idx;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CELERIS_CORE_PARAMS_CUH
|
||||||
91
src/CelerisLab/lbm/kernels/core/precision.cuh
Normal file
91
src/CelerisLab/lbm/kernels/core/precision.cuh
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
// CelerisLab – core/precision.cuh
|
||||||
|
// Storage precision abstraction (FP32 / FP16S / FP16C).
|
||||||
|
// Compute precision is always float (FP32).
|
||||||
|
//
|
||||||
|
// Controlled by macros.h:
|
||||||
|
// STORE_PRECISION 0 = FP32 (default)
|
||||||
|
// 1 = FP16S (IEEE-754 half + ×32768 scaling)
|
||||||
|
// 2 = FP16C (custom 1-4-11 format, higher precision)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_CORE_PRECISION_CUH
|
||||||
|
#define CELERIS_CORE_PRECISION_CUH
|
||||||
|
|
||||||
|
#ifndef STORE_PRECISION
|
||||||
|
#define STORE_PRECISION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FP16S – Scaled IEEE-754 Half (hardware-accelerated conversion)
|
||||||
|
// ============================================================================
|
||||||
|
#if STORE_PRECISION == 1
|
||||||
|
|
||||||
|
#include <cuda_fp16.h>
|
||||||
|
using fpxx = __half;
|
||||||
|
|
||||||
|
__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
|
||||||
|
return __half2float(p[idx]) * 3.0517578125e-5f; // ÷ 32768
|
||||||
|
}
|
||||||
|
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
|
||||||
|
p[idx] = __float2half_rn(v * 32768.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FPXX_BYTES 2
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FP16C – Custom 1-4-11 format (software conversion, ~3.6 dec. digits)
|
||||||
|
// ============================================================================
|
||||||
|
#elif STORE_PRECISION == 2
|
||||||
|
|
||||||
|
using fpxx = unsigned short;
|
||||||
|
|
||||||
|
__device__ __forceinline__ float half_custom_to_float(unsigned short x) {
|
||||||
|
unsigned int e = (x & 0x7800) >> 11;
|
||||||
|
unsigned int m = (x & 0x07FF) << 12;
|
||||||
|
unsigned int v = __float_as_uint((float)m) >> 23;
|
||||||
|
return __uint_as_float(
|
||||||
|
(x & 0x8000) << 16 |
|
||||||
|
(e != 0) * ((e + 112) << 23 | m) |
|
||||||
|
((e == 0) & (m != 0)) * ((v - 37) << 23 | ((m << (150 - v)) & 0x007FF000))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ unsigned short float_to_half_custom(float x) {
|
||||||
|
unsigned int b = __float_as_uint(x) + 0x00000800; // round-to-nearest-even
|
||||||
|
unsigned int e = (b & 0x7F800000) >> 23;
|
||||||
|
unsigned int m = b & 0x007FFFFF;
|
||||||
|
return (unsigned short)(
|
||||||
|
(b & 0x80000000) >> 16 |
|
||||||
|
(e > 112) * ((((e - 112) << 11) & 0x7800) | m >> 12) |
|
||||||
|
((e < 113) & (e > 100)) * ((((0x007FF800 + m) >> (124 - e)) + 1) >> 1)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
|
||||||
|
return half_custom_to_float(p[idx]);
|
||||||
|
}
|
||||||
|
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
|
||||||
|
p[idx] = float_to_half_custom(v);
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FPXX_BYTES 2
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// FP32 – No conversion (default)
|
||||||
|
// ============================================================================
|
||||||
|
#else
|
||||||
|
|
||||||
|
using fpxx = float;
|
||||||
|
|
||||||
|
__device__ __forceinline__ float load_ddf(const fpxx* p, unsigned long idx) {
|
||||||
|
return p[idx];
|
||||||
|
}
|
||||||
|
__device__ __forceinline__ void store_ddf(fpxx* p, unsigned long idx, float v) {
|
||||||
|
p[idx] = v;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define FPXX_BYTES 4
|
||||||
|
|
||||||
|
#endif // STORE_PRECISION
|
||||||
|
|
||||||
|
#endif // CELERIS_CORE_PRECISION_CUH
|
||||||
299
src/CelerisLab/lbm/kernels/kernel_v2.cu
Normal file
299
src/CelerisLab/lbm/kernels/kernel_v2.cu
Normal file
@ -0,0 +1,299 @@
|
|||||||
|
// CelerisLab – kernel_v2.cu
|
||||||
|
// ============================================================================
|
||||||
|
// Modular compilation entry point (Stage 1 Architecture)
|
||||||
|
//
|
||||||
|
// This file includes all modular headers and step kernels,
|
||||||
|
// and exports extern "C" functions for PyCUDA.
|
||||||
|
//
|
||||||
|
// Controlled by macros.h:
|
||||||
|
// NQ = 9 (D2Q9) or 19 (D3Q19)
|
||||||
|
// COLLISION_MODEL = 0 (SRT) / 1 (TRT) / 2 (MRT)
|
||||||
|
// STREAMING_MODEL = 0 (double-buffer) / 1 (Esoteric-Pull)
|
||||||
|
// STORE_PRECISION = 0 (FP32) / 1 (FP16S) / 2 (FP16C)
|
||||||
|
// USE_DDF_SHIFTING= 0 / 1
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <cuda.h>
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 0: Configuration (compile-time)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "macros.h"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 1: Core primitives
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "core/precision.cuh"
|
||||||
|
#include "core/flags.cuh"
|
||||||
|
#include "core/descriptors.cuh"
|
||||||
|
#include "core/layout.cuh"
|
||||||
|
#include "core/params.cuh"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 2: Operators
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "operators/macro.cuh"
|
||||||
|
#include "operators/collision_srt.cuh"
|
||||||
|
#include "operators/collision_trt.cuh"
|
||||||
|
#include "operators/collision_mrt.cuh"
|
||||||
|
#include "operators/forcing_guo.cuh"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 3: Streaming
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "streaming/pull_double_buffer.cuh"
|
||||||
|
#include "streaming/esopull_single_buffer.cuh"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 4: Boundary conditions
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "boundary/bounce_back.cuh"
|
||||||
|
#include "boundary/curved_boundary.cuh"
|
||||||
|
#include "boundary/inlet_outlet.cuh"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 5: Step kernels (selected by STREAMING_MODEL)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "step/one_step_double.cu"
|
||||||
|
#include "step/one_step_esopull.cu"
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Layer 6: IBM kernels (always available, launched separately)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#include "boundary/ibm_kernels.cuh"
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// Extern "C" wrappers (for PyCUDA / ctypes)
|
||||||
|
//
|
||||||
|
// Naming:
|
||||||
|
// OneStep – backward-compatible main step (double-buffer)
|
||||||
|
// InitTubeFlow_v2 – channel initialization (new ordering)
|
||||||
|
// StreamCollide – new API main step (double or esopull)
|
||||||
|
// CurvedBoundary – post-step curved boundary correction
|
||||||
|
// UpdateFields – recompute rho/u from DDF
|
||||||
|
// InitEsoPull – Esoteric-Pull initialization
|
||||||
|
// IBM_UpdateLag – IBM Lagrangian point update
|
||||||
|
// IBM_Eul2Lag – Euler → Lagrangian interpolation
|
||||||
|
// IBM_Lag2Eul – Lagrangian → Euler spreading
|
||||||
|
// ============================================================================
|
||||||
|
extern "C"
|
||||||
|
{
|
||||||
|
|
||||||
|
// ----- Backward-compatible init (uses new direction ordering) -----
|
||||||
|
__global__ void InitTubeFlow_v2(uint8_t* flag, fpxx* fi)
|
||||||
|
{
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
|
||||||
|
float u_init = U0 * 1.5f * (1.0f - 4.0f * ((float)y - 0.5f * (NY - 1))
|
||||||
|
* ((float)y - 0.5f * (NY - 1))
|
||||||
|
/ ((float)(NY - 2) * (float)(NY - 2)));
|
||||||
|
|
||||||
|
if (y == 0 || y == NY - 1 || x == 0 || x == NX - 1) {
|
||||||
|
flag[k] = LEGACY_SOLID;
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), 0.0f);
|
||||||
|
} else {
|
||||||
|
flag[k] = (uint8_t)LEGACY_FLUID;
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
float cu = (float)d_cx[i] * u_init;
|
||||||
|
float val = d_w[i] * RHO * (1.0f + 3.0f*cu + 4.5f*cu*cu - 1.5f*u_init*u_init);
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
val -= d_w[i];
|
||||||
|
#endif
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
|
||||||
|
float u_init = U0 * 1.5f * (1.0f - 4.0f * ((float)y - 0.5f * (NY - 1))
|
||||||
|
* ((float)y - 0.5f * (NY - 1))
|
||||||
|
/ ((float)(NY - 2) * (float)(NY - 2)));
|
||||||
|
|
||||||
|
if (y == 0 || y == NY - 1 || x == 0 || x == NX - 1) {
|
||||||
|
flag[k] = LEGACY_SOLID;
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), 0.0f);
|
||||||
|
} else {
|
||||||
|
flag[k] = (uint8_t)LEGACY_FLUID;
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
float cu = (float)d_cx[i] * u_init;
|
||||||
|
float val = d_w[i] * RHO * (1.0f + 3.0f*cu + 4.5f*cu*cu - 1.5f*u_init*u_init);
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
val -= d_w[i];
|
||||||
|
#endif
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Main step (double-buffer) -----
|
||||||
|
// Signature compatible with driver.py: flag, fi_in, fi_out, indx, delta, action, obs
|
||||||
|
__global__ void OneStep(
|
||||||
|
uint8_t* flag,
|
||||||
|
fpxx* fi_in,
|
||||||
|
fpxx* fi_out,
|
||||||
|
int32_t* indx,
|
||||||
|
float* delta,
|
||||||
|
float* action,
|
||||||
|
float* obs)
|
||||||
|
{
|
||||||
|
// Redirect to the modular StreamCollideDouble kernel body
|
||||||
|
// (We inline here to keep a single extern "C" entry point.)
|
||||||
|
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t fl = flag[k];
|
||||||
|
|
||||||
|
unsigned long j[NQ];
|
||||||
|
compute_neighbors(k, j);
|
||||||
|
|
||||||
|
float f[NQ];
|
||||||
|
|
||||||
|
#if STREAMING_MODEL == 0
|
||||||
|
stream_pull_load(k, f, fi_in, j);
|
||||||
|
#else
|
||||||
|
// Esoteric-Pull for OneStep compat would need timestep — not available
|
||||||
|
// in legacy signature. Use double-buffer mode only for OneStep.
|
||||||
|
stream_pull_load(k, f, fi_in, j);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float rho_n, ux, uy;
|
||||||
|
#if NQ == 9
|
||||||
|
compute_rho_u(f, rho_n, ux, uy);
|
||||||
|
|
||||||
|
// Inlet / outlet / wall bounce-back
|
||||||
|
if (fl & LEGACY_SOLID) {
|
||||||
|
bool interior_y = (y > 0u) && (y < (unsigned int)(NY - 1));
|
||||||
|
if (x == 0 && interior_y) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x + 1u, y);
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
apply_parabolic_inlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else if (x == (unsigned int)(NX - 1) && interior_y) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x - 1u, y);
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
apply_pressure_outlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Wall / corner: full-way bounce-back (swap all pairs)
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
float t = f[i]; f[i] = f[i+1]; f[i+1] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collision
|
||||||
|
if (fl & LEGACY_FLUID) {
|
||||||
|
float feq[NQ], Fin[NQ];
|
||||||
|
compute_feq(rho_n, ux, uy, feq);
|
||||||
|
zero_forcing(Fin);
|
||||||
|
|
||||||
|
#if COLLISION_MODEL == 0
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 1
|
||||||
|
collide_trt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 2
|
||||||
|
collide_mrt(f, rho_n, ux, uy, Fin, d_params.omega);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_pull_store(k, f, fi_out);
|
||||||
|
|
||||||
|
// Curved boundary processing
|
||||||
|
if ((fl & LEGACY_SOLID) && (fl & LEGACY_INTERFACE)) {
|
||||||
|
int id_off = indx[k];
|
||||||
|
int id_obj = *reinterpret_cast<int*>(&delta[id_off]);
|
||||||
|
float Uw = action[id_obj] * delta[id_off + NQ];
|
||||||
|
float Vw = action[id_obj] * delta[id_off + NQ + 1];
|
||||||
|
float* obs_fx = &obs[DIM * id_obj];
|
||||||
|
float* obs_fy = &obs[DIM * id_obj + 1];
|
||||||
|
apply_curved_boundary(k, x, y, fi_out, delta, id_off, Uw, Vw, obs_fx, obs_fy);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sensor
|
||||||
|
if (fl & LEGACY_SENSOR) {
|
||||||
|
int id_obj = indx[k];
|
||||||
|
atomicAdd(&obs[DIM * id_obj], ux);
|
||||||
|
atomicAdd(&obs[DIM * id_obj + 1], uy);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
// ---- Macroscopic (computed for all nodes; only used for fluid collision) ----
|
||||||
|
float uz;
|
||||||
|
compute_rho_u(f, rho_n, ux, uy, uz);
|
||||||
|
|
||||||
|
// ---- Boundary conditions ----
|
||||||
|
if (fl & LEGACY_SOLID) {
|
||||||
|
// Interior of inlet/outlet planes (skip corners at y=0/NY-1)
|
||||||
|
bool interior_y = (y > 0u) && (y < (unsigned int)(NY - 1));
|
||||||
|
if (x == 0 && interior_y) {
|
||||||
|
// Parabolic velocity inlet
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x + 1u, y, z);
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
apply_parabolic_inlet_3d(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else if (x == (unsigned int)(NX - 1) && interior_y) {
|
||||||
|
// Pressure outlet
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x - 1u, y, z);
|
||||||
|
for (int i = 0; i < NQ; i++)
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
apply_pressure_outlet_3d(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Wall nodes and corners: full-way bounce-back.
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
float t = f[i]; f[i] = f[i+1]; f[i+1] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Obstacle nodes: full-way bounce-back (same swap)
|
||||||
|
if (fl & LEGACY_OBSTACLE) {
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
float t = f[i]; f[i] = f[i+1]; f[i+1] = t;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Collision (fluid only) ----
|
||||||
|
if (fl & LEGACY_FLUID) {
|
||||||
|
float feq[NQ], Fin[NQ];
|
||||||
|
compute_feq(rho_n, ux, uy, uz, feq);
|
||||||
|
zero_forcing(Fin);
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
}
|
||||||
|
|
||||||
|
stream_pull_store(k, f, fi_out);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
} // extern "C"
|
||||||
@ -3,23 +3,23 @@
|
|||||||
// cuda parameters
|
// cuda parameters
|
||||||
#define MULT_GPU False
|
#define MULT_GPU False
|
||||||
#define NT 128
|
#define NT 128
|
||||||
#define X_1U 128
|
#define X_1U 256
|
||||||
#define Y_1U 32
|
#define Y_1U 128
|
||||||
#define Z_1U 1
|
#define Z_1U 32
|
||||||
|
|
||||||
// flow parameters
|
// flow parameters
|
||||||
#define LBtype float
|
#define LBtype float
|
||||||
#define UX 10
|
#define UX 1
|
||||||
#define UY 16
|
#define UY 1
|
||||||
#define UZ 1
|
#define UZ 1
|
||||||
#define NX 1280
|
#define NX 256
|
||||||
#define NY 512
|
#define NY 128
|
||||||
#define NZ 1
|
#define NZ 32
|
||||||
#define DIM 2
|
#define DIM 3
|
||||||
#define NQ 9
|
#define NQ 19
|
||||||
#define VIS 0.004
|
#define VIS 0.0096000000
|
||||||
#define RHO 1.0
|
#define RHO 1.0
|
||||||
#define U0 0.01
|
#define U0 0.04
|
||||||
|
|
||||||
// constants
|
// constants
|
||||||
#define PI 3.141592653589793238
|
#define PI 3.141592653589793238
|
||||||
@ -33,5 +33,31 @@
|
|||||||
#define V_TAYLOR 0b00000001
|
#define V_TAYLOR 0b00000001
|
||||||
|
|
||||||
// variables
|
// variables
|
||||||
#define N_OBJS 7
|
#define N_OBJS 0
|
||||||
// #define N_SENS 2
|
// #define N_SENS 2
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// New architecture configuration (Stage 1)
|
||||||
|
// These defaults are safe for backward compatibility.
|
||||||
|
// compiler.py can override any of them via modify_macro().
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// Collision model: 0=SRT, 1=TRT, 2=MRT
|
||||||
|
#ifndef COLLISION_MODEL
|
||||||
|
#define COLLISION_MODEL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Streaming model: 0=double-buffer, 1=esoteric-pull
|
||||||
|
#ifndef STREAMING_MODEL
|
||||||
|
#define STREAMING_MODEL 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Storage precision: 0=FP32, 1=FP16S, 2=FP16C
|
||||||
|
#ifndef STORE_PRECISION
|
||||||
|
#define STORE_PRECISION 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// DDF-shifting: 0=off, 1=on
|
||||||
|
#ifndef USE_DDF_SHIFTING
|
||||||
|
#define USE_DDF_SHIFTING 0
|
||||||
|
#endif
|
||||||
135
src/CelerisLab/lbm/kernels/operators/collision_mrt.cuh
Normal file
135
src/CelerisLab/lbm/kernels/operators/collision_mrt.cuh
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
// CelerisLab – operators/collision_mrt.cuh
|
||||||
|
// MRT (Multiple-Relaxation-Time) collision operator.
|
||||||
|
//
|
||||||
|
// D2Q9 Lallemand-Luo (Phys. Rev. E 61, 2000) with NEW paired ordering:
|
||||||
|
// cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
|
||||||
|
// cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
|
||||||
|
//
|
||||||
|
// Moment space:
|
||||||
|
// m[0] = ρ (conserved, s₀ = 0 or 1.0 overwrite)
|
||||||
|
// m[1] = e (energy, s₁ = s_e)
|
||||||
|
// m[2] = ε (energy², s₂ = s_eps)
|
||||||
|
// m[3] = jx (conserved, s₃ = 0 or 1.0 overwrite)
|
||||||
|
// m[4] = qx (energy flux x, s₄ = s_q)
|
||||||
|
// m[5] = jy (conserved, s₅ = 0 or 1.0 overwrite)
|
||||||
|
// m[6] = qy (energy flux y, s₆ = s_q)
|
||||||
|
// m[7] = pxx (stress, s₇ = s_nu = ω = 1/(3ν + 0.5))
|
||||||
|
// m[8] = pxy (stress, s₈ = s_nu)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_OPERATORS_COLLISION_MRT_CUH
|
||||||
|
#define CELERIS_OPERATORS_COLLISION_MRT_CUH
|
||||||
|
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D2Q9 MRT (fully expanded, no loops, no matrix storage)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ void collide_mrt(float* __restrict__ g,
|
||||||
|
float rho, float ux, float uy,
|
||||||
|
const float* __restrict__ Fin,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
// ----- Relaxation rates -----
|
||||||
|
// s_rho = s_jx = s_jy = 1.0 (conserved moments → overwrite to eq)
|
||||||
|
// s_e = s_eps = s_q = 1.2 (kinetic transport)
|
||||||
|
// s_nu = omega (viscosity-related)
|
||||||
|
const float s_rho = 1.0f; // conserved moment relaxation
|
||||||
|
const float s_e = 1.2f;
|
||||||
|
const float s_eps = 1.2f;
|
||||||
|
const float s_jx = 1.0f;
|
||||||
|
const float s_q = 1.2f;
|
||||||
|
const float s_jy = 1.0f;
|
||||||
|
const float s_nu = omega;
|
||||||
|
|
||||||
|
// ----- Pressure (from density) -----
|
||||||
|
const float p = (g[0]+g[1]+g[2]+g[3]+g[4]+g[5]+g[6]+g[7]+g[8]) / 3.0f;
|
||||||
|
|
||||||
|
// ----- Forward transform: m = M * g (new paired ordering) -----
|
||||||
|
float m[9];
|
||||||
|
m[0] = g[0] + g[1] + g[2] + g[3] + g[4] + g[5] + g[6] + g[7] + g[8];
|
||||||
|
m[1] = -4*g[0] - g[1] - g[2] - g[3] - g[4] + 2*g[5] + 2*g[6] + 2*g[7] + 2*g[8];
|
||||||
|
m[2] = 4*g[0] - 2*g[1] - 2*g[2] - 2*g[3] - 2*g[4] + g[5] + g[6] + g[7] + g[8];
|
||||||
|
m[3] = g[1] - g[2] + g[5] - g[6] + g[7] - g[8];
|
||||||
|
m[4] = -2*g[1] + 2*g[2] + g[5] - g[6] + g[7] - g[8];
|
||||||
|
m[5] = g[3] - g[4] + g[5] - g[6] - g[7] + g[8];
|
||||||
|
m[6] = -2*g[3] + 2*g[4] + g[5] - g[6] - g[7] + g[8];
|
||||||
|
m[7] = g[1] + g[2] - g[3] - g[4];
|
||||||
|
m[8] = g[5] + g[6] - g[7] - g[8];
|
||||||
|
|
||||||
|
// ----- Equilibrium moments -----
|
||||||
|
const float u2 = ux * ux + uy * uy;
|
||||||
|
float meq[9];
|
||||||
|
meq[0] = 3.0f * p; // ρ
|
||||||
|
meq[1] = -6.0f * p + 3.0f * RHO * u2; // e
|
||||||
|
meq[2] = 3.0f * p - 3.0f * RHO * u2; // ε
|
||||||
|
meq[3] = RHO * ux; // jx
|
||||||
|
meq[4] = -RHO * ux; // qx
|
||||||
|
meq[5] = RHO * uy; // jy
|
||||||
|
meq[6] = -RHO * uy; // qy
|
||||||
|
meq[7] = RHO * (ux * ux - uy * uy); // pxx
|
||||||
|
meq[8] = RHO * ux * uy; // pxy
|
||||||
|
|
||||||
|
// ----- Relaxation: delta_m[i] = s_i * (meq[i] - m[i]) -----
|
||||||
|
float dm[9];
|
||||||
|
dm[0] = s_rho * (meq[0] - m[0]);
|
||||||
|
dm[1] = s_e * (meq[1] - m[1]);
|
||||||
|
dm[2] = s_eps * (meq[2] - m[2]);
|
||||||
|
dm[3] = s_jx * (meq[3] - m[3]);
|
||||||
|
dm[4] = s_q * (meq[4] - m[4]);
|
||||||
|
dm[5] = s_jy * (meq[5] - m[5]);
|
||||||
|
dm[6] = s_q * (meq[6] - m[6]);
|
||||||
|
dm[7] = s_nu * (meq[7] - m[7]);
|
||||||
|
dm[8] = s_nu * (meq[8] - m[8]);
|
||||||
|
|
||||||
|
// ----- Inverse transform: g += M⁻¹ * dm (new paired ordering) -----
|
||||||
|
g[0] += ( dm[0] - dm[1] + dm[2] ) / 9.0f;
|
||||||
|
g[1] += (4 * dm[0] - dm[1] - 2* dm[2] + 6* dm[3] - 6* dm[4] + 9*dm[7]) / 36.0f;
|
||||||
|
g[2] += (4 * dm[0] - dm[1] - 2* dm[2] - 6* dm[3] + 6* dm[4] + 9*dm[7]) / 36.0f;
|
||||||
|
g[3] += (4 * dm[0] - dm[1] - 2* dm[2] + 6*dm[5] - 6*dm[6] - 9*dm[7]) / 36.0f;
|
||||||
|
g[4] += (4 * dm[0] - dm[1] - 2* dm[2] - 6*dm[5] + 6*dm[6] - 9*dm[7]) / 36.0f;
|
||||||
|
g[5] += (4 * dm[0] + 2* dm[1] + dm[2] + 6* dm[3] + 3* dm[4] + 6*dm[5] + 3*dm[6] + 9*dm[8]) / 36.0f;
|
||||||
|
g[6] += (4 * dm[0] + 2* dm[1] + dm[2] - 6* dm[3] - 3* dm[4] - 6*dm[5] - 3*dm[6] + 9*dm[8]) / 36.0f;
|
||||||
|
g[7] += (4 * dm[0] + 2* dm[1] + dm[2] + 6* dm[3] + 3* dm[4] - 6*dm[5] - 3*dm[6] - 9*dm[8]) / 36.0f;
|
||||||
|
g[8] += (4 * dm[0] + 2* dm[1] + dm[2] - 6* dm[3] - 3* dm[4] + 6*dm[5] + 3*dm[6] - 9*dm[8]) / 36.0f;
|
||||||
|
|
||||||
|
// ----- Add forcing (if present) -----
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < 9; i++) {
|
||||||
|
g[i] += Fin[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convenience wrapper: no external forcing
|
||||||
|
__device__ __forceinline__ void collide_mrt_no_force(float* __restrict__ g,
|
||||||
|
float rho, float ux, float uy,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
float Fin[9] = {0};
|
||||||
|
collide_mrt(g, rho, ux, uy, Fin, omega);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D3Q19 MRT (placeholder – use SRT or TRT until fully validated)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ void collide_mrt(float* __restrict__ g,
|
||||||
|
float rho, float ux, float uy, float uz,
|
||||||
|
const float* __restrict__ Fin,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
// TODO: implement D3Q19 MRT with 19-moment Gram–Schmidt basis
|
||||||
|
// Fall back to SRT for now
|
||||||
|
float feq[19];
|
||||||
|
compute_feq(rho, ux, uy, uz, feq);
|
||||||
|
const float c_tau = 1.0f - 0.5f * omega;
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < 19; i++) {
|
||||||
|
g[i] = fmaf(1.0f - omega, g[i], fmaf(omega, feq[i], c_tau * Fin[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ
|
||||||
|
|
||||||
|
#endif // CELERIS_OPERATORS_COLLISION_MRT_CUH
|
||||||
35
src/CelerisLab/lbm/kernels/operators/collision_srt.cuh
Normal file
35
src/CelerisLab/lbm/kernels/operators/collision_srt.cuh
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
// CelerisLab – operators/collision_srt.cuh
|
||||||
|
// SRT (BGK) collision operator.
|
||||||
|
// f_out[i] = (1 - ω) f[i] + ω feq[i] + Fin[i]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_OPERATORS_COLLISION_SRT_CUH
|
||||||
|
#define CELERIS_OPERATORS_COLLISION_SRT_CUH
|
||||||
|
|
||||||
|
__device__ __forceinline__ void collide_srt(float* __restrict__ f,
|
||||||
|
const float* __restrict__ feq,
|
||||||
|
const float* __restrict__ Fin,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
// Pre-compute forcing prefactor (1 - ω/2) for Guo scheme
|
||||||
|
const float c_tau = 1.0f - 0.5f * omega;
|
||||||
|
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f[i] = fmaf(1.0f - omega, f[i],
|
||||||
|
fmaf(omega, feq[i], c_tau * Fin[i]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant without forcing (Fin = 0)
|
||||||
|
__device__ __forceinline__ void collide_srt_no_force(float* __restrict__ f,
|
||||||
|
const float* __restrict__ feq,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f[i] = fmaf(1.0f - omega, f[i], omega * feq[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_OPERATORS_COLLISION_SRT_CUH
|
||||||
79
src/CelerisLab/lbm/kernels/operators/collision_trt.cuh
Normal file
79
src/CelerisLab/lbm/kernels/operators/collision_trt.cuh
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
// CelerisLab – operators/collision_trt.cuh
|
||||||
|
// TRT (Two-Relaxation-Time) collision operator.
|
||||||
|
//
|
||||||
|
// Symmetric part uses ω⁺ (= omega, transport viscosity)
|
||||||
|
// Antisymmetric uses ω⁻ (linked by magic parameter Λ = 3/16)
|
||||||
|
// ω⁻ = 1 / (Λ / (1/ω⁺ - 0.5) + 0.5)
|
||||||
|
//
|
||||||
|
// Paired direction ordering makes TRT natural:
|
||||||
|
// f_s[i] = 0.5*(f[i] + f[opp(i)]) symmetric
|
||||||
|
// f_a[i] = 0.5*(f[i] - f[opp(i)]) antisymmetric
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_OPERATORS_COLLISION_TRT_CUH
|
||||||
|
#define CELERIS_OPERATORS_COLLISION_TRT_CUH
|
||||||
|
|
||||||
|
// Magic parameter Λ = 3/16 (optimal for porous-media / bounce-back wall location)
|
||||||
|
#define TRT_MAGIC_PARAM (0.1875f)
|
||||||
|
|
||||||
|
__device__ __forceinline__ float compute_omega_minus(float omega_plus) {
|
||||||
|
return 1.0f / (TRT_MAGIC_PARAM / (1.0f / omega_plus - 0.5f) + 0.5f);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ void collide_trt(float* __restrict__ f,
|
||||||
|
const float* __restrict__ feq,
|
||||||
|
const float* __restrict__ Fin,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
const float wp = omega;
|
||||||
|
const float wm = compute_omega_minus(wp);
|
||||||
|
|
||||||
|
// Direction 0: rest particle – treat as symmetric-only
|
||||||
|
f[0] = f[0] - wp * (f[0] - feq[0]) + Fin[0];
|
||||||
|
|
||||||
|
// Direction pairs (i, i+1) for i = 1, 3, 5, …
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
const int ib = i + 1; // opposite (paired layout)
|
||||||
|
|
||||||
|
// Current and opposite
|
||||||
|
const float fi = f[i];
|
||||||
|
const float fb = f[ib];
|
||||||
|
|
||||||
|
// Equilibrium
|
||||||
|
const float feqi = feq[i];
|
||||||
|
const float feqb = feq[ib];
|
||||||
|
|
||||||
|
// Symmetric / antisymmetric non-equilibrium
|
||||||
|
const float delta_s = 0.5f * ((fi - feqi) + (fb - feqb));
|
||||||
|
const float delta_a = 0.5f * ((fi - feqi) - (fb - feqb));
|
||||||
|
|
||||||
|
// Relax + forcing
|
||||||
|
f[i] = fi - wp * delta_s - wm * delta_a + Fin[i];
|
||||||
|
f[ib] = fb - wp * delta_s + wm * delta_a + Fin[ib];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant without forcing (Fin = 0)
|
||||||
|
__device__ __forceinline__ void collide_trt_no_force(float* __restrict__ f,
|
||||||
|
const float* __restrict__ feq,
|
||||||
|
float omega)
|
||||||
|
{
|
||||||
|
const float wp = omega;
|
||||||
|
const float wm = compute_omega_minus(wp);
|
||||||
|
|
||||||
|
f[0] = f[0] - wp * (f[0] - feq[0]);
|
||||||
|
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
const int ib = i + 1;
|
||||||
|
const float fi = f[i], fb = f[ib];
|
||||||
|
const float feqi = feq[i], feqb = feq[ib];
|
||||||
|
const float delta_s = 0.5f * ((fi - feqi) + (fb - feqb));
|
||||||
|
const float delta_a = 0.5f * ((fi - feqi) - (fb - feqb));
|
||||||
|
f[i] = fi - wp * delta_s - wm * delta_a;
|
||||||
|
f[ib] = fb - wp * delta_s + wm * delta_a;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_OPERATORS_COLLISION_TRT_CUH
|
||||||
102
src/CelerisLab/lbm/kernels/operators/forcing_guo.cuh
Normal file
102
src/CelerisLab/lbm/kernels/operators/forcing_guo.cuh
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// CelerisLab – operators/forcing_guo.cuh
|
||||||
|
// Guo forcing scheme (Guo, Zheng & Shi, Phys. Rev. E 65, 2002).
|
||||||
|
//
|
||||||
|
// Velocity correction: u* = u + F·dt / (2ρ)
|
||||||
|
// Forcing term:
|
||||||
|
// Fin[i] = (1 - ω/2) · w_i · [ (c_i - u*)/cs² + (c_i · u*)/cs⁴ · c_i ] · F
|
||||||
|
// simplified: Fin[i] = 9·w_i · [ (c_i·F)(c_i·u* + 1/3) - u*·F/3 ]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_OPERATORS_FORCING_GUO_CUH
|
||||||
|
#define CELERIS_OPERATORS_FORCING_GUO_CUH
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// D2Q9 Guo forcing terms
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
__device__ __forceinline__ void apply_guo_velocity_correction(
|
||||||
|
float& ux, float& uy,
|
||||||
|
float fx, float fy, float rho)
|
||||||
|
{
|
||||||
|
float rho2 = 0.5f / rho;
|
||||||
|
ux = fmaf(fx, rho2, ux);
|
||||||
|
uy = fmaf(fy, rho2, uy);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_guo_forcing(
|
||||||
|
float ux, float uy, // velocity (already corrected: u*)
|
||||||
|
float fx, float fy, // force density
|
||||||
|
float* __restrict__ Fin)
|
||||||
|
{
|
||||||
|
const float uF = -0.33333334f * fmaf(ux, fx, uy * fy); // -u*·F / 3
|
||||||
|
|
||||||
|
Fin[0] = 9.0f * W0_VAL * uF;
|
||||||
|
|
||||||
|
// Straight directions
|
||||||
|
// cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
|
||||||
|
// cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
|
||||||
|
Fin[1] = 9.0f * WS_VAL * fmaf( fx, ux + 0.33333334f, uF); // +x
|
||||||
|
Fin[2] = 9.0f * WS_VAL * fmaf(-fx, -ux + 0.33333334f, uF); // -x
|
||||||
|
Fin[3] = 9.0f * WS_VAL * fmaf( fy, uy + 0.33333334f, uF); // +y
|
||||||
|
Fin[4] = 9.0f * WS_VAL * fmaf(-fy, -uy + 0.33333334f, uF); // -y
|
||||||
|
|
||||||
|
// Diagonal directions
|
||||||
|
const float f5x = fx, f5y = fy; // +x+y
|
||||||
|
const float c5u = ux + uy;
|
||||||
|
const float c5F = f5x + f5y;
|
||||||
|
Fin[5] = 9.0f * WE_VAL * fmaf(c5F, c5u + 0.33333334f, uF);
|
||||||
|
|
||||||
|
const float c6u = -ux - uy; // -x-y
|
||||||
|
const float c6F = -fx - fy;
|
||||||
|
Fin[6] = 9.0f * WE_VAL * fmaf(c6F, c6u + 0.33333334f, uF);
|
||||||
|
|
||||||
|
const float c7u = ux - uy; // +x-y
|
||||||
|
const float c7F = fx - fy;
|
||||||
|
Fin[7] = 9.0f * WE_VAL * fmaf(c7F, c7u + 0.33333334f, uF);
|
||||||
|
|
||||||
|
const float c8u = -ux + uy; // -x+y
|
||||||
|
const float c8F = -fx + fy;
|
||||||
|
Fin[8] = 9.0f * WE_VAL * fmaf(c8F, c8u + 0.33333334f, uF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
|
||||||
|
__device__ __forceinline__ void apply_guo_velocity_correction(
|
||||||
|
float& ux, float& uy, float& uz,
|
||||||
|
float fx, float fy, float fz, float rho)
|
||||||
|
{
|
||||||
|
float rho2 = 0.5f / rho;
|
||||||
|
ux = fmaf(fx, rho2, ux);
|
||||||
|
uy = fmaf(fy, rho2, uy);
|
||||||
|
uz = fmaf(fz, rho2, uz);
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_guo_forcing(
|
||||||
|
float ux, float uy, float uz,
|
||||||
|
float fx, float fy, float fz,
|
||||||
|
float* __restrict__ Fin)
|
||||||
|
{
|
||||||
|
const float uF = -0.33333334f * fmaf(ux, fx, fmaf(uy, fy, uz * fz));
|
||||||
|
|
||||||
|
Fin[0] = 9.0f * W0_VAL * uF;
|
||||||
|
|
||||||
|
// Generic loop for D3Q19 (compiler will fully unroll)
|
||||||
|
for (int i = 1; i < 19; i++) {
|
||||||
|
float ci_dot_F = d_cx[i]*fx + d_cy[i]*fy + d_cz[i]*fz;
|
||||||
|
float ci_dot_u = d_cx[i]*ux + d_cy[i]*uy + d_cz[i]*uz;
|
||||||
|
Fin[i] = 9.0f * d_w[i] * fmaf(ci_dot_F, ci_dot_u + 0.33333334f, uF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Zero forcing helper (when no external force)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ void zero_forcing(float* __restrict__ Fin) {
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < NQ; i++) Fin[i] = 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_OPERATORS_FORCING_GUO_CUH
|
||||||
168
src/CelerisLab/lbm/kernels/operators/macro.cuh
Normal file
168
src/CelerisLab/lbm/kernels/operators/macro.cuh
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
// CelerisLab – operators/macro.cuh
|
||||||
|
// Macroscopic quantity computation (rho, u) and equilibrium distribution (feq).
|
||||||
|
// Supports DDF-shifting (USE_DDF_SHIFTING=1) and standard mode (=0).
|
||||||
|
//
|
||||||
|
// D2Q9 paired ordering:
|
||||||
|
// cx = {0, 1,-1, 0, 0, 1,-1, 1,-1}
|
||||||
|
// cy = {0, 0, 0, 1,-1, 1,-1,-1, 1}
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_OPERATORS_MACRO_CUH
|
||||||
|
#define CELERIS_OPERATORS_MACRO_CUH
|
||||||
|
|
||||||
|
#ifndef USE_DDF_SHIFTING
|
||||||
|
#define USE_DDF_SHIFTING 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// compute_rho_u: f[NQ] → (rho, ux, uy [, uz])
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_rho_u(const float* __restrict__ f,
|
||||||
|
float& rho,
|
||||||
|
float& ux, float& uy)
|
||||||
|
{
|
||||||
|
// Density: sum of all populations
|
||||||
|
rho = f[0] + f[1] + f[2] + f[3] + f[4] + f[5] + f[6] + f[7] + f[8];
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
rho += 1.0f; // DDF-shifting: stored f_tilde = f - w, sum(w) = 1
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// Momentum (works identically with or without DDF-shifting,
|
||||||
|
// because sum(w_i * cx_i) = 0)
|
||||||
|
float inv_rho = 1.0f / rho;
|
||||||
|
// cx: 0 1 -1 0 0 1 -1 1 -1
|
||||||
|
ux = (f[1] - f[2] + f[5] - f[6] + f[7] - f[8]) * inv_rho;
|
||||||
|
// cy: 0 0 0 1 -1 1 -1 -1 1
|
||||||
|
uy = (f[3] - f[4] + f[5] - f[6] - f[7] + f[8]) * inv_rho;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_rho_u(const float* __restrict__ f,
|
||||||
|
float& rho,
|
||||||
|
float& ux, float& uy, float& uz)
|
||||||
|
{
|
||||||
|
rho = f[0];
|
||||||
|
for (int i = 1; i < 19; i++) rho += f[i];
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
rho += 1.0f;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
float inv_rho = 1.0f / rho;
|
||||||
|
ux = (f[1]-f[2] + f[7]-f[8] + f[9]-f[10] + f[13]-f[14] + f[15]-f[16]) * inv_rho;
|
||||||
|
uy = (f[3]-f[4] + f[7]-f[8] + f[11]-f[12] + f[14]-f[13] + f[17]-f[18]) * inv_rho;
|
||||||
|
uz = (f[5]-f[6] + f[9]-f[10] + f[11]-f[12] + f[16]-f[15] + f[18]-f[17]) * inv_rho;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// compute_feq: (rho, u) → feq[NQ]
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
#if NQ == 9
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_feq(float rho, float ux, float uy,
|
||||||
|
float* __restrict__ feq)
|
||||||
|
{
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
const float rhom1 = rho - 1.0f; // perturbation density
|
||||||
|
#endif
|
||||||
|
const float ux3 = 3.0f * ux;
|
||||||
|
const float uy3 = 3.0f * uy;
|
||||||
|
const float c3 = -1.5f * (ux * ux + uy * uy); // = -|u|²/(2cs²)
|
||||||
|
|
||||||
|
// Helper: feq_i = w_i * rho * (1 + 3 ci·u + 4.5 (ci·u)² - 1.5 u²)
|
||||||
|
// With DDF-shifting: feq_tilde_i = feq_i - w_i
|
||||||
|
// = w_i * (rho * (1 + ...) - 1) = w_i * (rho*(...) + rho - 1)
|
||||||
|
|
||||||
|
#define FEQ_CORE(cu) (rho * fmaf(0.5f * 3.0f, (cu) * (cu), (cu) + c3))
|
||||||
|
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
#define FEQ(w, cu) ((w) * fmaf(rho, fmaf(0.5f, (cu)*(cu), (cu) + c3), rhom1))
|
||||||
|
#else
|
||||||
|
#define FEQ(w, cu) ((w) * rho * (1.0f + (cu) + 0.5f * (cu) * (cu) + c3))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// i=0: rest
|
||||||
|
feq[0] = FEQ(W0_VAL, 0.0f);
|
||||||
|
|
||||||
|
// (1,2) ±x
|
||||||
|
feq[1] = FEQ(WS_VAL, ux3);
|
||||||
|
feq[2] = FEQ(WS_VAL, -ux3);
|
||||||
|
|
||||||
|
// (3,4) ±y
|
||||||
|
feq[3] = FEQ(WS_VAL, uy3);
|
||||||
|
feq[4] = FEQ(WS_VAL, -uy3);
|
||||||
|
|
||||||
|
// Pre-compute diagonal velocity combos
|
||||||
|
const float u0 = ux3 + uy3; // +x+y
|
||||||
|
const float u1 = ux3 - uy3; // +x-y
|
||||||
|
|
||||||
|
// (5,6) ±(x+y)
|
||||||
|
feq[5] = FEQ(WE_VAL, u0);
|
||||||
|
feq[6] = FEQ(WE_VAL, -u0);
|
||||||
|
|
||||||
|
// (7,8) ±(x-y)
|
||||||
|
feq[7] = FEQ(WE_VAL, u1);
|
||||||
|
feq[8] = FEQ(WE_VAL, -u1);
|
||||||
|
|
||||||
|
#undef FEQ
|
||||||
|
#undef FEQ_CORE
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
|
||||||
|
__device__ __forceinline__ void compute_feq(float rho, float ux, float uy, float uz,
|
||||||
|
float* __restrict__ feq)
|
||||||
|
{
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
const float rhom1 = rho - 1.0f;
|
||||||
|
#endif
|
||||||
|
const float ux3 = 3.0f * ux;
|
||||||
|
const float uy3 = 3.0f * uy;
|
||||||
|
const float uz3 = 3.0f * uz;
|
||||||
|
const float c3 = -1.5f * (ux*ux + uy*uy + uz*uz);
|
||||||
|
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
#define FEQ(w, cu) ((w) * fmaf(rho, fmaf(0.5f, (cu)*(cu), (cu) + c3), rhom1))
|
||||||
|
#else
|
||||||
|
#define FEQ(w, cu) ((w) * rho * (1.0f + (cu) + 0.5f * (cu) * (cu) + c3))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
feq[0] = FEQ(W0_VAL, 0.0f);
|
||||||
|
|
||||||
|
// Straight
|
||||||
|
feq[1] = FEQ(WS_VAL, ux3); feq[2] = FEQ(WS_VAL, -ux3);
|
||||||
|
feq[3] = FEQ(WS_VAL, uy3); feq[4] = FEQ(WS_VAL, -uy3);
|
||||||
|
feq[5] = FEQ(WS_VAL, uz3); feq[6] = FEQ(WS_VAL, -uz3);
|
||||||
|
|
||||||
|
// Diagonal
|
||||||
|
const float u0 = ux3+uy3, u1 = ux3+uz3, u2 = uy3+uz3;
|
||||||
|
const float u3 = ux3-uy3, u4 = ux3-uz3, u5 = uy3-uz3;
|
||||||
|
|
||||||
|
feq[7] = FEQ(WE_VAL, u0); feq[8] = FEQ(WE_VAL, -u0);
|
||||||
|
feq[9] = FEQ(WE_VAL, u1); feq[10] = FEQ(WE_VAL, -u1);
|
||||||
|
feq[11] = FEQ(WE_VAL, u2); feq[12] = FEQ(WE_VAL, -u2);
|
||||||
|
feq[13] = FEQ(WE_VAL, u3); feq[14] = FEQ(WE_VAL, -u3);
|
||||||
|
feq[15] = FEQ(WE_VAL, u4); feq[16] = FEQ(WE_VAL, -u4);
|
||||||
|
feq[17] = FEQ(WE_VAL, u5); feq[18] = FEQ(WE_VAL, -u5);
|
||||||
|
|
||||||
|
#undef FEQ
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // NQ
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// compute_pressure (diagnostic, from state equation p = cs² ρ)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ float compute_pressure(float rho) {
|
||||||
|
return CS2 * rho;
|
||||||
|
}
|
||||||
|
|
||||||
|
__device__ __forceinline__ float compute_pressure_perturbation(float rho, float rho_ref) {
|
||||||
|
return CS2 * (rho - rho_ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_OPERATORS_MACRO_CUH
|
||||||
332
src/CelerisLab/lbm/kernels/step/one_step_double.cu
Normal file
332
src/CelerisLab/lbm/kernels/step/one_step_double.cu
Normal file
@ -0,0 +1,332 @@
|
|||||||
|
// CelerisLab – step/one_step_double.cu
|
||||||
|
// Main LBM step kernel using double-buffer PULL streaming.
|
||||||
|
//
|
||||||
|
// Workflow per node:
|
||||||
|
// 1. Pull-stream: gather DDF from neighbors → local f[NQ]
|
||||||
|
// 2. Compute macroscopic quantities (ρ, u)
|
||||||
|
// 3. Apply boundary conditions (inlet/outlet/wall)
|
||||||
|
// 4. Compute equilibrium + forcing
|
||||||
|
// 5. Collision (SRT / TRT / MRT)
|
||||||
|
// 6. Write to output buffer
|
||||||
|
//
|
||||||
|
// Kernel signature maintains backward compatibility with driver.py:
|
||||||
|
// flag* – cell flags (uint8 for legacy, uint32 for new)
|
||||||
|
// fi_in* – input DDF array (fpxx)
|
||||||
|
// fi_out* – output DDF array (fpxx)
|
||||||
|
// indx* – per-cell offset into delta pool
|
||||||
|
// delta* – curved boundary parameter pool
|
||||||
|
// action* – per-object control input
|
||||||
|
// obs* – per-object observation output (force / velocity)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_STEP_ONE_STEP_DOUBLE_CU
|
||||||
|
#define CELERIS_STEP_ONE_STEP_DOUBLE_CU
|
||||||
|
|
||||||
|
__global__ void StreamCollideDouble(
|
||||||
|
const uint8_t* __restrict__ flag,
|
||||||
|
const fpxx* __restrict__ fi_in,
|
||||||
|
fpxx* __restrict__ fi_out,
|
||||||
|
const int32_t* __restrict__ indx,
|
||||||
|
const float* __restrict__ delta,
|
||||||
|
const float* __restrict__ action,
|
||||||
|
float* __restrict__ obs,
|
||||||
|
float* __restrict__ rho_arr, // macro output (can be NULL)
|
||||||
|
float* __restrict__ u_arr) // macro output (can be NULL)
|
||||||
|
{
|
||||||
|
// ----- Thread → node mapping (2D) -----
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Read flag -----
|
||||||
|
uint8_t fl = flag[k];
|
||||||
|
|
||||||
|
// ----- Neighbor indices -----
|
||||||
|
unsigned long j[NQ];
|
||||||
|
compute_neighbors(k, j);
|
||||||
|
|
||||||
|
// ----- Pull-stream: load DDF from neighbors -----
|
||||||
|
float f[NQ];
|
||||||
|
stream_pull_load(k, f, fi_in, j);
|
||||||
|
|
||||||
|
// ----- Compute macroscopic quantities -----
|
||||||
|
float rho_n, ux, uy;
|
||||||
|
#if NQ == 9
|
||||||
|
compute_rho_u(f, rho_n, ux, uy);
|
||||||
|
#elif NQ == 19
|
||||||
|
float uz;
|
||||||
|
compute_rho_u(f, rho_n, ux, uy, uz);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Boundary conditions (inlet/outlet/wall) -----
|
||||||
|
#if NQ == 9
|
||||||
|
if (fl & LEGACY_SOLID) {
|
||||||
|
if (x == 0) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x + 1u, y);
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
}
|
||||||
|
apply_parabolic_inlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else if (x == NX - 1) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x - 1u, y);
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
}
|
||||||
|
apply_pressure_outlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif NQ == 19
|
||||||
|
if (fl & LEGACY_SOLID) {
|
||||||
|
if (x == 0) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x + 1u, y, z);
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
}
|
||||||
|
apply_parabolic_inlet_3d(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else if (x == NX - 1) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x - 1u, y, z);
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f_neb[i] = load_ddf(fi_in, index_f(k_neb, (unsigned int)i));
|
||||||
|
}
|
||||||
|
apply_pressure_outlet_3d(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Wall bounce-back (top/bottom walls) -----
|
||||||
|
#if NQ == 9
|
||||||
|
if (y == 1 || y == NY - 2) {
|
||||||
|
apply_wall_bb_d2q9(y, f);
|
||||||
|
}
|
||||||
|
#elif NQ == 19
|
||||||
|
if (y == 1 || y == NY - 2) {
|
||||||
|
apply_wall_bb_d3q19_y(y, f);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Compute equilibrium -----
|
||||||
|
if (fl & LEGACY_FLUID) {
|
||||||
|
float feq[NQ];
|
||||||
|
float Fin[NQ];
|
||||||
|
|
||||||
|
#if NQ == 9
|
||||||
|
compute_feq(rho_n, ux, uy, feq);
|
||||||
|
zero_forcing(Fin);
|
||||||
|
|
||||||
|
// ----- Collision -----
|
||||||
|
#if COLLISION_MODEL == 0
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 1
|
||||||
|
collide_trt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 2
|
||||||
|
collide_mrt(f, rho_n, ux, uy, Fin, d_params.omega);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
compute_feq(rho_n, ux, uy, uz, feq);
|
||||||
|
zero_forcing(Fin);
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Write to output buffer -----
|
||||||
|
stream_pull_store(k, f, fi_out);
|
||||||
|
|
||||||
|
// ----- Write macroscopic fields (optional) -----
|
||||||
|
if (rho_arr != nullptr) {
|
||||||
|
rho_arr[k] = rho_n;
|
||||||
|
}
|
||||||
|
if (u_arr != nullptr) {
|
||||||
|
#if DIM == 2
|
||||||
|
u_arr[k] = ux;
|
||||||
|
u_arr[TOTAL_CELLS + k] = uy;
|
||||||
|
#elif DIM == 3
|
||||||
|
u_arr[k] = ux;
|
||||||
|
u_arr[TOTAL_CELLS + k] = uy;
|
||||||
|
u_arr[2 * TOTAL_CELLS + k] = uz;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Sensor observation -----
|
||||||
|
if (fl & LEGACY_SENSOR) {
|
||||||
|
int id_obj = indx[k];
|
||||||
|
atomicAdd(&obs[DIM * id_obj], ux);
|
||||||
|
atomicAdd(&obs[DIM * id_obj + 1], uy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// Curved boundary post-processing kernel (separate launch)
|
||||||
|
// Processes SOLID+INTERFACE nodes → modifies fi_out at neighbors.
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void CurvedBoundaryPost(
|
||||||
|
const uint8_t* __restrict__ flag,
|
||||||
|
fpxx* __restrict__ fi_out,
|
||||||
|
const int32_t* __restrict__ indx,
|
||||||
|
const float* __restrict__ delta,
|
||||||
|
const float* __restrict__ action,
|
||||||
|
float* __restrict__ obs)
|
||||||
|
{
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t fl = flag[k];
|
||||||
|
|
||||||
|
if ((fl & LEGACY_SOLID) && (fl & LEGACY_INTERFACE)) {
|
||||||
|
#if DIM == 2
|
||||||
|
int id_off = indx[k];
|
||||||
|
int id_obj = *reinterpret_cast<const int*>(&delta[id_off]);
|
||||||
|
|
||||||
|
// Wall velocity from action + normal
|
||||||
|
float Uw = action[id_obj] * delta[id_off + NQ];
|
||||||
|
float Vw = action[id_obj] * delta[id_off + NQ + 1];
|
||||||
|
|
||||||
|
float* obs_fx = (obs != nullptr) ? &obs[DIM * id_obj] : nullptr;
|
||||||
|
float* obs_fy = (obs != nullptr) ? &obs[DIM * id_obj + 1] : nullptr;
|
||||||
|
|
||||||
|
apply_curved_boundary(k, x, y, fi_out, delta, id_off, Uw, Vw, obs_fx, obs_fy);
|
||||||
|
#endif // DIM == 2 curved BC
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// InitTubeFlow: Initialize parabolic tube flow (backward compatible)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void InitTubeFlow(uint8_t* flag, fpxx* fi)
|
||||||
|
{
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
|
||||||
|
float u_init = U0 * 1.5f * (1.0f - 4.0f * ((float)y - 0.5f * (NY - 1))
|
||||||
|
* ((float)y - 0.5f * (NY - 1))
|
||||||
|
/ ((float)(NY - 2) * (float)(NY - 2)));
|
||||||
|
|
||||||
|
if (y == 0 || y == NY - 1 || x == 0 || x == NX - 1) {
|
||||||
|
flag[k] = LEGACY_SOLID;
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), 0.0f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flag[k] = (uint8_t)LEGACY_FLUID;
|
||||||
|
// feq with rho=RHO, u=(u_init, 0)
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
float cu = (float)d_cx[i] * u_init; // cy=0 for parabolic flow
|
||||||
|
float val = d_w[i] * RHO * (1.0f + 3.0f * cu + 4.5f * cu * cu - 1.5f * u_init * u_init);
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
val -= d_w[i]; // store f_tilde = f - w
|
||||||
|
#endif
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
|
||||||
|
float u_init = U0 * 1.5f * (1.0f - 4.0f * ((float)y - 0.5f * (NY - 1))
|
||||||
|
* ((float)y - 0.5f * (NY - 1))
|
||||||
|
/ ((float)(NY - 2) * (float)(NY - 2)));
|
||||||
|
|
||||||
|
// Walls: y=0, y=NY-1, x=0, x=NX-1; z is periodic
|
||||||
|
if (y == 0 || y == NY - 1 || x == 0 || x == NX - 1) {
|
||||||
|
flag[k] = LEGACY_SOLID;
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), 0.0f);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
flag[k] = (uint8_t)LEGACY_FLUID;
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
float cu = (float)d_cx[i] * u_init;
|
||||||
|
float val = d_w[i] * RHO * (1.0f + 3.0f * cu + 4.5f * cu * cu - 1.5f * u_init * u_init);
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
val -= d_w[i];
|
||||||
|
#endif
|
||||||
|
store_ddf(fi, index_f(k, (unsigned int)i), val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// UpdateMacro: Recompute rho/u from DDF (for diagnostics/output)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void UpdateMacro(
|
||||||
|
const fpxx* __restrict__ fi,
|
||||||
|
float* __restrict__ rho_arr,
|
||||||
|
float* __restrict__ u_arr,
|
||||||
|
const uint8_t* __restrict__ flag)
|
||||||
|
{
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
|
||||||
|
if (flag[k] & LEGACY_SOLID) return;
|
||||||
|
|
||||||
|
unsigned long j[NQ];
|
||||||
|
compute_neighbors(k, j);
|
||||||
|
|
||||||
|
float f[NQ];
|
||||||
|
// For double-buffer, just read from the current buffer (no streaming)
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f[i] = load_ddf(fi, index_f(k, (unsigned int)i));
|
||||||
|
}
|
||||||
|
|
||||||
|
float rho_n, ux, uy;
|
||||||
|
compute_rho_u(f, rho_n, ux, uy);
|
||||||
|
|
||||||
|
rho_arr[k] = rho_n;
|
||||||
|
u_arr[k] = ux;
|
||||||
|
u_arr[TOTAL_CELLS + k] = uy;
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
|
||||||
|
if (flag[k] & LEGACY_SOLID) return;
|
||||||
|
|
||||||
|
float f[NQ];
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
f[i] = load_ddf(fi, index_f(k, (unsigned int)i));
|
||||||
|
}
|
||||||
|
|
||||||
|
float rho_n, ux, uy, uz;
|
||||||
|
compute_rho_u(f, rho_n, ux, uy, uz);
|
||||||
|
|
||||||
|
rho_arr[k] = rho_n;
|
||||||
|
u_arr[k] = ux;
|
||||||
|
u_arr[TOTAL_CELLS + k] = uy;
|
||||||
|
u_arr[2 * TOTAL_CELLS + k] = uz;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_STEP_ONE_STEP_DOUBLE_CU
|
||||||
198
src/CelerisLab/lbm/kernels/step/one_step_esopull.cu
Normal file
198
src/CelerisLab/lbm/kernels/step/one_step_esopull.cu
Normal file
@ -0,0 +1,198 @@
|
|||||||
|
// CelerisLab – step/one_step_esopull.cu
|
||||||
|
// Main LBM step kernel using Esoteric-Pull single-buffer streaming.
|
||||||
|
//
|
||||||
|
// Workflow per node:
|
||||||
|
// 1. load_f_esopull: read DDF (streaming second half)
|
||||||
|
// 2. Compute macroscopic quantities (ρ, u)
|
||||||
|
// 3. Boundary conditions
|
||||||
|
// 4. Equilibrium + forcing + collision
|
||||||
|
// 5. store_f_esopull: write DDF (streaming first half)
|
||||||
|
//
|
||||||
|
// Compared to double-buffer:
|
||||||
|
// - Uses HALF the memory (single fi array)
|
||||||
|
// - Timestep counter t is required
|
||||||
|
// - fi_alt pointer is unused (can be NULL)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_STEP_ONE_STEP_ESOPULL_CU
|
||||||
|
#define CELERIS_STEP_ONE_STEP_ESOPULL_CU
|
||||||
|
|
||||||
|
__global__ void StreamCollideEsoPull(
|
||||||
|
fpxx* __restrict__ fi, // single DDF buffer (read/write)
|
||||||
|
const uint8_t* __restrict__ flag,
|
||||||
|
const int32_t* __restrict__ indx,
|
||||||
|
const float* __restrict__ delta,
|
||||||
|
const float* __restrict__ action,
|
||||||
|
float* __restrict__ obs,
|
||||||
|
float* __restrict__ rho_arr,
|
||||||
|
float* __restrict__ u_arr,
|
||||||
|
const float* __restrict__ force_field, // Euler force field [DIM*N], or NULL
|
||||||
|
unsigned long t) // current timestep
|
||||||
|
{
|
||||||
|
// ----- Thread → node mapping -----
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
#elif DIM == 3
|
||||||
|
unsigned int x, y, z;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, z, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY || z >= (unsigned int)NZ) return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
uint8_t fl = flag[k];
|
||||||
|
|
||||||
|
// Skip pure solid / gas
|
||||||
|
if ((fl & LEGACY_SOLID) && !(fl & LEGACY_INTERFACE) && !(fl & LEGACY_SENSOR)) {
|
||||||
|
// For inlet/outlet solid nodes, we still process below
|
||||||
|
if (x != 0 && x != (unsigned int)(NX - 1)) return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Neighbor indices -----
|
||||||
|
unsigned long j[NQ];
|
||||||
|
compute_neighbors(k, j);
|
||||||
|
|
||||||
|
// ----- Esoteric-Pull: load DDF (streaming second half) -----
|
||||||
|
float f[NQ];
|
||||||
|
load_f_esopull(k, f, fi, j, t);
|
||||||
|
|
||||||
|
// ----- Compute macroscopic quantities -----
|
||||||
|
float rho_n, ux, uy;
|
||||||
|
#if NQ == 9
|
||||||
|
compute_rho_u(f, rho_n, ux, uy);
|
||||||
|
#elif NQ == 19
|
||||||
|
float uz;
|
||||||
|
compute_rho_u(f, rho_n, ux, uy, uz);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Boundary conditions -----
|
||||||
|
#if NQ == 9
|
||||||
|
if (fl & LEGACY_SOLID) {
|
||||||
|
if (x == 0) {
|
||||||
|
float f_neb[NQ];
|
||||||
|
unsigned long k_neb = linear_index(x + 1u, y);
|
||||||
|
load_f_esopull(k_neb, f_neb, fi, j, t); // approximate: reuse j
|
||||||
|
// More accurate: compute j_neb separately
|
||||||
|
unsigned long j_neb[NQ];
|
||||||
|
compute_neighbors(k_neb, j_neb);
|
||||||
|
load_f_esopull(k_neb, f_neb, fi, j_neb, t);
|
||||||
|
apply_parabolic_inlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
else if (x == (unsigned int)(NX - 1)) {
|
||||||
|
unsigned long k_neb = linear_index(x - 1u, y);
|
||||||
|
unsigned long j_neb[NQ];
|
||||||
|
compute_neighbors(k_neb, j_neb);
|
||||||
|
float f_neb[NQ];
|
||||||
|
load_f_esopull(k_neb, f_neb, fi, j_neb, t);
|
||||||
|
apply_pressure_outlet(f, f_neb, (float)y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (y == 1 || y == (unsigned int)(NY - 2)) {
|
||||||
|
apply_wall_bb_d2q9(y, f);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// ----- Forcing -----
|
||||||
|
float Fin[NQ];
|
||||||
|
float fxn = d_params.fx, fyn = d_params.fy;
|
||||||
|
|
||||||
|
if (force_field != nullptr) {
|
||||||
|
fxn += force_field[k];
|
||||||
|
fyn += force_field[TOTAL_CELLS + k];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fxn != 0.0f || fyn != 0.0f) {
|
||||||
|
#if NQ == 9
|
||||||
|
apply_guo_velocity_correction(ux, uy, fxn, fyn, rho_n);
|
||||||
|
compute_guo_forcing(ux, uy, fxn, fyn, Fin);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
zero_forcing(Fin);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Clamp velocity for stability -----
|
||||||
|
const float CS_LIMIT = 0.57735027f; // 1/sqrt(3)
|
||||||
|
ux = fminf(fmaxf(ux, -CS_LIMIT), CS_LIMIT);
|
||||||
|
uy = fminf(fmaxf(uy, -CS_LIMIT), CS_LIMIT);
|
||||||
|
|
||||||
|
// ----- Collision -----
|
||||||
|
if (fl & LEGACY_FLUID) {
|
||||||
|
float feq[NQ];
|
||||||
|
#if NQ == 9
|
||||||
|
compute_feq(rho_n, ux, uy, feq);
|
||||||
|
|
||||||
|
#if COLLISION_MODEL == 0
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 1
|
||||||
|
collide_trt(f, feq, Fin, d_params.omega);
|
||||||
|
#elif COLLISION_MODEL == 2
|
||||||
|
collide_mrt(f, rho_n, ux, uy, Fin, d_params.omega);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#elif NQ == 19
|
||||||
|
float fzn = d_params.fz;
|
||||||
|
if (force_field != nullptr) fzn += force_field[2*TOTAL_CELLS + k];
|
||||||
|
apply_guo_velocity_correction(ux, uy, uz, fxn, fyn, fzn, rho_n);
|
||||||
|
compute_feq(rho_n, ux, uy, uz, feq);
|
||||||
|
collide_srt(f, feq, Fin, d_params.omega);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Esoteric-Pull: write DDF (streaming first half) -----
|
||||||
|
store_f_esopull(k, f, fi, j, t);
|
||||||
|
|
||||||
|
// ----- Write macroscopic fields -----
|
||||||
|
if (rho_arr != nullptr) rho_arr[k] = rho_n;
|
||||||
|
if (u_arr != nullptr) {
|
||||||
|
u_arr[k] = ux;
|
||||||
|
u_arr[TOTAL_CELLS + k] = uy;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----- Sensor observation -----
|
||||||
|
if (fl & LEGACY_SENSOR) {
|
||||||
|
int id_obj = indx[k];
|
||||||
|
atomicAdd(&obs[DIM * id_obj], ux);
|
||||||
|
atomicAdd(&obs[DIM * id_obj + 1], uy);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// InitializeEsoPull: Initialize DDF via Esoteric-Pull store (t=1)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__global__ void InitializeEsoPull(uint8_t* flag, fpxx* fi)
|
||||||
|
{
|
||||||
|
#if DIM == 2
|
||||||
|
unsigned int x, y;
|
||||||
|
unsigned long k;
|
||||||
|
index_from_thread(x, y, k);
|
||||||
|
if (x >= (unsigned int)NX || y >= (unsigned int)NY) return;
|
||||||
|
|
||||||
|
unsigned long j[NQ];
|
||||||
|
compute_neighbors(k, j);
|
||||||
|
|
||||||
|
float feq[NQ];
|
||||||
|
if (y == 0 || y == NY - 1 || x == 0 || x == NX - 1) {
|
||||||
|
flag[k] = LEGACY_SOLID;
|
||||||
|
for (int i = 0; i < NQ; i++) feq[i] = 0.0f;
|
||||||
|
} else {
|
||||||
|
flag[k] = (uint8_t)LEGACY_FLUID;
|
||||||
|
float u_init = U0 * 1.5f * (1.0f - 4.0f * ((float)y - 0.5f*(NY-1))
|
||||||
|
* ((float)y - 0.5f*(NY-1))
|
||||||
|
/ ((float)(NY-2) * (float)(NY-2)));
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
float cu = (float)d_cx[i] * u_init;
|
||||||
|
feq[i] = d_w[i] * RHO * (1.0f + 3.0f*cu + 4.5f*cu*cu - 1.5f*u_init*u_init);
|
||||||
|
#if USE_DDF_SHIFTING
|
||||||
|
feq[i] -= d_w[i];
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Write via Esoteric-Pull store at t=1 (odd step) for correct alignment
|
||||||
|
store_f_esopull(k, feq, fi, j, 1ul);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_STEP_ONE_STEP_ESOPULL_CU
|
||||||
@ -0,0 +1,79 @@
|
|||||||
|
// CelerisLab – streaming/esopull_single_buffer.cuh
|
||||||
|
// Esoteric-Pull single-buffer streaming (Geier & Schönherr, 2017).
|
||||||
|
//
|
||||||
|
// Uses ONE DDF array (half the memory of double-buffer).
|
||||||
|
// Relies on paired direction ordering: (i, i+1) are opposites.
|
||||||
|
//
|
||||||
|
// Key idea: load_f and store_f alternate read/write positions
|
||||||
|
// based on timestep parity, so they never conflict in a single step.
|
||||||
|
//
|
||||||
|
// Even step (t%2 == 0):
|
||||||
|
// Read: f[i] ← fi[n, i+1] (local, reverse slot)
|
||||||
|
// f[i+1] ← fi[j[i], i ] (neighbor, forward slot)
|
||||||
|
// Write: fi[j[i], i+1] ← f[i] (neighbor, reverse slot)
|
||||||
|
// fi[n, i ] ← f[i+1] (local, forward slot)
|
||||||
|
//
|
||||||
|
// Odd step (t%2 == 1):
|
||||||
|
// Read: f[i] ← fi[n, i ] (local, forward slot)
|
||||||
|
// f[i+1] ← fi[j[i], i+1] (neighbor, reverse slot)
|
||||||
|
// Write: fi[j[i], i+1] ← f[i] (neighbor, forward slot → stored in reverse)
|
||||||
|
// fi[n, i ] ← f[i+1] (local, reverse slot → stored in forward)
|
||||||
|
//
|
||||||
|
// Wait, let me use the exact FluidX3D convention:
|
||||||
|
// Odd step read: f[i] ← fi[n, i], f[i+1] ← fi[j[i], i+1]
|
||||||
|
// Odd step write: fi[j[i], i+1] ← f[i], fi[n, i] ← f[i+1]
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_STREAMING_ESOPULL_SINGLE_BUFFER_CUH
|
||||||
|
#define CELERIS_STREAMING_ESOPULL_SINGLE_BUFFER_CUH
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// load_f_esopull: Esoteric-Pull read phase (streaming second half)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void load_f_esopull(unsigned long n,
|
||||||
|
float* __restrict__ f,
|
||||||
|
const fpxx* __restrict__ fi,
|
||||||
|
const unsigned long* __restrict__ j,
|
||||||
|
unsigned long t)
|
||||||
|
{
|
||||||
|
// i=0: rest particle always from self
|
||||||
|
f[0] = load_ddf(fi, index_f(n, 0u));
|
||||||
|
|
||||||
|
for (unsigned int i = 1; i < NQ; i += 2) {
|
||||||
|
if (t & 1) {
|
||||||
|
// Odd step
|
||||||
|
f[i] = load_ddf(fi, index_f(n, i));
|
||||||
|
f[i + 1] = load_ddf(fi, index_f(j[i], i + 1));
|
||||||
|
} else {
|
||||||
|
// Even step
|
||||||
|
f[i] = load_ddf(fi, index_f(n, i + 1));
|
||||||
|
f[i + 1] = load_ddf(fi, index_f(j[i], i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// store_f_esopull: Esoteric-Pull write phase (streaming first half)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ inline void store_f_esopull(unsigned long n,
|
||||||
|
const float* __restrict__ f,
|
||||||
|
fpxx* __restrict__ fi,
|
||||||
|
const unsigned long* __restrict__ j,
|
||||||
|
unsigned long t)
|
||||||
|
{
|
||||||
|
store_ddf(fi, index_f(n, 0u), f[0]);
|
||||||
|
|
||||||
|
for (unsigned int i = 1; i < NQ; i += 2) {
|
||||||
|
if (t & 1) {
|
||||||
|
// Odd step
|
||||||
|
store_ddf(fi, index_f(j[i], i + 1), f[i]);
|
||||||
|
store_ddf(fi, index_f(n, i), f[i + 1]);
|
||||||
|
} else {
|
||||||
|
// Even step
|
||||||
|
store_ddf(fi, index_f(j[i], i), f[i]);
|
||||||
|
store_ddf(fi, index_f(n, i + 1), f[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_STREAMING_ESOPULL_SINGLE_BUFFER_CUH
|
||||||
56
src/CelerisLab/lbm/kernels/streaming/pull_double_buffer.cuh
Normal file
56
src/CelerisLab/lbm/kernels/streaming/pull_double_buffer.cuh
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
// CelerisLab – streaming/pull_double_buffer.cuh
|
||||||
|
// Double-buffer PULL streaming.
|
||||||
|
// f_local[i] = f_in[ neighbor(n, -c_i), i ]
|
||||||
|
// f_out[n, i] = f_collided[i] (after collision, written to second buffer)
|
||||||
|
//
|
||||||
|
// This replaces the old PUSH scheme where each thread writes to neighbors.
|
||||||
|
// Pull streaming provides better memory coalescing on GPU.
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
#ifndef CELERIS_STREAMING_PULL_DOUBLE_BUFFER_CUH
|
||||||
|
#define CELERIS_STREAMING_PULL_DOUBLE_BUFFER_CUH
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// stream_pull_load: gather DDF from neighboring nodes into local f[NQ]
|
||||||
|
// n – current node
|
||||||
|
// f – output: local distribution (compute precision, float)
|
||||||
|
// fi – input: source DDF array (storage precision, fpxx)
|
||||||
|
// j – neighbor indices (j[i] = neighbor in direction i)
|
||||||
|
//
|
||||||
|
// Pull semantic: f[i] at node n = fi[ j[opp(i)], i ]
|
||||||
|
// i.e. the population traveling in direction i at node n
|
||||||
|
// arrived from the node at n - c_i = neighbor in direction opp(i)
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ void stream_pull_load(unsigned long n,
|
||||||
|
float* __restrict__ f,
|
||||||
|
const fpxx* __restrict__ fi,
|
||||||
|
const unsigned long* __restrict__ j)
|
||||||
|
{
|
||||||
|
// i=0 (rest): always from self
|
||||||
|
f[0] = load_ddf(fi, index_f(n, 0u));
|
||||||
|
|
||||||
|
// For paired ordering, opp(i) swaps within each pair:
|
||||||
|
// opp(1)=2, opp(2)=1, opp(3)=4, opp(4)=3, ...
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 1; i < NQ; i += 2) {
|
||||||
|
// population i arrives from direction opp(i) = i+1
|
||||||
|
f[i] = load_ddf(fi, index_f(j[i+1], (unsigned int)i));
|
||||||
|
// population i+1 arrives from direction opp(i+1) = i
|
||||||
|
f[i + 1] = load_ddf(fi, index_f(j[i], (unsigned int)(i + 1)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
// stream_pull_store: write collided f[NQ] to the OUTPUT buffer at node n
|
||||||
|
// ---------------------------------------------------------------------------
|
||||||
|
__device__ __forceinline__ void stream_pull_store(unsigned long n,
|
||||||
|
const float* __restrict__ f,
|
||||||
|
fpxx* __restrict__ fi_out)
|
||||||
|
{
|
||||||
|
#pragma unroll
|
||||||
|
for (int i = 0; i < NQ; i++) {
|
||||||
|
store_ddf(fi_out, index_f(n, (unsigned int)i), f[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // CELERIS_STREAMING_PULL_DOUBLE_BUFFER_CUH
|
||||||
362
tests/test_d2q9_visual.py
Normal file
362
tests/test_d2q9_visual.py
Normal file
@ -0,0 +1,362 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
D2Q9 Regression Test — Poiseuille Channel + Cylinder Flow
|
||||||
|
==========================================================
|
||||||
|
Uses the ORIGINAL kernel.cu with same grid / BCs.
|
||||||
|
Produces matplotlib figures for visual validation.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tests/test_d2q9_visual.py --device 2
|
||||||
|
python tests/test_d2q9_visual.py --device 2 --cylinder
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, argparse, time
|
||||||
|
|
||||||
|
# Ensure CelerisLab package is importable
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'src'))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pycuda.driver as cuda
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
from matplotlib.colors import Normalize
|
||||||
|
|
||||||
|
from CelerisLab.cuda import compiler
|
||||||
|
from CelerisLab.common import preprocess as preproc
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Configuration ━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
NX, NY = 1280, 512
|
||||||
|
NQ = 9
|
||||||
|
NT = 128
|
||||||
|
DIM = 2
|
||||||
|
VIS = 0.002
|
||||||
|
U0 = 0.01
|
||||||
|
RHO = 1.0
|
||||||
|
TOTAL = NX * NY
|
||||||
|
|
||||||
|
# Original direction vectors (const.h ordering)
|
||||||
|
E = np.array([[0,0],[1,0],[0,1],[-1,0],[0,-1],[1,1],[-1,1],[-1,-1],[1,-1]], dtype=np.int32)
|
||||||
|
OPP = np.array([0, 3, 4, 1, 2, 7, 8, 5, 6], dtype=np.int32)
|
||||||
|
|
||||||
|
FLUID_FLAG = 0b00000001
|
||||||
|
SOLID_FLAG = 0b00000010
|
||||||
|
INTERFACE_FLAG = 0b00001000
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Helpers ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def configure_macros(n_objs=0):
|
||||||
|
"""Write macros.h to match original kernel settings."""
|
||||||
|
lines = compiler.read_lines(compiler.kernel_path("macros.h"))
|
||||||
|
defs = {
|
||||||
|
'MULT_GPU': 'False', 'NT': NT,
|
||||||
|
'X_1U': 128, 'Y_1U': 32, 'Z_1U': 1,
|
||||||
|
'LBtype': 'float',
|
||||||
|
'UX': 10, 'UY': 16, 'UZ': 1,
|
||||||
|
'NX': NX, 'NY': NY, 'NZ': 1,
|
||||||
|
'DIM': DIM, 'NQ': NQ,
|
||||||
|
'VIS': VIS, 'RHO': f'{RHO}', 'U0': U0,
|
||||||
|
'N_OBJS': n_objs,
|
||||||
|
}
|
||||||
|
for name, val in defs.items():
|
||||||
|
lines = compiler.modify_macro(lines, name, val)
|
||||||
|
compiler.write_lines(compiler.kernel_path("macros.h"), lines)
|
||||||
|
|
||||||
|
|
||||||
|
def extract_fields(ddf_host):
|
||||||
|
"""Compute rho, u, v from host DDF (original const.h ordering).
|
||||||
|
|
||||||
|
The original kernel uses DDF-shifting: stores f_shifted = f - w_i*RHO.
|
||||||
|
So sum(f_shifted) = rho - RHO (~0 for incompressible flow),
|
||||||
|
and momentum = sum(e_x * f_shifted) works because sum(w_i * e_x) = 0.
|
||||||
|
"""
|
||||||
|
f = ddf_host.reshape(NQ, NY, NX)
|
||||||
|
rho = np.sum(f, axis=0) + RHO # un-shift: rho = sum(f_shifted) + RHO
|
||||||
|
u = (f[1] + f[5] + f[8] - f[3] - f[6] - f[7]) / RHO
|
||||||
|
v = (f[2] + f[5] + f[6] - f[4] - f[7] - f[8]) / RHO
|
||||||
|
return rho, u, v
|
||||||
|
|
||||||
|
|
||||||
|
def analytical_poiseuille(y_arr):
|
||||||
|
"""Analytical parabolic profile matching InitTubeFlow."""
|
||||||
|
yy = (y_arr - 0.5 * (NY - 1)) / (NY - 2.0)
|
||||||
|
return U0 * 1.5 * (1 - 4 * yy**2)
|
||||||
|
|
||||||
|
|
||||||
|
def build_cylinder_data(cx, cy, radius):
|
||||||
|
"""Replicate driver.py add_cylinder logic for flag / delta / indx."""
|
||||||
|
flag = np.ones(TOTAL, dtype=np.uint8) # init all FLUID
|
||||||
|
indx = np.zeros(TOTAL, dtype=np.int32)
|
||||||
|
delta_list = []
|
||||||
|
index_offset = 0
|
||||||
|
|
||||||
|
# Build Poiseuille flag first (walls + solid borders)
|
||||||
|
for y in range(NY):
|
||||||
|
for x in range(NX):
|
||||||
|
k = x + y * NX
|
||||||
|
if y == 0 or y == NY - 1 or x == 0 or x == NX - 1:
|
||||||
|
flag[k] = SOLID_FLAG
|
||||||
|
|
||||||
|
# Add cylinder
|
||||||
|
for x in range(int(cx - radius) - 1, int(cx + radius) + 1):
|
||||||
|
for y in range(int(cy - radius) - 1, int(cy + radius) + 1):
|
||||||
|
if (x - cx)**2 + (y - cy)**2 < radius**2:
|
||||||
|
k = x + y * NX
|
||||||
|
flag[k] = SOLID_FLAG
|
||||||
|
dt = np.zeros(11, dtype=np.float32)
|
||||||
|
dt[0] = np.int32(0).view(np.float32) # id_object = 0
|
||||||
|
has_interface = False
|
||||||
|
for i in range(NQ):
|
||||||
|
xn = x + E[i][0]
|
||||||
|
yn = y + E[i][1]
|
||||||
|
if (xn - cx)**2 + (yn - cy)**2 >= radius**2:
|
||||||
|
has_interface = True
|
||||||
|
xi, yi = preproc.find_circle_intersection(
|
||||||
|
x, y, xn, yn, cx, cy, radius)
|
||||||
|
d_neb = np.sqrt((xi - xn)**2 + (yi - yn)**2)
|
||||||
|
e_len = np.sqrt(E[i][0]**2 + E[i][1]**2)
|
||||||
|
if e_len > 0:
|
||||||
|
dt[i] = d_neb / e_len
|
||||||
|
if has_interface:
|
||||||
|
flag[k] |= INTERFACE_FLAG
|
||||||
|
dt[9] = (cy - y) / radius
|
||||||
|
dt[10] = (x - cx) / radius
|
||||||
|
indx[k] = index_offset
|
||||||
|
delta_list.append(dt)
|
||||||
|
index_offset += 11
|
||||||
|
|
||||||
|
delta = np.concatenate(delta_list) if delta_list else np.zeros(1, dtype=np.float32)
|
||||||
|
return flag, indx, delta
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Simulation ━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def run_simulation(device_id, n_steps, n_objs, flag_host, indx_host, delta_host):
|
||||||
|
"""Compile kernel, run LBM, return DDF on host."""
|
||||||
|
cuda.init()
|
||||||
|
dev = cuda.Device(device_id)
|
||||||
|
ctx = dev.make_context()
|
||||||
|
print(f"[GPU {device_id}] {dev.name()}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
configure_macros(n_objs)
|
||||||
|
compiler.compile_kernel()
|
||||||
|
ptx_path = compiler.kernel_path("kernel.ptx")
|
||||||
|
mod = cuda.module_from_file(ptx_path)
|
||||||
|
step_fn = mod.get_function("OneStep")
|
||||||
|
init_fn = mod.get_function("InitTubeFlow")
|
||||||
|
|
||||||
|
nbytes_ddf = TOTAL * NQ * 4
|
||||||
|
ddf_gpu = cuda.mem_alloc(nbytes_ddf)
|
||||||
|
temp_gpu = cuda.mem_alloc(nbytes_ddf)
|
||||||
|
flag_gpu = cuda.mem_alloc(flag_host.nbytes)
|
||||||
|
indx_gpu = cuda.mem_alloc(indx_host.nbytes)
|
||||||
|
delta_gpu = cuda.mem_alloc(max(delta_host.nbytes, 4))
|
||||||
|
|
||||||
|
action_host = np.zeros(max(n_objs, 1), dtype=np.float32)
|
||||||
|
obs_host = np.zeros(max(n_objs * DIM, 1), dtype=np.float32)
|
||||||
|
action_gpu = cuda.mem_alloc(action_host.nbytes)
|
||||||
|
obs_gpu = cuda.mem_alloc(obs_host.nbytes)
|
||||||
|
|
||||||
|
cuda.memcpy_htod(action_gpu, action_host)
|
||||||
|
cuda.memcpy_htod(obs_gpu, obs_host)
|
||||||
|
|
||||||
|
block = (NT, 1, 1)
|
||||||
|
grid = (NX // NT, NY, 1)
|
||||||
|
|
||||||
|
# Init Poiseuille
|
||||||
|
init_fn(flag_gpu, ddf_gpu, block=block, grid=grid)
|
||||||
|
ctx.synchronize()
|
||||||
|
|
||||||
|
# Overwrite flag / indx / delta for cylinder case
|
||||||
|
cuda.memcpy_htod(flag_gpu, flag_host)
|
||||||
|
cuda.memcpy_htod(indx_gpu, indx_host)
|
||||||
|
cuda.memcpy_htod(delta_gpu, delta_host)
|
||||||
|
|
||||||
|
# Step loop
|
||||||
|
t0 = time.time()
|
||||||
|
for i in range(n_steps):
|
||||||
|
step_fn(flag_gpu, ddf_gpu, temp_gpu, indx_gpu, delta_gpu,
|
||||||
|
action_gpu, obs_gpu, block=block, grid=grid)
|
||||||
|
ddf_gpu, temp_gpu = temp_gpu, ddf_gpu
|
||||||
|
ctx.synchronize()
|
||||||
|
dt = time.time() - t0
|
||||||
|
mlups = TOTAL * n_steps / dt / 1e6
|
||||||
|
print(f" {n_steps} steps in {dt:.2f}s ({mlups:.1f} MLUPS)")
|
||||||
|
|
||||||
|
# Copy back
|
||||||
|
ddf = np.zeros(TOTAL * NQ, dtype=np.float32)
|
||||||
|
cuda.memcpy_dtoh(ddf, ddf_gpu)
|
||||||
|
flag_out = np.zeros(TOTAL, dtype=np.uint8)
|
||||||
|
cuda.memcpy_dtoh(flag_out, flag_gpu)
|
||||||
|
|
||||||
|
return ddf, flag_out
|
||||||
|
finally:
|
||||||
|
ctx.pop()
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Visualization ━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def plot_poiseuille(ddf, flag, out_path):
|
||||||
|
"""3-panel figure: velocity mag, u(y) profile, pressure along centerline."""
|
||||||
|
rho, u, v = extract_fields(ddf)
|
||||||
|
vel_mag = np.sqrt(u**2 + v**2)
|
||||||
|
|
||||||
|
# Mask solid cells for display
|
||||||
|
mask = (flag.reshape(NY, NX) & SOLID_FLAG).astype(bool)
|
||||||
|
vel_mag_masked = np.ma.array(vel_mag, mask=mask)
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(18, 5))
|
||||||
|
|
||||||
|
# (a) Velocity magnitude heatmap
|
||||||
|
ax = axes[0]
|
||||||
|
im = ax.imshow(vel_mag_masked, origin='lower', aspect='auto',
|
||||||
|
cmap='jet', extent=[0, NX, 0, NY])
|
||||||
|
plt.colorbar(im, ax=ax, label='|u|')
|
||||||
|
ax.set_title('Velocity magnitude')
|
||||||
|
ax.set_xlabel('x'); ax.set_ylabel('y')
|
||||||
|
|
||||||
|
# (b) u(y) profile at x = NX/2 vs. analytical
|
||||||
|
ax = axes[1]
|
||||||
|
x_mid = NX // 2
|
||||||
|
y_arr = np.arange(NY, dtype=float)
|
||||||
|
ax.plot(u[:, x_mid], y_arr, 'b-', lw=2, label='LBM')
|
||||||
|
ax.plot(analytical_poiseuille(y_arr), y_arr, 'r--', lw=1.5, label='Analytical')
|
||||||
|
ax.set_xlabel('u_x'); ax.set_ylabel('y')
|
||||||
|
ax.set_title(f'u(y) at x={x_mid}')
|
||||||
|
ax.legend()
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
# (c) Pressure along centerline y = NY/2
|
||||||
|
ax = axes[2]
|
||||||
|
y_mid = NY // 2
|
||||||
|
p = rho / 3.0
|
||||||
|
ax.plot(np.arange(NX), p[y_mid, :], 'g-', lw=1.5)
|
||||||
|
ax.set_xlabel('x'); ax.set_ylabel('p = ρ/3')
|
||||||
|
ax.set_title(f'Pressure along centerline (y={y_mid})')
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
fig.suptitle(f'D2Q9 Poiseuille – NX={NX}, NY={NY}, VIS={VIS}, U0={U0}', fontsize=13)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(out_path, dpi=150)
|
||||||
|
print(f" Saved: {out_path}")
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
def plot_cylinder(ddf, flag, cx, cy, radius, out_path):
|
||||||
|
"""3-panel figure: velocity magnitude (zoom), vorticity, streamlines."""
|
||||||
|
rho, u, v = extract_fields(ddf)
|
||||||
|
vel_mag = np.sqrt(u**2 + v**2)
|
||||||
|
mask = (flag.reshape(NY, NX) & SOLID_FLAG).astype(bool)
|
||||||
|
|
||||||
|
# Zoom window around cylinder
|
||||||
|
pad = int(radius * 8)
|
||||||
|
x0 = max(int(cx - pad), 0)
|
||||||
|
x1 = min(int(cx + pad * 2), NX)
|
||||||
|
y0 = max(int(cy - pad), 0)
|
||||||
|
y1 = min(int(cy + pad), NY)
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(1, 3, figsize=(20, 6))
|
||||||
|
|
||||||
|
# (a) Velocity magnitude (zoomed)
|
||||||
|
ax = axes[0]
|
||||||
|
vm_z = np.ma.array(vel_mag[y0:y1, x0:x1], mask=mask[y0:y1, x0:x1])
|
||||||
|
im = ax.imshow(vm_z, origin='lower', aspect='equal',
|
||||||
|
cmap='jet', extent=[x0, x1, y0, y1])
|
||||||
|
circ = plt.Circle((cx, cy), radius, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ)
|
||||||
|
plt.colorbar(im, ax=ax, label='|u|')
|
||||||
|
ax.set_title('Velocity magnitude')
|
||||||
|
|
||||||
|
# (b) Vorticity
|
||||||
|
ax = axes[1]
|
||||||
|
dvdx = np.gradient(v, axis=1)
|
||||||
|
dudy = np.gradient(u, axis=0)
|
||||||
|
omega = dvdx - dudy
|
||||||
|
om_z = np.ma.array(omega[y0:y1, x0:x1], mask=mask[y0:y1, x0:x1])
|
||||||
|
vmax = np.percentile(np.abs(omega[~mask]), 99)
|
||||||
|
im = ax.imshow(om_z, origin='lower', aspect='equal',
|
||||||
|
cmap='RdBu_r', extent=[x0, x1, y0, y1],
|
||||||
|
vmin=-vmax, vmax=vmax)
|
||||||
|
circ2 = plt.Circle((cx, cy), radius, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ2)
|
||||||
|
plt.colorbar(im, ax=ax, label='ω')
|
||||||
|
ax.set_title('Vorticity')
|
||||||
|
|
||||||
|
# (c) Streamlines
|
||||||
|
ax = axes[2]
|
||||||
|
X, Y = np.meshgrid(np.arange(x0, x1), np.arange(y0, y1))
|
||||||
|
u_z = u[y0:y1, x0:x1].copy()
|
||||||
|
v_z = v[y0:y1, x0:x1].copy()
|
||||||
|
u_z[mask[y0:y1, x0:x1]] = 0
|
||||||
|
v_z[mask[y0:y1, x0:x1]] = 0
|
||||||
|
speed = np.sqrt(u_z**2 + v_z**2)
|
||||||
|
ax.streamplot(X, Y, u_z, v_z, color=speed, cmap='jet',
|
||||||
|
density=2.0, linewidth=0.8)
|
||||||
|
circ3 = plt.Circle((cx, cy), radius, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ3)
|
||||||
|
ax.set_xlim(x0, x1); ax.set_ylim(y0, y1)
|
||||||
|
ax.set_aspect('equal')
|
||||||
|
ax.set_title('Streamlines')
|
||||||
|
|
||||||
|
fig.suptitle(f'D2Q9 Cylinder Flow – Re_D={U0*1.5*2*radius/VIS:.0f}, D={2*radius}', fontsize=13)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(out_path, dpi=150)
|
||||||
|
print(f" Saved: {out_path}")
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='D2Q9 Regression Test')
|
||||||
|
parser.add_argument('--device', type=int, default=2,
|
||||||
|
help='CUDA device ID (default: 2)')
|
||||||
|
parser.add_argument('--cylinder', action='store_true',
|
||||||
|
help='Also run cylinder flow test')
|
||||||
|
parser.add_argument('--steps-pois', type=int, default=5000,
|
||||||
|
help='Steps for Poiseuille (default: 5000)')
|
||||||
|
parser.add_argument('--steps-cyl', type=int, default=30000,
|
||||||
|
help='Steps for cylinder (default: 30000)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'output')
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
# ---- Test 1: Poiseuille ----
|
||||||
|
print("\n===== Test 1: Poiseuille Channel Flow =====")
|
||||||
|
flag_pois = np.ones(TOTAL, dtype=np.uint8)
|
||||||
|
indx_pois = np.zeros(TOTAL, dtype=np.int32)
|
||||||
|
delta_pois = np.zeros(1, dtype=np.float32)
|
||||||
|
|
||||||
|
ddf, flag = run_simulation(args.device, args.steps_pois, 0,
|
||||||
|
flag_pois, indx_pois, delta_pois)
|
||||||
|
plot_poiseuille(ddf, flag, os.path.join(out_dir, 'poiseuille_d2q9.png'))
|
||||||
|
|
||||||
|
# Error metric
|
||||||
|
rho, u, v = extract_fields(ddf)
|
||||||
|
y_arr = np.arange(NY, dtype=float)
|
||||||
|
u_ana = analytical_poiseuille(y_arr)
|
||||||
|
x_mid = NX // 2
|
||||||
|
u_num = u[:, x_mid]
|
||||||
|
# Interior cells only (skip walls)
|
||||||
|
err = np.max(np.abs(u_num[2:-2] - u_ana[2:-2])) / np.max(np.abs(u_ana[2:-2]))
|
||||||
|
print(f" L∞ relative error at x={x_mid}: {err:.2e}")
|
||||||
|
|
||||||
|
# ---- Test 2: Cylinder ----
|
||||||
|
if args.cylinder:
|
||||||
|
print("\n===== Test 2: Flow Around Cylinder =====")
|
||||||
|
cyl_cx, cyl_cy, cyl_r = 256.0, 256.0, 32.0
|
||||||
|
flag_cyl, indx_cyl, delta_cyl = build_cylinder_data(cyl_cx, cyl_cy, cyl_r)
|
||||||
|
|
||||||
|
ddf2, flag2 = run_simulation(args.device, args.steps_cyl, 1,
|
||||||
|
flag_cyl, indx_cyl, delta_cyl)
|
||||||
|
plot_cylinder(ddf2, flag2, cyl_cx, cyl_cy, cyl_r,
|
||||||
|
os.path.join(out_dir, 'cylinder_d2q9.png'))
|
||||||
|
|
||||||
|
print("\nDone.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
327
tests/test_d3q19_cylinder.py
Normal file
327
tests/test_d3q19_cylinder.py
Normal file
@ -0,0 +1,327 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
D3Q19 SRT — Cylinder Wake Flow (Periodic Z)
|
||||||
|
=============================================
|
||||||
|
Tests the v2 modular kernel (kernel_v2.cu) in 3D.
|
||||||
|
Cylinder axis along z, parabolic inlet, pressure outlet, no-slip y-walls.
|
||||||
|
Produces cross-section visualizations at z=NZ/2.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python tests/test_d3q19_cylinder.py --device 3
|
||||||
|
python tests/test_d3q19_cylinder.py --device 3 --re 200 --steps 50000
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys, os, argparse, time, struct
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'src'))
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import pycuda.driver as cuda
|
||||||
|
|
||||||
|
import matplotlib
|
||||||
|
matplotlib.use('Agg')
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
from CelerisLab.cuda import compiler
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Configuration ━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
# Reasonable 3D grid — fits in < 500 MB GPU memory
|
||||||
|
NX, NY, NZ = 256, 128, 32
|
||||||
|
NQ = 19
|
||||||
|
NT = 128
|
||||||
|
DIM = 3
|
||||||
|
RHO = 1.0
|
||||||
|
CYL_CX, CYL_CY = 64.0, 64.0 # Cylinder center (x,y)
|
||||||
|
CYL_R = 12.0 # Cylinder radius
|
||||||
|
TOTAL = NX * NY * NZ
|
||||||
|
|
||||||
|
# D3Q19 paired direction ordering (from descriptors.cuh)
|
||||||
|
# 0:rest (1,2)±x (3,4)±y (5,6)±z
|
||||||
|
# (7,8)±(x+y) (9,10)±(x+z) (11,12)±(y+z)
|
||||||
|
# (13,14)±(x-y) (15,16)±(x-z) (17,18)±(y-z)
|
||||||
|
CX = np.array([0, 1,-1, 0, 0, 0, 0, 1,-1, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0], dtype=np.int32)
|
||||||
|
CY = np.array([0, 0, 0, 1,-1, 0, 0, 1,-1, 0, 0, 1,-1,-1, 1, 0, 0, 1,-1], dtype=np.int32)
|
||||||
|
CZ = np.array([0, 0, 0, 0, 0, 1,-1, 0, 0, 1,-1, 1,-1, 0, 0,-1, 1,-1, 1], dtype=np.int32)
|
||||||
|
W = np.array([1/3] + [1/18]*6 + [1/36]*12, dtype=np.float32)
|
||||||
|
|
||||||
|
FLUID_FLAG = 0x01
|
||||||
|
SOLID_FLAG = 0x02
|
||||||
|
OBSTACLE_FLAG = 0x04 # triggers half-way BB at adjacent fluid nodes
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Helpers ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def compute_vis_omega(Re, D, U0):
|
||||||
|
"""Compute viscosity and omega from target Reynolds number."""
|
||||||
|
vis = U0 * D / Re
|
||||||
|
omega = 1.0 / (3.0 * vis + 0.5)
|
||||||
|
return vis, omega
|
||||||
|
|
||||||
|
|
||||||
|
def configure_macros_3d(vis, u0, n_objs=0):
|
||||||
|
"""Write macros.h for D3Q19 SRT."""
|
||||||
|
lines = compiler.read_lines(compiler.kernel_path("macros.h"))
|
||||||
|
defs = {
|
||||||
|
'MULT_GPU': 'False', 'NT': NT,
|
||||||
|
'X_1U': NX, 'Y_1U': NY, 'Z_1U': NZ,
|
||||||
|
'LBtype': 'float',
|
||||||
|
'UX': 1, 'UY': 1, 'UZ': 1,
|
||||||
|
'NX': NX, 'NY': NY, 'NZ': NZ,
|
||||||
|
'DIM': DIM, 'NQ': NQ,
|
||||||
|
'VIS': f'{vis:.10f}', 'RHO': f'{RHO}', 'U0': u0,
|
||||||
|
'N_OBJS': n_objs,
|
||||||
|
'COLLISION_MODEL': 0, # SRT
|
||||||
|
'STREAMING_MODEL': 0, # double-buffer
|
||||||
|
'STORE_PRECISION': 0, # FP32
|
||||||
|
'USE_DDF_SHIFTING': 0,
|
||||||
|
}
|
||||||
|
for name, val in defs.items():
|
||||||
|
lines = compiler.modify_macro(lines, name, val)
|
||||||
|
compiler.write_lines(compiler.kernel_path("macros.h"), lines)
|
||||||
|
|
||||||
|
|
||||||
|
def build_cylinder_3d(cx, cy, radius):
|
||||||
|
"""Build flag array for 3D cylinder (axis along z, periodic z)."""
|
||||||
|
flag = np.ones(TOTAL, dtype=np.uint8) * FLUID_FLAG
|
||||||
|
for z in range(NZ):
|
||||||
|
for y in range(NY):
|
||||||
|
for x in range(NX):
|
||||||
|
k = z * NY * NX + y * NX + x
|
||||||
|
# Channel walls
|
||||||
|
if y == 0 or y == NY - 1 or x == 0 or x == NX - 1:
|
||||||
|
flag[k] = SOLID_FLAG
|
||||||
|
# Cylinder body (obstacle — triggers BB at fluid neighbors)
|
||||||
|
elif (x - cx)**2 + (y - cy)**2 < radius**2:
|
||||||
|
flag[k] = OBSTACLE_FLAG
|
||||||
|
return flag
|
||||||
|
|
||||||
|
|
||||||
|
def extract_fields_3d(ddf_host, z_slice):
|
||||||
|
"""Extract rho, u, v, w at a given z-slice from D3Q19 DDF (v2 ordering)."""
|
||||||
|
# DDF layout: f[i * TOTAL + k] where k = z*NY*NX + y*NX + x
|
||||||
|
f = ddf_host.reshape(NQ, NZ, NY, NX)
|
||||||
|
fz = f[:, z_slice, :, :] # shape (NQ, NY, NX)
|
||||||
|
rho = np.sum(fz, axis=0)
|
||||||
|
ux = np.zeros_like(rho)
|
||||||
|
uy = np.zeros_like(rho)
|
||||||
|
uz_field = np.zeros_like(rho)
|
||||||
|
for i in range(NQ):
|
||||||
|
ux += CX[i] * fz[i]
|
||||||
|
uy += CY[i] * fz[i]
|
||||||
|
uz_field += CZ[i] * fz[i]
|
||||||
|
ux /= rho
|
||||||
|
uy /= rho
|
||||||
|
uz_field /= rho
|
||||||
|
return rho, ux, uy, uz_field
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Simulation ━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def run_d3q19(device_id, n_steps, vis, u0, flag_host):
|
||||||
|
"""Compile v2 kernel, run D3Q19 SRT, return DDF."""
|
||||||
|
omega = 1.0 / (3.0 * vis + 0.5)
|
||||||
|
cuda.init()
|
||||||
|
dev = cuda.Device(device_id)
|
||||||
|
ctx = dev.make_context()
|
||||||
|
print(f"[GPU {device_id}] {dev.name()}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
configure_macros_3d(vis, u0)
|
||||||
|
compiler.compile_kernel_v2()
|
||||||
|
ptx_path = compiler.kernel_path("kernel_v2.ptx")
|
||||||
|
mod = cuda.module_from_file(ptx_path)
|
||||||
|
|
||||||
|
# Get kernels (extern "C" entries from kernel_v2.cu)
|
||||||
|
init_fn = mod.get_function("InitTubeFlow_v2")
|
||||||
|
step_fn = mod.get_function("OneStep")
|
||||||
|
|
||||||
|
# Set d_params.omega via __constant__ memory
|
||||||
|
params_ptr, params_size = mod.get_global("d_params")
|
||||||
|
# LBMParams struct layout (see params.cuh):
|
||||||
|
# Nx(4) Ny(4) Nz(4) N(8) omega(4) omega_bulk(4) fx(4) fy(4) fz(4)
|
||||||
|
# rho_ref(4) u_inlet(4) n_objects(4)
|
||||||
|
# Pack: unsigned int Nx, Ny, Nz; unsigned long N; float omega, omega_bulk, fx, fy, fz, rho_ref, u_inlet; unsigned int n_objects
|
||||||
|
params_data = struct.pack('IIIQfffffffI',
|
||||||
|
NX, NY, NZ,
|
||||||
|
TOTAL,
|
||||||
|
omega, 0.0, # omega, omega_bulk
|
||||||
|
0.0, 0.0, 0.0, # fx, fy, fz
|
||||||
|
RHO, u0, # rho_ref, u_inlet
|
||||||
|
0) # n_objects
|
||||||
|
# Pad to match struct size
|
||||||
|
if len(params_data) < params_size:
|
||||||
|
params_data += b'\x00' * (params_size - len(params_data))
|
||||||
|
cuda.memcpy_htod(params_ptr, params_data)
|
||||||
|
|
||||||
|
# Allocate
|
||||||
|
nbytes_ddf = TOTAL * NQ * 4
|
||||||
|
ddf_gpu = cuda.mem_alloc(nbytes_ddf)
|
||||||
|
temp_gpu = cuda.mem_alloc(nbytes_ddf)
|
||||||
|
flag_gpu = cuda.mem_alloc(flag_host.nbytes)
|
||||||
|
indx_gpu = cuda.mem_alloc(TOTAL * 4)
|
||||||
|
delta_gpu = cuda.mem_alloc(4)
|
||||||
|
action_gpu = cuda.mem_alloc(4)
|
||||||
|
obs_gpu = cuda.mem_alloc(4)
|
||||||
|
|
||||||
|
# Dummy arrays
|
||||||
|
cuda.memset_d32(indx_gpu, 0, TOTAL)
|
||||||
|
cuda.memset_d32(delta_gpu, 0, 1)
|
||||||
|
cuda.memset_d32(action_gpu, 0, 1)
|
||||||
|
cuda.memset_d32(obs_gpu, 0, 1)
|
||||||
|
|
||||||
|
block = (NT, 1, 1)
|
||||||
|
grid = (NX // NT, NY, NZ)
|
||||||
|
|
||||||
|
# Initialize parabolic flow
|
||||||
|
init_fn(flag_gpu, ddf_gpu, block=block, grid=grid)
|
||||||
|
ctx.synchronize()
|
||||||
|
|
||||||
|
# Overwrite flags with cylinder geometry
|
||||||
|
cuda.memcpy_htod(flag_gpu, flag_host)
|
||||||
|
|
||||||
|
# Step loop
|
||||||
|
print(f" Running {n_steps} steps (NX={NX}, NY={NY}, NZ={NZ}, omega={omega:.4f})...")
|
||||||
|
t0 = time.time()
|
||||||
|
for i in range(n_steps):
|
||||||
|
step_fn(flag_gpu, ddf_gpu, temp_gpu, indx_gpu, delta_gpu,
|
||||||
|
action_gpu, obs_gpu,
|
||||||
|
block=block, grid=grid)
|
||||||
|
ddf_gpu, temp_gpu = temp_gpu, ddf_gpu
|
||||||
|
if (i + 1) % 5000 == 0:
|
||||||
|
ctx.synchronize()
|
||||||
|
elapsed = time.time() - t0
|
||||||
|
mlups = TOTAL * (i + 1) / elapsed / 1e6
|
||||||
|
print(f" step {i+1}/{n_steps} ({mlups:.1f} MLUPS)")
|
||||||
|
ctx.synchronize()
|
||||||
|
dt = time.time() - t0
|
||||||
|
mlups = TOTAL * n_steps / dt / 1e6
|
||||||
|
print(f" Done: {dt:.1f}s, {mlups:.1f} MLUPS")
|
||||||
|
|
||||||
|
# Copy back
|
||||||
|
ddf = np.zeros(TOTAL * NQ, dtype=np.float32)
|
||||||
|
cuda.memcpy_dtoh(ddf, ddf_gpu)
|
||||||
|
return ddf
|
||||||
|
|
||||||
|
finally:
|
||||||
|
ctx.pop()
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Visualization ━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def plot_d3q19_cylinder(ddf, flag, Re, u0, out_path):
|
||||||
|
"""4-panel figure at z=NZ/2: vel-mag, vorticity, streamlines, u(y) profile."""
|
||||||
|
z_mid = NZ // 2
|
||||||
|
rho, ux, uy, uz = extract_fields_3d(ddf, z_mid)
|
||||||
|
vel_mag = np.sqrt(ux**2 + uy**2 + uz**2)
|
||||||
|
|
||||||
|
mask2d = ((flag.reshape(NZ, NY, NX)[z_mid] & (SOLID_FLAG | OBSTACLE_FLAG)) != 0)
|
||||||
|
vel_masked = np.ma.array(vel_mag, mask=mask2d)
|
||||||
|
|
||||||
|
fig, axes = plt.subplots(2, 2, figsize=(16, 10))
|
||||||
|
|
||||||
|
# (a) Velocity magnitude
|
||||||
|
ax = axes[0, 0]
|
||||||
|
im = ax.imshow(vel_masked, origin='lower', aspect='auto',
|
||||||
|
cmap='jet', extent=[0, NX, 0, NY])
|
||||||
|
circ = plt.Circle((CYL_CX, CYL_CY), CYL_R, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ)
|
||||||
|
plt.colorbar(im, ax=ax, label='|u|')
|
||||||
|
ax.set_title(f'Velocity magnitude (z={z_mid})')
|
||||||
|
ax.set_xlabel('x'); ax.set_ylabel('y')
|
||||||
|
|
||||||
|
# (b) Vorticity ω_z = ∂v/∂x − ∂u/∂y
|
||||||
|
ax = axes[0, 1]
|
||||||
|
dvdx = np.gradient(uy, axis=1)
|
||||||
|
dudy = np.gradient(ux, axis=0)
|
||||||
|
omega_z = dvdx - dudy
|
||||||
|
om_masked = np.ma.array(omega_z, mask=mask2d)
|
||||||
|
vmax = np.percentile(np.abs(omega_z[~mask2d]), 99) if np.any(~mask2d) else 1e-3
|
||||||
|
im = ax.imshow(om_masked, origin='lower', aspect='auto',
|
||||||
|
cmap='RdBu_r', extent=[0, NX, 0, NY],
|
||||||
|
vmin=-vmax, vmax=vmax)
|
||||||
|
circ2 = plt.Circle((CYL_CX, CYL_CY), CYL_R, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ2)
|
||||||
|
plt.colorbar(im, ax=ax, label='ω_z')
|
||||||
|
ax.set_title('Vorticity ω_z')
|
||||||
|
ax.set_xlabel('x'); ax.set_ylabel('y')
|
||||||
|
|
||||||
|
# (c) Streamlines
|
||||||
|
ax = axes[1, 0]
|
||||||
|
X, Y = np.meshgrid(np.arange(NX), np.arange(NY))
|
||||||
|
ux_c = ux.copy(); ux_c[mask2d] = 0
|
||||||
|
uy_c = uy.copy(); uy_c[mask2d] = 0
|
||||||
|
speed = np.sqrt(ux_c**2 + uy_c**2)
|
||||||
|
ax.streamplot(X, Y, ux_c, uy_c, color=speed, cmap='jet',
|
||||||
|
density=2.5, linewidth=0.7)
|
||||||
|
circ3 = plt.Circle((CYL_CX, CYL_CY), CYL_R, fill=True, color='gray', alpha=0.7)
|
||||||
|
ax.add_patch(circ3)
|
||||||
|
ax.set_xlim(0, NX); ax.set_ylim(0, NY)
|
||||||
|
ax.set_aspect('auto')
|
||||||
|
ax.set_title('Streamlines')
|
||||||
|
ax.set_xlabel('x'); ax.set_ylabel('y')
|
||||||
|
|
||||||
|
# (d) u_x(y) profiles at different x stations
|
||||||
|
ax = axes[1, 1]
|
||||||
|
y_arr = np.arange(NY)
|
||||||
|
# Analytical parabolic inlet
|
||||||
|
yy = (y_arr - 0.5 * (NY - 1)) / (NY - 2.0)
|
||||||
|
u_ana = u0 * 1.5 * (1 - 4 * yy**2)
|
||||||
|
|
||||||
|
x_stations = [NX // 8, NX // 4, NX // 2, 3 * NX // 4]
|
||||||
|
for xs in x_stations:
|
||||||
|
ax.plot(ux[:, xs], y_arr, label=f'x={xs}')
|
||||||
|
ax.plot(u_ana, y_arr, 'k--', lw=1.5, label='Analytical inlet')
|
||||||
|
ax.set_xlabel('u_x'); ax.set_ylabel('y')
|
||||||
|
ax.set_title('u_x(y) profiles')
|
||||||
|
ax.legend(fontsize=8)
|
||||||
|
ax.grid(True, alpha=0.3)
|
||||||
|
|
||||||
|
fig.suptitle(f'D3Q19 SRT Cylinder — Re={Re:.0f}, D={2*CYL_R:.0f}, '
|
||||||
|
f'Grid={NX}×{NY}×{NZ}', fontsize=13)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig(out_path, dpi=150)
|
||||||
|
print(f" Saved: {out_path}")
|
||||||
|
plt.close(fig)
|
||||||
|
|
||||||
|
|
||||||
|
# ━━━━━━━━━━━━━━━━━━━━━━━ Main ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
||||||
|
|
||||||
|
def main():
|
||||||
|
parser = argparse.ArgumentParser(description='D3Q19 SRT Cylinder Flow')
|
||||||
|
parser.add_argument('--device', type=int, default=0,
|
||||||
|
help='CUDA device ID (default: 0)')
|
||||||
|
parser.add_argument('--re', type=float, default=100.0,
|
||||||
|
help='Reynolds number based on diameter (default: 100)')
|
||||||
|
parser.add_argument('--u0', type=float, default=0.04,
|
||||||
|
help='Inlet characteristic velocity (default: 0.04)')
|
||||||
|
parser.add_argument('--steps', type=int, default=30000,
|
||||||
|
help='Number of LBM steps (default: 30000)')
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
out_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', 'output')
|
||||||
|
os.makedirs(out_dir, exist_ok=True)
|
||||||
|
|
||||||
|
D = 2 * CYL_R
|
||||||
|
vis, omega = compute_vis_omega(args.re, D, args.u0)
|
||||||
|
print(f"\n===== D3Q19 SRT Cylinder Flow =====")
|
||||||
|
print(f" Re = {args.re:.0f}, D = {D:.0f}, U0 = {args.u0}")
|
||||||
|
print(f" ν = {vis:.6f}, ω = {omega:.4f}")
|
||||||
|
if omega > 1.95:
|
||||||
|
print(f" WARNING: omega={omega:.4f} is close to 2.0, stability may be poor.")
|
||||||
|
print(f" Consider reducing U0 or Re.")
|
||||||
|
|
||||||
|
flag = build_cylinder_3d(CYL_CX, CYL_CY, CYL_R)
|
||||||
|
n_solid = np.sum(flag == SOLID_FLAG)
|
||||||
|
n_fluid = np.sum(flag == FLUID_FLAG)
|
||||||
|
print(f" Grid: {NX}×{NY}×{NZ} = {TOTAL} cells (fluid: {n_fluid}, solid: {n_solid})")
|
||||||
|
print(f" Memory: ~{2 * TOTAL * NQ * 4 / 1e6:.0f} MB for double-buffer DDF")
|
||||||
|
|
||||||
|
ddf = run_d3q19(args.device, args.steps, vis, args.u0, flag)
|
||||||
|
|
||||||
|
plot_d3q19_cylinder(ddf, flag, args.re, args.u0,
|
||||||
|
os.path.join(out_dir, f'cylinder_d3q19_re{int(args.re)}.png'))
|
||||||
|
|
||||||
|
print("\nDone.")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
Loading…
Reference in New Issue
Block a user