test: retire dense-route tests, require tiles everywhere (D2)

Retired with the dense refinement route (results recorded at 1573942 and
2469af7 or named as accepted losses):
- test_refinement_blocked_base: BlockedBaseGPU exact parity (3 tests),
  DenseBlockedRouteParity (6 tests incl. MRT and two bodies),
  DenseBlockedRouteCPU (capability matrix, ledger comparator);
- test_refinement_blocked_fine: BlockedFineGPU dense-vs-blocked (3 tests);
- test_refinement_mrt_gpu: test_dense_versus_blocked_mrt_two_level;
- test_raw_exchange_gpu: test_dense_fine_storage_reference (private
  link_exchange cut-link oracle, accepted loss);
- test_refinement_gpu: test_bounded_equivalence_and_ownership (dense
  CELERIS_REFINEMENT_AUDIT ownership counters, accepted loss); its physics
  stays in test_bounded_equivalence_blocked; eight_steps loses the audit
  branch and the misleading 'ownership/counts PASS' label;
- test_fine_surface_gpu: test_fine_surface_four_hops (dense per-hop flux
  ledger, CurvedLinkSoA reflection oracle, audit counts: accepted losses;
  raw exchange covers the boundary part only, not full flux closure) and the
  dense SoA fault tests (missing surface buffer, close frees SoA); blocked
  equivalents remain in test_refinement_blocked_fine/_base;
- test_raw_exchange.test_dense_observation_rejected_before_device_access.
New TileContractCPU: level_blocks omitted/None/wrong length/None entry/
wrong arity/non-integer/bool/non-positive/non-tuple/not dividing all raise
before CUDA; whole-level tiles reach CUDA. Rejection tests and fixtures pass
tiles; launch-order and kernel-text tests follow the blocked root;
examples/fixed_periodic_patch uses whole-level tiles (+ host tile test).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-27 15:12:20 +00:00
1 parent e18f7abe2b
commit 2b10b01c49
14 files changed
+84 -777

No files matched your search

