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:
1 parent
f1dcf329ff
commit
64665319a4
5 files changed
+152
-10
No files matched your search
@@ -233,6 +233,56 @@ class FixedPatchSetTests(unittest.TestCase):
|
||||
self.build(self.FACE).fine_layout(block)
|
||||
|
||||
|
||||
class PatchSetBoundaryTests(unittest.TestCase):
|
||||
"""fine_objects/jets on a level-wide patch set (16x32, patches 4 cells apart)."""
|
||||
PATCHES = ((2, 2, 14, 14), (18, 2, 30, 14))
|
||||
|
||||
@staticmethod
|
||||
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)
|
||||
|
||||
def prepare(self, nx, rects, objects, jets=()):
|
||||
cfg = LBMConfig(nx=nx, ny=16, viscosity=.1)
|
||||
s = refinement.FixedPatchSet2D.from_config(cfg, rects)
|
||||
return s, refinement.prepare_fine_boundary(cfg, s, objects, jets=jets)
|
||||
|
||||
def test_two_bodies_are_the_one_body_case_translated(self):
|
||||
_, one = self.prepare(16, self.PATCHES[:1], [self.body(8., 8.)])
|
||||
_, two = self.prepare(32, self.PATCHES, [self.body(8., 8., 7), self.body(24., 8., 7)])
|
||||
f1, f2 = one.flags.reshape(32, 32), two.flags.reshape(32, 64)
|
||||
np.testing.assert_array_equal(f2[:, :32], f1)
|
||||
np.testing.assert_array_equal(f2[:, 32:], f1)
|
||||
n = len(one.curved_host[0])
|
||||
self.assertEqual(len(two.curved_host[0]), 2 * n)
|
||||
body_id = two.curved_host[3]
|
||||
self.assertEqual(sorted(set(body_id.tolist())), [0, 1]) # positions, not obj_id 7
|
||||
# Translation must reproduce identical link geometry (prerequisite of the
|
||||
# bitwise GPU tiling gate): dirs, q and fallback equal, receivers shifted.
|
||||
fidx, dirs, q, fallback = (two.curved_host[i] for i in (0, 1, 2, 7))
|
||||
for column in (dirs, q, fallback):
|
||||
np.testing.assert_array_equal(column[:n], column[n:])
|
||||
y, x = np.divmod(fidx.astype(np.int64), 64)
|
||||
np.testing.assert_array_equal(y[:n], y[n:])
|
||||
np.testing.assert_array_equal(x[:n] + 32, x[n:])
|
||||
|
||||
def test_body_outside_a_single_patch_rejected(self):
|
||||
for x in (16., 13.): # in the gap / crossing the patch edge
|
||||
with self.subTest(x=x), self.assertRaises(ValueError):
|
||||
self.prepare(32, self.PATCHES, [self.body(x, 8.)])
|
||||
|
||||
def test_one_jet_over_two_bodies_rejected_even_at_zero_amplitude(self):
|
||||
from CelerisLab.body.jets import Jet, Parallel, Tube
|
||||
bodies = [self.body(8., 8.), self.body(24., 8.)]
|
||||
span = Jet(Tube((5., 8.), 0., 22., .6), Parallel(0.), amplitude=0.)
|
||||
with self.assertRaisesRegex(ValueError, "one jet"):
|
||||
self.prepare(32, self.PATCHES, bodies, jets=[span])
|
||||
own = [Jet(Tube((5., 8.), 0., 6., .6), Parallel(0.), amplitude=.002),
|
||||
Jet(Tube((21., 8.), 0., 6., .6), Parallel(0.), amplitude=.002)]
|
||||
self.prepare(32, self.PATCHES, bodies, jets=own) # one jet per body: accepted
|
||||
|
||||
|
||||
class NestPatchTests(unittest.TestCase):
|
||||
"""Three-level single chain: L2 bounds in L1 window cells (probe fixture)."""
|
||||
def setUp(self):
|
||||
|
||||
Reference in new issue
Block a user