Files
CelerisLab/tests/validation/multigrid/test_channel_root_gpu.py
T

134 lines
6.8 KiB
Python

"""Short root-channel GPU gates. Caller owns GPU admission (CUDA_VISIBLE_DEVICES).
Run: python -m unittest discover -s tests/validation/multigrid -p test_channel_root_gpu.py
Uniform parity covers every ghost/corner and bx=1 outlet halo donors. AMR gates
are split invariance and finite evolution, not external DFG benchmark validation.
"""
from dataclasses import replace
import struct
import unittest
import numpy as np
import pycuda.driver as cuda
from CelerisLab.config import LBMConfig
from CelerisLab.cuda import compiler_v2 as compiler
from CelerisLab.cuda.context import CudaContext
from CelerisLab.lbm.blocks import BlockLayout2D
from CelerisLab.lbm.descriptors import FLUID, SOLID, BC_WALL, BC_INLET, BC_OUTLET
from CelerisLab.lbm.population_storage import _encode_physical
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
def state(ny, nx):
q, y, x = np.mgrid[:9, :ny, :nx]
w = np.array([4/9]+[1/9]*4+[1/36]*4)[:, None, None]
return w*(1+.001*np.sin(x+3*y+q))
def compile_module(cfg, context, source):
cfg.validate()
build = compiler.generate_config(cfg)
try:
module = compiler.load_module(compiler.compile_kernel(context.sm_arch, source=source, build_dir=build))
finally:
compiler.cleanup_build_dir(build)
cuda.memcpy_htod(module.get_global("d_params")[0], struct.pack("=ffffI", cfg.omega, 0, 0, 0, 0))
return module
def run_root(cfg, context, initial, block=None, steps=8):
"""Launch the production uniform or blocked kernel, without a test-specific backend."""
allocations = []
def upload(a):
a = np.ascontiguousarray(a)
p = cuda.mem_alloc(a.nbytes); allocations.append(p); cuda.memcpy_htod(p, a)
return p
try:
layout = None if block is None else BlockLayout2D.build(cfg.ny, cfg.nx, *block)
local = cfg if layout is None else replace(cfg, nx=layout.local_shape[1], ny=layout.local_shape[0])
module = compile_module(local, context, "kernel_v2.cu" if layout is None else "blocks.cu")
data = initial if layout is None else layout.to_blocked(initial)
current, spare = upload(data), upload(np.full_like(data, np.nan))
tpb = cfg.threads_per_block
kw = dict(block=(tpb, 1, 1))
if layout is None:
flags = np.full((cfg.ny,cfg.nx), FLUID, np.uint16)
flags[:, 0] = SOLID|BC_INLET; flags[:, -1] = SOLID|BC_OUTLET
flags[[0, -1], :] = SOLID|BC_WALL
flag = upload(flags)
step = module.get_function("OneStep")
else:
dst, src = upload(layout.halo_dst), upload(layout.halo_src)
gather = module.get_function("BlockHaloCopy")
step = module.get_function("BlockChannelStep")
cuda.memcpy_htod(module.get_global("d_block_channel")[0], struct.pack("=IIf", cfg.nx, cfg.ny, cfg.velocity))
grid = ((local.nx+tpb-1)//tpb, local.ny, 1 if layout is None else layout.nblocks)
for _ in range(steps):
if layout is None:
step(flag, current, spare, grid=grid, **kw)
else:
gather(current, dst, src, np.uint32(len(layout.halo_dst)),
grid=((len(layout.halo_dst)+tpb-1)//tpb, 1), **kw)
step(current, spare, np.uint32(1), np.uintp(0), np.uintp(0), grid=grid, **kw)
current, spare = spare, current
cuda.Context.synchronize()
result = np.empty_like(data); cuda.memcpy_dtoh(result, current)
return result if layout is None else layout.to_dense(result)
finally:
for pointer in allocations: pointer.free()
class ChannelRootGPU(unittest.TestCase):
def test_uniform_matched_root(self):
with CudaContext(0) as context:
for shifted in (False, True):
for profile, clamp, damping in (("uniform", True, .5), ("parabolic", True, .5),
("parabolic", False, .8)):
cfg = LBMConfig(nx=24, ny=20, viscosity=.1, velocity=.025,
ddf_shifting=shifted, inlet_profile=profile,
outlet_backflow_clamp=clamp, outlet_srt_neq_damp=damping)
initial = _encode_physical(state(cfg.ny, cfg.nx), shifted)
expected = run_root(cfg, context, initial)
self.assertTrue(np.isfinite(expected).all())
for block in ((20,24), (5,6), (4,1)):
actual = run_root(cfg, context, initial, block)
np.testing.assert_array_equal(actual, expected)
def test_amr_channel_body_jet_smoke(self):
from CelerisLab.body.geometry.circle import CircleGeometry
from CelerisLab.body.objects import SimObject, SurfaceActuation
center = (24., 16.)
body = SimObject(0, geometry=CircleGeometry(*center, 2.15), center=center, radius=2.15)
jet = SurfaceActuation(.7, .85, .001).as_jet(center, 2.15)
with CudaContext(0) as context:
for shifted in (False, True):
for bodies in (False, True):
cfg = LBMConfig(nx=48, ny=32, viscosity=.1, velocity=.015, ddf_shifting=shifted)
reference = None
for block in ((32,48), (8,8), (4,1)):
with FixedPatchStepper(cfg, (8,6,40,26), context=context,
coarse_boundary="channel", base_block=block, fine_block=(8,8),
fine_objects=(body,) if bodies else (), jets=(jet,) if bodies else ()) as backend:
arrays = [state(*level.shape[1:]) for level in backend._levels]
backend.initialize(*arrays)
backend.step(4)
if bodies: backend.set_jet_amplitude(0, -.001)
backend.step(4)
raw = backend._download_raw()
physical = backend.download()
for a, mask in zip(raw.values, backend._state):
self.assertTrue(np.isfinite(a[:,mask]).all())
self.assertTrue(np.isfinite(raw.values[0][:,0,:]).all())
self.assertTrue(np.isnan(physical[0][:,0,:]).all())
backend._initialize_raw(raw)
for a,b in zip(raw.values, backend._download_raw().values):
np.testing.assert_array_equal(a,b)
if reference is None: reference = raw.values
else:
for a,b in zip(reference, raw.values): np.testing.assert_array_equal(a,b)
with self.assertRaisesRegex(ValueError, "periodic"):
backend.remap_child([])
if __name__ == "__main__": unittest.main()