Multipatch: fine_objects and jets on several patches (level-wide projection)
prepare_fine_boundary accepts a FixedPatchSet2D: objects are projected once onto the level-wide fine grid (FixedPatchSet2D.fine_origin = (0, 0), same coarse cell-edge -> fine cell transform), each circle must lie inside one patch, coarse transfer cells are checked against the covered union, and body ids stay object positions, so the existing jet one-body-per-jet, overlap and (receiver, dir) writer checks run once over the whole level (caller obj_id cannot bypass them). The stepper no longer rejects fine_objects/jets with several patches; blocked link tables, BFL and the jet amplitude cache are reused unchanged. No kernel change. README/docstring scope updated. Tests: CPU translation/ids/rejections (outside one patch, one jet over two bodies at zero amplitude); GPU one body via the new path == single-patch path (both modes), two bodies (+jets, amplitude switch) == tiled single (bitwise). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f1dcf329ff
commit
64665319a4
@@ -53,6 +53,13 @@ class MultipatchCPU(unittest.TestCase):
|
||||
with self.assertRaisesRegex(ValueError, "closer"):
|
||||
FixedPatchStepper(cfg, ((4, 4, 8, 12), (10, 4, 14, 12)), context=context,
|
||||
fine_block=(8, 4), base_block=(4, 4))
|
||||
from CelerisLab.body.jets import Jet, Parallel, Tube
|
||||
cfg32 = LBMConfig(nx=32, ny=16, viscosity=.1)
|
||||
span = Jet(Tube((5., 8.), 0., 22., .6), Parallel(0.), amplitude=0.)
|
||||
with self.assertRaisesRegex(ValueError, "one jet"): # same obj_id cannot bypass it
|
||||
FixedPatchStepper(cfg32, ((2, 2, 14, 14), (18, 2, 30, 14)), context=context,
|
||||
fine_block=(4, 4), base_block=(4, 4),
|
||||
fine_objects=[body(8., 8.), body(24., 8.)], jets=[span])
|
||||
|
||||
|
||||
class MultipatchGPU(unittest.TestCase):
|
||||
@@ -144,5 +151,76 @@ class MultipatchGPU(unittest.TestCase):
|
||||
print(f"face gap4 step {step}: weighted leaf mass {mass:.9g} (diagnostic)", flush=True)
|
||||
|
||||
|
||||
def body(x, y, obj_id=0):
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
return SimObject(obj_id, geometry=CircleGeometry(x, y, 2.15), center=(x, y), radius=2.15)
|
||||
|
||||
|
||||
class MultipatchBodiesGPU(unittest.TestCase):
|
||||
"""fine_objects/jets on several patches (frozen gates, bitwise)."""
|
||||
PATCHES = ((2, 2, 14, 14), (18, 2, 30, 14))
|
||||
STEPS = (0, 1, 2, 8)
|
||||
|
||||
def run_steps(self, context, cfg, bounds, coarse, fine, switch=None, **kw):
|
||||
out = []
|
||||
with FixedPatchStepper(cfg, bounds, context=context, **kw) as backend:
|
||||
backend.initialize(coarse, fine)
|
||||
for step in self.STEPS:
|
||||
if switch is not None and step == 8:
|
||||
for index, amplitude in switch:
|
||||
backend.set_jet_amplitude(index, amplitude)
|
||||
backend.step(step - backend.step_count)
|
||||
out.append(backend.download() + raw(backend))
|
||||
return out
|
||||
|
||||
def test_one_body_matches_single_patch_path(self):
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
with CudaContext(0) as context:
|
||||
for mode in ("periodic", "slot_reservoir"):
|
||||
cfg = config(mode)
|
||||
p = FixedPatch2D.from_config(cfg, BOUNDS)
|
||||
s = FixedPatchSet2D.from_config(cfg, (BOUNDS,))
|
||||
coarse, fine = state(NY, NX, 1), state(*p.fine_shape, 2)
|
||||
kw = dict(coarse_boundary=mode, fine_block=(8, 4), base_block=(3, 4),
|
||||
fine_objects=[circle()])
|
||||
old = self.run_steps(context, cfg, BOUNDS, coarse, fine, **kw)
|
||||
new = self.run_steps(context, cfg, (BOUNDS,), coarse, level_fine(s, [fine]), **kw)
|
||||
for step, a, b in zip(self.STEPS, old, new):
|
||||
label = f"{mode} step {step}"
|
||||
exact(self, b[0], a[0], label + " coarse")
|
||||
exact(self, window(s, 0, b[1]), a[1], label + " fine")
|
||||
exact(self, window(s, 0, b[3]), a[3], label + " raw fine")
|
||||
|
||||
def test_two_bodies_and_jets_equal_tiled_single(self):
|
||||
"""Translation copy across a block seam, with jets and an amplitude switch."""
|
||||
from CelerisLab.body.jets import Jet, Parallel, Tube
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
cfg16 = LBMConfig(nx=16, ny=16, viscosity=.1)
|
||||
cfg32 = replace(cfg16, nx=32)
|
||||
one = FixedPatchSet2D.from_config(cfg16, self.PATCHES[:1])
|
||||
two = FixedPatchSet2D.from_config(cfg32, self.PATCHES)
|
||||
coarse, fine = state(16, 16, 3), state(*one.patches[0].fine_shape, 4)
|
||||
jet = lambda x: Jet(Tube((x, 8.), 0., 6., .6), Parallel(0.), amplitude=.002)
|
||||
kw = dict(fine_block=(4, 4), base_block=(4, 4))
|
||||
with CudaContext(0) as context:
|
||||
for jets in ((), (jet(5.),)):
|
||||
ref = self.run_steps(context, cfg16, self.PATCHES[:1], coarse, level_fine(one, [fine]),
|
||||
switch=[(0, -.001)] if jets else None,
|
||||
fine_objects=[body(8., 8.)], jets=jets, **kw)
|
||||
both = jets and (jet(5.), jet(21.))
|
||||
got = self.run_steps(context, cfg32, self.PATCHES, np.concatenate([coarse, coarse], 2),
|
||||
level_fine(two, [fine, fine]),
|
||||
switch=[(0, -.001), (1, -.001)] if jets else None,
|
||||
fine_objects=[body(8., 8., 5), body(24., 8., 5)],
|
||||
jets=both or (), **kw)
|
||||
for step, a, b in zip(self.STEPS, ref, got):
|
||||
label = f"jets={len(jets)} step {step}"
|
||||
exact(self, b[0], np.concatenate([a[0], a[0]], axis=2), label + " coarse")
|
||||
for i in (0, 1):
|
||||
exact(self, window(two, i, b[1]), window(one, 0, a[1]), f"{label} fine {i}")
|
||||
exact(self, window(two, i, b[3]), window(one, 0, a[3]), f"{label} raw fine {i}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
Reference in New Issue
Block a user