feat: add matched SRT channel closure for blocked AMR root

This commit is contained in:
TRON committed 2026-10-17 23:53:24 -07:00
1 parent 20ae677092
commit 5b4610441c
9 files changed
+504 -14

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
// Actual production step kernels under a CPU launch shim. No CUDA claim.
#include <cmath>
#include <cstdio>
#include <cstdint>
#include <vector>
#define __device__
#define __constant__
#define __global__
#define __forceinline__ inline
#define __launch_bounds__(...)
#define LATTICE_D2Q9 0
#define LATTICE_D3Q19 1
#define LATTICE_MODEL 0
#define NQ 9
#define DIM 2
#define NZ 1
#define NT 1
#define COLLISION_MODEL 0
#define USE_LES 0
#define RHO 1.f
#define U0 .025f
#define OMEGA_COLLISION_MIN .01f
#define OMEGA_COLLISION_MAX 1.99f
#define Y_WALL_BC 0
#define OUTLET_MODE 0
struct Dim {unsigned int x=0,y=0,z=0;} threadIdx,blockIdx,blockDim;
inline float __fdiv_rn(float a,float b){return a/b;}
#include "core/precision.cuh"
#include "core/descriptors.cuh"
#include "core/layout.cuh"
#include "core/params.cuh"
#include "core/flags.cuh"
#include "operators/macro.cuh"
#include "operators/collision_srt.cuh"
#include "streaming/pull_double_buffer.cuh"
#if UNIFORM
#include "operators/forcing_guo.cuh"
#include "operators/helpers.cuh"
#include "boundary/bounce_back.cuh"
#include "boundary/inlet_outlet.cuh"
#include "step/one_step_double.cu"
#else
constexpr unsigned char KIND_OUTER=2;
#include "step/block_channel.cu"
#endif
int main(){
constexpr int gx=24,gy=20,n=gx*gy;
std::vector<float> state(9*n),next(9*n);
for(int q=0;q<9;q++)for(int y=0;y<gy;y++)for(int x=0;x<gx;x++)
state[q*n+y*gx+x]=d_w[q]*(1.f+.001f*std::sin(float(x+3*y+q)))-USE_DDF_SHIFTING*d_w[q];
d_params={1.25f,0,0,0,0};
#if UNIFORM
std::vector<uint16_t> flags(n,FLAG_FLUID);
for(int y=0;y<gy;y++)for(int x=0;x<gx;x++){
if(y==0||y==gy-1) flags[y*gx+x]=FLAG_SOLID|FLAG_BC_WALL;
else if(x==0) flags[y*gx+x]=FLAG_SOLID|FLAG_BC_INLET;
else if(x==gx-1) flags[y*gx+x]=FLAG_SOLID|FLAG_BC_OUTLET;
}
#else
constexpr int bx=NX-2,by=NY-2,nbx=gx/bx,nby=gy/by;
std::vector<float> input(nbx*nby*9*TOTAL_CELLS),output(input.size());
d_block_channel={gx,gy,U0};
#endif
for(int step=0;step<5;step++){
#if UNIFORM
for(int y=0;y<gy;y++)for(int x=0;x<gx;x++){
blockIdx={unsigned(x),unsigned(y),0};OneStep(flags.data(),state.data(),next.data());
}
#else
// Populate cores and every wrapped halo from the same dense source.
for(int b=0;b<nbx*nby;b++)for(int y=0;y<NY;y++)for(int x=0;x<NX;x++)for(int q=0;q<9;q++){
int xx=((b%nbx)*bx+x-1+gx)%gx, yy=((b/nbx)*by+y-1+gy)%gy;
input[b*9*TOTAL_CELLS+q*TOTAL_CELLS+y*NX+x]=state[q*n+yy*gx+xx];
}
for(int b=0;b<nbx*nby;b++)for(int y=1;y<NY-1;y++)for(int x=1;x<NX-1;x++){
blockIdx={unsigned(x),unsigned(y),unsigned(b)};
BlockChannelStep(input.data(),output.data(),1,nullptr,nullptr);
for(int q=0;q<9;q++)next[q*n+((b/nbx)*by+y-1)*gx+(b%nbx)*bx+x-1]=output[b*9*TOTAL_CELLS+q*TOTAL_CELLS+y*NX+x];
}
#endif
state.swap(next);
}
for(float f:state)printf("%.9g\n",f);
}
+99
View File
@@ -0,0 +1,99 @@
"""Root-channel host contract and actual-header CPU parity (no CUDA evidence)."""
from dataclasses import replace
import numpy as np
import pytest
from CelerisLab.config import LBMConfig
from CelerisLab.lbm.blocks import BlockLayout2D
from CelerisLab.lbm.channel import channel_masks, validate_channel_config, validate_channel_transfers
from CelerisLab.lbm.refinement import FixedPatch2D
from CelerisLab.lbm.refinement_tables import blocked_base_tables
from .test_numerical_operators import compile_run
@pytest.mark.parametrize("shift", [0, 1])
@pytest.mark.parametrize("profile", [0, 1])
def test_actual_kernel_uniform_parity(tmp_path, shift, profile):
"""Nonuniform ghosts, corners, top wall, global profile and outlet halo donor."""
defines = dict(USE_DDF_SHIFTING=shift, INLET_PROFILE=profile)
uniform = np.array(compile_run(tmp_path, "channel_root.cpp", dict(
defines, UNIFORM=1, NX=24, NY=20)), np.float32)
assert np.isfinite(uniform).all()
for by, bx in ((20, 24), (5, 6), (4, 1)):
blocked = np.array(compile_run(tmp_path, "channel_root.cpp", dict(
defines, UNIFORM=0, NX=bx+2, NY=by+2)), np.float32)
np.testing.assert_array_equal(blocked, uniform)
def transfer(bounds, block=(4, 6)):
cfg = LBMConfig(nx=24, ny=20, viscosity=.1)
pair = FixedPatch2D.from_config(cfg, bounds)
layout = BlockLayout2D.build(20, 24, *block)
targets = blocked_base_tables(pair, layout, np.argsort(pair.coalesce_coarse))[1]
return pair, layout, targets
def test_masks_and_minimal_footprint():
fluid, bulk = channel_masks((20, 24))
assert fluid.sum() == 18*22 and bulk.sum() == 16*22
assert fluid[1, 1] and not bulk[1, 1]
assert bulk[2, 1] and not fluid[0, 0]
# No tile alignment or artificial large padding: closest legal x donor is x=1.
validate_channel_transfers(*transfer((3, 4, 19, 16)))
for bounds in ((2, 4, 19, 16), (3, 3, 19, 16), (3, 4, 22, 16), (3, 4, 19, 17)):
with pytest.raises(ValueError, match="bulk"):
validate_channel_transfers(*transfer(bounds))
def test_actual_closure_read_conflicts():
pair, layout, targets = transfer((6, 6, 18, 14))
validate_channel_transfers(pair, layout, targets)
for target in (layout.slot(layout.owner[5, -1]-1, 2),
layout.slot(layout.owner[1, 4], 4),
layout.slot(layout.owner[-2, 4], 3)):
with pytest.raises(ValueError, match="immutable"):
validate_channel_transfers(pair, layout, np.append(targets, target))
def test_channel_scope():
cfg = LBMConfig(nx=24, ny=20, viscosity=.1)
validate_channel_config(cfg, [(4, 6), (4, 4)])
with pytest.raises(ValueError, match="all-blocked"):
validate_channel_config(cfg, [None, (4, 4)])
for change in (dict(inlet_scheme="equilibrium"), dict(outlet_mode="zero_gradient"),
dict(y_wall_bc="free_slip"), dict(wall_slot_width=2)):
with pytest.raises(ValueError, match="channel requires"):
validate_channel_config(replace(cfg, **change), [(4, 6), (4, 4)])
def test_evolved_ghost_snapshot_and_physical_download():
from contextlib import nullcontext
from unittest.mock import Mock
from .test_refinement_three_level_multipatch import host_module
host = host_module()
storage = {}
host.compiler.load_module.return_value.get_global.side_effect = lambda name: (name, 12 if name == "d_block_channel" else 20)
host.cuda.mem_alloc.side_effect = lambda size: Mock()
host.cuda.memcpy_htod.side_effect = lambda ptr, a: storage.__setitem__(id(ptr), np.array(a, copy=True))
host.cuda.memcpy_dtoh.side_effect = lambda a, ptr: np.copyto(a, storage[id(ptr)])
context = Mock(activate=nullcontext, sm_arch="sm_70")
cfg = LBMConfig(nx=24, ny=20, viscosity=.1, velocity=.025)
backend = host.FixedPatchStepper(cfg, (6, 6, 18, 14), context=context,
coarse_boundary="channel", base_block=(4, 6), fine_block=(4, 4))
try:
weights = np.array([4/9]+[1/9]*4+[1/36]*4)[:, None, None]
arrays = [np.broadcast_to(weights, level.shape).copy() for level in backend._levels]
arrays[0][:, 0, 0] *= 1.001
backend.initialize(*arrays)
snapshot = backend._download_raw()
assert np.isfinite(snapshot.values[0][:, 0, :]).all()
assert np.isnan(backend.download()[0][:, 0, :]).all()
backend._initialize_raw(snapshot)
for a, b in zip(snapshot.values, backend._download_raw().values):
np.testing.assert_array_equal(a, b)
arrays[0][0, 0, 0] = np.nan
with pytest.raises(ValueError, match="finite"):
backend.initialize(*arrays)
assert backend._levels[0].kernel == host.compiler.load_module.return_value.get_function("BlockChannelStep")
finally:
backend.close()