test(D1): refinement followup checks run on all-blocked storage

Every FixedPatchStepper in test_refinement_followup passes explicit tiles
(whole-level tiles on the GPU cases, (1, 1) in the pre-CUDA margin
contract). Assertions kept; dense-specific probes rewritten:
- fault fakes use a blocked BlockLayout2D instead of _layouts [None, None];
- slot/reservoir: the fine module sees its tile window incl. halo
  (fx+2, fy+2) with slot fields zeroed; the root's walls are
  block_root_bc 1 (fine 0) instead of dense Y_WALL_BC/TOP_WALL_BC macros;
- raw owner storage is read through each level's layout (to_dense);
- the off-centre allocation bound counts blocked buffers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Claude
2026-10-27 10:44:05 +00:00
co-authored by Claude Opus 5.5
parent 2ccd7d576e
commit ce393fa468
@@ -17,6 +17,7 @@ import pycuda.driver as cuda
import CelerisLab
from CelerisLab.lbm import refinement_stepper as implementation
from CelerisLab.lbm.blocks import BlockLayout2D
from test_refinement_gpu import (ROOT, POP_ATOL, MOMENT_ATOL, ref, postcollision,
totals, LBMConfig, CudaContext, compiler,
PeriodicPatchStepper)
@@ -83,6 +84,21 @@ def scalar_interval(coarse, fine, bounds, slot=None):
return result, current
def whole_tiles(cfg, bounds):
"""One whole-level tile per level: (ny, nx) at the root, the fine window below."""
return [(cfg.ny, cfg.nx), implementation.FixedPatch2D.from_config(cfg, bounds).fine_shape]
def owner_arrays(backend):
"""Raw owner storage of every level as dense (9, ny, nx), before public masking."""
arrays = []
for level in backend._levels:
blocked = np.empty(level.layout.blocked_shape, np.float32)
cuda.memcpy_dtoh(blocked, level.current)
arrays.append(level.layout.to_dense(blocked))
return arrays
class FaultGuardsCPU(unittest.TestCase):
def test_boundary_mode_and_margin_contract(self):
cfg = LBMConfig(nx=32, ny=24, viscosity=.1, velocity=0, wall_normal_velocity=.001,
@@ -95,7 +111,7 @@ class FaultGuardsCPU(unittest.TestCase):
with self.subTest(mode=mode, bounds=bounds):
with self.assertRaises(ValueError):
implementation.FixedPatchStepper(cfg, context=context, coarse_boundary=mode,
refinements=[bounds])
refinements=[bounds], level_blocks=[(1, 1)] * 2)
context.activate.assert_not_called()
# Both exact channel limits and the original periodic two-cell margin
# must reach context activation, with no allocation or device required.
@@ -104,18 +120,19 @@ class FaultGuardsCPU(unittest.TestCase):
with self.subTest(mode=mode, bounds=bounds):
with self.assertRaisesRegex(RuntimeError, "validated before CUDA"):
implementation.FixedPatchStepper(cfg, context=context, coarse_boundary=mode,
refinements=[bounds])
refinements=[bounds], level_blocks=[(1, 1)] * 2)
def fake_backend(self):
backend = PeriodicPatchStepper.__new__(PeriodicPatchStepper)
backend._layouts = [None, None] # dense storage on both levels
layout = BlockLayout2D.build(1, 1, 1, 1, periodic=(False, False))
backend._layouts = [layout, layout] # blocked storage on both levels
backend._ddf_shifting = False # unshifted on every level, as _check_storage expects
backend._level_cfgs = [LBMConfig(nx=16, ny=16, viscosity=.1)] * 2 # ddf_shifting off
backend.ctx = Mock(activate=nullcontext)
backend._lock = RLock()
backend._status = "ready"
backend._stream = Mock()
backend._levels = [Mock(shape=(9, 1, 1), layout=None), Mock(shape=(9, 1, 1), layout=None)]
backend._levels = [Mock(shape=(9, 1, 1), layout=layout), Mock(shape=(9, 1, 1), layout=layout)]
backend._allocations = [Mock(), Mock()]
return backend
@@ -158,7 +175,8 @@ class FaultGuardsCPU(unittest.TestCase):
context = Mock(activate=nullcontext, sm_arch="sm_70")
with patch.object(implementation, "HALO", 2):
with self.assertRaisesRegex(RuntimeError, "four-cell"):
PeriodicPatchStepper(cfg, context=context, refinements=[(4, 4, 12, 12)])
PeriodicPatchStepper(cfg, context=context, refinements=[(4, 4, 12, 12)],
level_blocks=[(16, 16), (24, 24)])
p = implementation.FixedPatch2D.from_config(cfg, (4, 4, 12, 12))
from CelerisLab.lbm import hierarchy
bad = patch.object(implementation.FixedPatch2D, "from_config",
@@ -169,10 +187,12 @@ class FaultGuardsCPU(unittest.TestCase):
with patch.object(cuda, "Stream"), patch.object(cuda, "mem_get_info", return_value=(1 << 40, 1 << 40)), bad, patch.object(cuda, "mem_alloc") as alloc, \
patch.object(cuda, "memcpy_htod") as upload:
with self.assertRaisesRegex(RuntimeError, "config chain is inconsistent"):
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds])
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds],
level_blocks=[(16, 16), (24, 24)])
with patch.object(hierarchy, "_check_chain"), \
self.assertRaisesRegex(RuntimeError, "omega/tau"):
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds])
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds],
level_blocks=[(16, 16), (24, 24)])
alloc.assert_not_called()
upload.assert_not_called()
module = Mock()
@@ -182,7 +202,8 @@ class FaultGuardsCPU(unittest.TestCase):
patch.object(compiler, "load_module", return_value=module), \
patch.object(cuda, "memcpy_htod") as upload:
with self.assertRaisesRegex(RuntimeError, "layout mismatch"):
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds])
PeriodicPatchStepper(cfg, context=context, refinements=[p.bounds],
level_blocks=[(16, 16), (24, 24)])
upload.assert_not_called()
@@ -250,23 +271,28 @@ class NormalBuildGPU(unittest.TestCase):
parts = line.split()
if len(parts) == 3 and parts[0] == "#define":
definitions[parts[1]] = parts[2]
generated.append((metadata["config"], definitions))
generated.append((metadata["config"], definitions, build))
return build
max_error = max_bc_error = 0.0
with CudaContext(0) as context:
with patch.object(compiler, "generate_config", side_effect=record_config):
backend = implementation.FixedPatchStepper(cfg, context=context, refinements=[bounds],
coarse_boundary="slot_reservoir")
coarse_boundary="slot_reservoir",
level_blocks=whole_tiles(cfg, bounds))
with backend:
self.assertEqual(len(generated), 2)
fine_config, fine_defines = generated[1]
self.assertEqual((fine_config["nx"], fine_config["ny"]), (fx, fy))
fine_config, fine_defines, _ = generated[1]
# Blocked kernels see one tile window including the 1-cell halo.
self.assertEqual((fine_config["nx"], fine_config["ny"]), (fx+2, fy+2))
for name in ("Y_WALL_BC", "TOP_WALL_BC", "WALL_SLOT_WIDTH",
"WALL_NORMAL_VELOCITY", "TOP_WALL_NORMAL_VELOCITY"):
self.assertEqual(float(fine_defines[name].removesuffix("f")), 0, name)
self.assertEqual(int(generated[0][1]["Y_WALL_BC"]), 2)
self.assertEqual(int(generated[0][1]["TOP_WALL_BC"]), 1)
# The root's physical slot/reservoir walls come from BLOCK_ROOT_BC=1
# (module constant d_block_wall); the fine level has none.
root_meta = json.loads((Path(generated[0][2])/".celeris-build").read_text())
fine_meta = json.loads((Path(generated[1][2])/".celeris-build").read_text())
self.assertEqual((root_meta["block_root_bc"], fine_meta["block_root_bc"]), (1, 0))
# Independently derive all target links, including the eight
# whose coarse sources are uncovered, from physical midpoints.
targets, corners = set(), set()
@@ -286,9 +312,7 @@ class NormalBuildGPU(unittest.TestCase):
expected = scalar_interval(*expected, bounds, slot=(amplitude, width))
backend.step()
self.assertEqual(backend.step_count, step)
raw = [np.empty(a.shape, np.float32) for a in initial]
for level, array in zip(backend._levels, raw):
cuda.memcpy_dtoh(array, level.current)
raw = owner_arrays(backend)
# Read raw storage before masking: active NaNs cannot be
# hidden by the public download's non-leaf cleanup.
self.assertTrue(np.isfinite(raw[0][:, coarse_leaf]).all())
@@ -327,7 +351,7 @@ class NormalBuildGPU(unittest.TestCase):
max_pop = max_drift = max_moment = 0.0
with CudaContext(0) as context, PeriodicPatchStepper(
LBMConfig(nx=16, ny=16, viscosity=.1), context=context,
refinements=[(4, 4, 12, 12)]) as backend:
refinements=[(4, 4, 12, 12)], level_blocks=[(16, 16), (24, 24)]) as backend:
backend.initialize(*postcollision(state, spec, maps))
before = totals(backend.download(), maps)
for step in range(1, 9):
@@ -358,17 +382,17 @@ class NormalBuildGPU(unittest.TestCase):
fine_mask[4:-4, 4:-4] = True
initial[1][:, ~fine_mask] = np.nan
expected = scalar_interval(*[a.astype(np.float64) for a in initial], bounds)
with CudaContext(0) as context, PeriodicPatchStepper(cfg, context=context, refinements=[bounds]) as backend:
with CudaContext(0) as context, PeriodicPatchStepper(cfg, context=context, refinements=[bounds],
level_blocks=[(13, 17), (14, 16)]) as backend:
self.assertEqual(backend.patch.fine_shape, (14, 16))
p = backend.patch
allocated = 2 * 9 * (17*13+16*14) * 4 + sum(a.nbytes for a in
(p.ghost_fine, p.ghost_coarse, p.coalesce_coarse, p.coalesce_fine))
allocated = 2 * 4 * sum(int(np.prod(level.layout.blocked_shape)) for level in backend._levels) \
+ sum(a.nbytes for a in (p.ghost_fine, p.ghost_coarse, p.coalesce_coarse, p.coalesce_fine))
self.assertLess(allocated, 128*1024)
backend.initialize(*initial)
backend.step()
coarse, fine = backend.download()
raw_fine = np.empty((9, 14, 16), np.float32)
cuda.memcpy_dtoh(raw_fine, backend._levels[1].current)
raw_fine = owner_arrays(backend)[1]
max_error = 0.0
for got, want in zip((coarse, raw_fine), expected):
np.testing.assert_array_equal(np.isnan(got), np.isnan(want))