Multipatch: FixedPatchSet2D host metadata and opt-in FixedPatchStepper path
Several separated (>= 4 coarse cells) body-free 2:1 patches share one level-wide fine grid stored by a sparse BlockLayout2D over the patch supports. FixedPatchSet2D re-addresses each FixedPatch2D's tables to level coordinates (same rows/child order, canonical patch order). The stepper accepts a sequence of rectangles as bounds (requires fine_block and base_block; rejects fine_objects/jets), reusing the existing blocked step sequence and kernels. Helpers use a covered mask instead of one rectangle and skip unstored fine holes. No kernel changes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
93d1cddd27
commit
4c8240ea13
3 files changed
+206
-13
No files matched your search
@@ -163,5 +163,72 @@ class FixedPatchTests(unittest.TestCase):
|
||||
p.check_synchronization(c, f, phase=phase)
|
||||
|
||||
|
||||
class FixedPatchSetTests(unittest.TestCase):
|
||||
"""Several separated patches: level-wide re-addressing of FixedPatch2D tables."""
|
||||
FACE = ((4, 4, 8, 12), (12, 4, 16, 12)) # 16x24 coarse, face gap 4
|
||||
S1 = ((4, 4, 12, 12), (20, 4, 28, 12)) # 16x32 coarse, translation copy
|
||||
|
||||
def build(self, rects, ny=16, nx=24):
|
||||
return refinement.FixedPatchSet2D.from_config(LBMConfig(nx=nx, ny=ny, viscosity=.1), rects)
|
||||
|
||||
def test_tables_are_shifted_single_patch_tables(self):
|
||||
s = self.build(self.FACE)
|
||||
fy, fx = s.fine_shape
|
||||
self.assertEqual((fy, fx), (32, 48))
|
||||
rows = cells = 0
|
||||
for p in s.patches:
|
||||
y0, x0 = p.fine_level_origin
|
||||
ly, lx = p.fine_shape
|
||||
np.testing.assert_array_equal(s.fine_kind[y0:y0 + ly, x0:x0 + lx], p.fine_kind)
|
||||
gy, gx = np.divmod(p.ghost_fine.astype(np.int64), lx)
|
||||
np.testing.assert_array_equal(s.ghost_fine[cells:cells + len(gy)],
|
||||
(gy + y0) * fx + gx + x0)
|
||||
np.testing.assert_array_equal(s.ghost_coarse[cells:cells + len(gy)], p.ghost_coarse)
|
||||
q, c = np.divmod(p.coalesce_fine.astype(np.int64), ly * lx)
|
||||
cy, cx = np.divmod(c, lx)
|
||||
np.testing.assert_array_equal(s.coalesce_fine[rows:rows + len(q)],
|
||||
q * fy * fx + (cy + y0) * fx + cx + x0)
|
||||
np.testing.assert_array_equal(s.coalesce_coarse[rows:rows + len(q)], p.coalesce_coarse)
|
||||
rows, cells = rows + len(q), cells + len(gy)
|
||||
self.assertEqual((rows, cells), (len(s.coalesce_coarse), len(s.ghost_fine)))
|
||||
self.assertEqual(int((s.fine_kind == ACTIVE).sum()), 4 * int(s.covered.sum()))
|
||||
|
||||
def test_input_order_is_irrelevant(self):
|
||||
a, b = self.build(self.FACE), self.build(self.FACE[::-1])
|
||||
for name in ("fine_kind", "ghost_fine", "ghost_coarse", "coalesce_coarse", "coalesce_fine"):
|
||||
np.testing.assert_array_equal(getattr(a, name), getattr(b, name))
|
||||
self.assertEqual(a.bounds, b.bounds)
|
||||
|
||||
def test_one_patch_set_matches_fixed_patch(self):
|
||||
p = FixedPatch2D.from_config(config(), (4, 4, 12, 12))
|
||||
s = self.build(((4, 4, 12, 12),), nx=16)
|
||||
np.testing.assert_array_equal(s.covered, p.covered)
|
||||
self.assertEqual((s.tau_coarse, s.tau_fine), (p.tau_coarse, p.tau_fine))
|
||||
np.testing.assert_array_equal(s.coalesce_coarse, p.coalesce_coarse)
|
||||
np.testing.assert_array_equal(s.ghost_coarse, p.ghost_coarse)
|
||||
|
||||
def test_gap_and_overlap_rejected(self):
|
||||
for gap in (0, 2, 3):
|
||||
with self.subTest(gap=gap), self.assertRaisesRegex(ValueError, "closer"):
|
||||
self.build(((4, 4, 8, 12), (8 + gap, 4, 12 + gap, 12)))
|
||||
with self.assertRaises(ValueError):
|
||||
self.build(())
|
||||
with self.assertRaises(ValueError): # FixedPatch2D margin still applies
|
||||
self.build(((1, 4, 8, 12), (12, 4, 16, 12)))
|
||||
|
||||
def test_sparse_fine_layout_stores_exactly_the_supports(self):
|
||||
for rects, nx in ((self.FACE, 24), (self.S1, 32)):
|
||||
s = self.build(rects, nx=nx)
|
||||
layout = s.fine_layout((4, 4))
|
||||
support = np.zeros(s.fine_shape, bool)
|
||||
for p in s.patches:
|
||||
y0, x0 = p.fine_level_origin
|
||||
support[y0:y0 + p.fine_shape[0], x0:x0 + p.fine_shape[1]] = True
|
||||
np.testing.assert_array_equal(layout.owned, support)
|
||||
self.assertTrue(layout.owned.ravel()[s.ghost_fine.astype(np.intp)].all())
|
||||
with self.assertRaisesRegex(ValueError, "aligned"):
|
||||
self.build(self.FACE).fine_layout((5, 4))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main(verbosity=2)
|
||||
Reference in new issue
Block a user