@@ -3,36 +3,11 @@
Run CPU and GPU classes in separate processes. GPU callers must first check
resources/device availability; this file never selects or reserves a GPU.
Dense/blocked route parity (DenseBlockedRouteParity): the same configuration,
initial state, body/Jet schedule and tilings on three storage routes, only
level_blocks varying: D/D [None, None], D/B [None, FINE_BLOCK] (dense root,
the mixed pattern of current examples) and B/B [(12, 8), FINE_BLOCK]. Gate:
every public and owner array identical in FP32 bits and NaN masks at steps
0, 1, 2, 8 and after re-initialize; D/B and B/B raw-exchange ledgers equal.
No tolerance. A mismatch is reported with route pair/level/step and diagnosed
(boundary semantics vs arithmetic ordering), never widened.
Route capabilities (rejections checked before CUDA by test_route_capability_matrix):
combination D/D D/B B/B
periodic / slot_reservoir root, fine circle yes yes yes (parity gate)
actuated circle, velocity Jet + amplitude yes yes yes (parity gate)
raw/window exchange on a body level rejected yes yes
shifted storage rejected rejected yes
projected explosion rejected rejected yes
channel root rejected rejected yes
several L1 rectangles rejected rejected yes
depth >= 3 with a dense level rejected rejected yes
MRT, periodic root, circle/actuation/Jet yes yes yes (parity gate)
two fine bodies (Jet + passive, two actuated) yes yes yes (parity gate)
MRT with slot/reservoir root rejected on every route (on_site_velocity is SRT-only)
Slot/reservoir walls are compile-time WALL_* macros on a dense root and the
d_block_wall constant on a blocked root: BC-encoding parity, bit-identical by
this gate. Dense fine bodies use CurvedLinkSoA + CurvedBoundaryKernel
(n_objects > 0), blocked ones BlockCurvedBoundary; same links and physics,
compared through the population rows. MRT body-free is also covered by
test_refinement_mrt_gpu::test_dense_versus_blocked_mrt_two_level. Parked:
shifted and depth >= 3 (no dense route to compare).
Dense refinement storage was removed: every level is blocked and level_blocks
is required (TileContractCPU). The retired dense/blocked route parity gates
(BlockedBaseGPU exact parity, DenseBlockedRouteParity incl. MRT and two
bodies, the route capability matrix) passed and are recorded at 1573942 and
2469af7.
"""
from contextlib import ExitStack, contextmanager, nullcontext
from dataclasses import replace
@@ -51,7 +26,6 @@ from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY
from CelerisLab.lbm.refinement import ACTIVE, OUTER_GHOST, FixedPatch2D
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper, blocked_base_tables
from test_refinement_blocked_fine import NX, NY, BOUNDS, config, state, circle, actuation
from test_refinement_jets import SPEED, jet
BASE_BLOCKS = ((24, 32), (12, 8), (3, 4), (1, 1))
FINE_BLOCK = (16, 20)
@@ -298,224 +272,33 @@ class BlockedBaseCPU(unittest.TestCase):
allocation.free.assert_called_once_with()
class BlockedBaseGPU(unittest.TestCase):
def test_bodyfree_exact_parity_and_raw_invalid_slots(self):
self.compare([(None, ((24, 32), (3, 4), (1, 1)), 8)])
class TileContractCPU(unittest.TestCase):
"""level_blocks is required: one positive (by, bx) tile per level, checked before CUDA."""
def test_actuated_circle_exact_parity_and_raw_invalid_slots(self):
self.compare([(.002, ((12, 8),), 8), (-.002, ((12, 8),), 8)])
def test_non_power_two_slot_width_exact_parity(self):
self.compare([(None, ((3, 4),), 7), (None, ((3, 4),), 10)], ("slot_reservoir",))
def compare(self, cases, modes=("periodic", "slot_reservoir")):
from CelerisLab.cuda.context import CudaContext
expected_source = Path(__file__).resolve().parents[3] / "src/CelerisLab/lbm/refinement_stepper.py"
self.assertEqual(Path(refinement_stepper.__file__).resolve(), expected_source)
with CudaContext(0) as context:
for mode in modes:
for speed, layouts, width in cases:
cfg = config(mode)
if mode == "slot_reservoir":
cfg = replace(cfg, wall_slot_width=width)
p = FixedPatch2D.from_config(cfg, BOUNDS)
arrays = state(NY, NX, 1), state(*p.fine_shape, 2)
reference = None
for base_block in (None, *layouts):
label = f"{mode} width={width} speed={speed} base={base_block}"
snapshots = []
with FixedPatchStepper(cfg, context=context, coarse_boundary=mode,
refinements=[BOUNDS], level_blocks=[base_block, FINE_BLOCK],
level_objects=[(), () if speed is None
else (circle(actuation(speed)),)]) as backend:
backend.initialize(*arrays)
for step in (0, 1, 2, 8):
if step == 1 and base_block is not None:
# Blocked staging dirties invalid input slots; its output
# must poison them. Dense parent does not stage there.
coarse = backend._levels[0]
spare = coarse.layout.to_blocked(np.full(coarse.shape, 42, np.float32))
cuda.memcpy_htod(coarse.spare, spare)
backend.step(step - backend.step_count)
public, owner = backend.download(), raw(backend)
self.assertEqual(backend.step_count, step)
self.assertTrue(np.isnan(owner[0][:, 6:18, 8:24]).all(), label)
if mode != "periodic":
self.assertTrue(np.isnan(owner[0][:, (0, -1), :]).all(), label)
snapshots.append(public + owner)
backend.initialize(*arrays)
self.assertEqual(backend.step_count, 0)
for actual, expected in zip(backend.download() + raw(backend), snapshots[0]):
exact(self, actual, expected, label + " reset")
if reference is None:
reference = snapshots
else:
for step, actual, expected in zip((0, 1, 2, 8), snapshots, reference):
for index, (a, b) in enumerate(zip(actual, expected)):
exact(self, a, b, f"{label} step={step} array={index}")
print(f"PASS {label}: full finite bits/NaN masks at 0,1,2,8 + reset", flush=True)
ROUTES = (("D/D", [None, None]), ("D/B", [None, FINE_BLOCK]), ("B/B", [(12, 8), FINE_BLOCK]))
# Two-body rows: patch (4, 4, 28, 20) gives a 40 x 56 fine window, tiled (8, 8).
WIDE_BOUNDS = (4, 4, 28, 20)
WIDE_ROUTES = (("D/D", [None, None]), ("D/B", [None, (8, 8)]), ("B/B", [(12, 8), (8, 8)]))
LEFT, RIGHT, RADIUS = (12.0, 12.0), (20.0, 12.0), 2.15
def body(ident, center, spec=None):
from CelerisLab.body.geometry.circle import CircleGeometry
from CelerisLab.body.objects import SimObject
return SimObject(ident, geometry=CircleGeometry(*center, RADIUS), center=center, radius=RADIUS,
surface_actuation=spec)
TWO_BODY_CASES = (
("jet + passive", lambda: [body(0, LEFT), body(1, RIGHT)],
lambda: [actuation(SPEED).as_jet(LEFT, RADIUS)], -SPEED),
("actuated +/-", lambda: [body(0, LEFT, actuation(SPEED)), body(1, RIGHT, actuation(-SPEED))],
lambda: [], None),
)
def same_summary(a, b):
"""Exact equality of nested exchange summaries (dicts, sequences, arrays, numbers)."""
if isinstance(a, dict):
return a.keys() == b.keys() and all(same_summary(a[k], b[k]) for k in a)
if isinstance(a, (list, tuple)):
return len(a) == len(b) and all(same_summary(x, y) for x, y in zip(a, b))
a, b = np.asarray(a), np.asarray(b)
if a.dtype.kind in "biufc" and b.dtype.kind in "biufc":
return a.shape == b.shape and np.array_equal(a, b, equal_nan=True)
return a.dtype == b.dtype and np.array_equal(a, b) # labels such as the window name
class DenseBlockedRouteCPU(unittest.TestCase):
def test_same_summary_compares_labels_and_every_number(self):
ledger = {"window": "last_base_step", "time_start": 3, "total": {"force": [1.0, -2.0],
"torque": 0.5}, "aperture": {"force": np.array([np.nan, 1.0])}}
self.assertTrue(same_summary(ledger, replace_value(ledger, ())))
for path, value in ((("window",), "last_substep"), (("time_start",), 4),
(("total", "force"), [1.0, -2.000001]), (("total", "torque"), 0.25),
(("aperture", "force"), np.array([0.0, 1.0]))):
with self.subTest(path=path):
self.assertFalse(same_summary(ledger, replace_value(ledger, path, value)))
self.assertFalse(same_summary(replace_value(ledger, path, value), ledger))
def test_route_capability_matrix(self):
def test_level_blocks_required_and_validated(self):
context = Mock(activate=Mock(side_effect=AssertionError("CUDA reached")))
periodic = config("periodic")
inner = (BOUNDS[0] + 2, BOUNDS[1] + 2, BOUNDS[2] - 2, BOUNDS[3] - 2)
second = (BOUNDS[0], BOUNDS[1], BOUNDS[0] + 2, BOUNDS[1] + 2)
cases = (
("raw exchange on dense body level", periodic, dict(
level_blocks=[None, None], level_objects=[(), (circle(),)], observe_raw_exchange=True)),
("shifted D/D", replace(periodic, ddf_shifting=True), dict(level_blocks=[None, None])),
("shifted D/B", replace(periodic, ddf_shifting=True), dict(level_blocks=[None, FINE_BLOCK])),
("projected D/D", periodic, dict(level_blocks=[None, None], explosion="projected_linear")),
("projected D/B", periodic, dict(level_blocks=[None, FINE_BLOCK], explosion="projected_linear")),
("channel D/B", periodic, dict(level_blocks=[None, FINE_BLOCK], coarse_boundary="channel")),
("several L1 rectangles", periodic, dict(refinements=[[BOUNDS, second]], level_blocks=[None, None])),
("depth 3 dense", periodic, dict(refinements=[BOUNDS, inner], level_blocks=[None, None, None])),
)
for label, cfg, kw in cases:
kw.setdefault("refinements", [BOUNDS])
cfg = config("periodic")
for label, kw in (("omitted", {}),
("None list", dict(level_blocks=None)),
("too few levels", dict(level_blocks=[(12, 8)])),
("too many levels", dict(level_blocks=[(12, 8), FINE_BLOCK, FINE_BLOCK])),
("None root", dict(level_blocks=[None, FINE_BLOCK])),
("None fine", dict(level_blocks=[(12, 8), None])),
("wrong arity", dict(level_blocks=[(12,), FINE_BLOCK])),
("non-integer", dict(level_blocks=[(12, 8.0), FINE_BLOCK])),
("bool", dict(level_blocks=[(True, 8), FINE_BLOCK])),
("non-positive", dict(level_blocks=[(0, 8), FINE_BLOCK])),
("non-tuple", dict(level_blocks=[12, FINE_BLOCK])),
("not dividing", dict(level_blocks=[(7, 8), FINE_BLOCK]))):
with self.subTest(label), self.assertRaises(ValueError):
FixedPatchStepper(cfg, context=context, **kw)
FixedPatchStepper(cfg, context=context, refinements=[BOUNDS], **kw)
with self.assertRaisesRegex(ValueError, "whole-level tile"):
FixedPatchStepper(cfg, context=context, refinements=[BOUNDS])
context.activate.assert_not_called()
def replace_value(tree, path, value=None):
"""Deep copy of a nested dict with the item at ``path`` replaced (no change for ())."""
if isinstance(tree, dict):
return {k: (value if path == (k,) else replace_value(v, path[1:], value) if path[:1] == (k,)
else replace_value(v, (), None)) for k, v in tree.items()}
return np.array(tree, copy=True) if isinstance(tree, np.ndarray) else tree
class DenseBlockedRouteParity(unittest.TestCase):
"""D/D vs D/B vs B/B, unshifted SRT, one circle or one velocity Jet; see module docstring."""
def run_routes(self, mode, objects=(), jets=(), switch=None, cfg=None, bounds=BOUNDS, routes=ROUTES):
from CelerisLab.cuda.context import CudaContext
cfg = cfg or config(mode)
p = FixedPatch2D.from_config(cfg, bounds)
arrays = state(NY, NX, 1), state(*p.fine_shape, 2)
bodies = sorted({obj.obj_id for obj in objects})
results = {}
with CudaContext(0) as context:
for name, blocks in routes:
observe = bool(objects) and blocks[1] is not None
snapshots, ledgers = [], []
with FixedPatchStepper(cfg, context=context, coarse_boundary=mode, refinements=[bounds],
level_blocks=blocks, level_objects=[(), tuple(objects)],
level_jets=[(), tuple(jets)],
observe_raw_exchange=observe) as backend:
backend.initialize(*arrays)
for step in (0, 1, 2, 8):
if step == 8 and switch is not None:
backend.set_jet_amplitude(0, switch)
backend.step(step - backend.step_count)
snapshots.append(backend.download() + raw(backend))
if observe and step:
ledgers.append(tuple(backend.read_raw_exchange(body, window=window)
for body in bodies
for window in ("last_base_step", "last_substep")))
backend.initialize(*arrays)
snapshots.append(backend.download() + raw(backend))
results[name] = snapshots, ledgers
return results
def check(self, label, results):
reference, _ = results["D/D"]
for name in ("D/B", "B/B"):
for step, actual, expected in zip((0, 1, 2, 8, "reset"), results[name][0], reference):
for index, (a, b) in enumerate(zip(actual, expected)):
exact(self, a, b, f"{label} {name} vs D/D step={step} array={index}")
if results["D/B"][1]:
for step, a, b in zip((1, 2, 8), results["D/B"][1], results["B/B"][1]):
self.assertTrue(same_summary(a, b), f"{label} raw exchange D/B vs B/B step={step}")
for name, (snapshots, _) in results.items():
masses = [float(np.nansum(level.astype(np.float64))) for level in snapshots[3][:2]]
print(f"{label} {name}: step 8 mass by level {masses}", flush=True)
print(f"PASS {label}: D/D = D/B = B/B bits/NaN masks at 0,1,2,8 + reset", flush=True)
def test_body_free(self):
for mode in ("periodic", "slot_reservoir"):
self.check(f"{mode} body-free", self.run_routes(mode))
def test_passive_circle(self):
for mode in ("periodic", "slot_reservoir"):
self.check(f"{mode} circle", self.run_routes(mode, objects=[circle()]))
def test_actuated_circle(self):
for mode in ("periodic", "slot_reservoir"):
for speed in (SPEED, -SPEED):
self.check(f"{mode} actuated {speed}",
self.run_routes(mode, objects=[circle(actuation(speed))]))
def test_velocity_jet_with_amplitude_switch(self):
for mode in ("periodic", "slot_reservoir"):
self.check(f"{mode} jet switch",
self.run_routes(mode, objects=[circle()], jets=[jet()], switch=-SPEED))
def test_mrt_bodies(self):
"""MRT, periodic root (on_site_velocity slot/reservoir is SRT-only on every route)."""
cfg = replace(config("periodic"), collision="MRT")
for label, objects, jets, switch in (("circle", [circle()], (), None),
("actuated +", [circle(actuation(SPEED))], (), None),
("actuated -", [circle(actuation(-SPEED))], (), None),
("jet switch", [circle()], [jet()], -SPEED)):
self.check(f"MRT periodic {label}",
self.run_routes("periodic", objects, jets, switch, cfg=cfg))
def test_two_bodies(self):
"""Two circles on a wider patch (same tiles on every route): Jet + passive, two actuated."""
for mode in ("periodic", "slot_reservoir"):
for label, objects, jets, switch in TWO_BODY_CASES:
self.check(f"{mode} two bodies {label}",
self.run_routes(mode, objects(), jets(), switch, bounds=WIDE_BOUNDS,
routes=WIDE_ROUTES))
# The documented whole-level tiles are always valid (construction reaches CUDA).
whole = [(NY, NX), FixedPatch2D.from_config(cfg, BOUNDS).fine_shape]
with self.assertRaisesRegex(AssertionError, "CUDA reached"):
FixedPatchStepper(cfg, context=context, refinements=[BOUNDS], level_blocks=whole)
if __name__ == "__main__":