lbm/hierarchy.py loops the existing pair logic (FixedPatch2D/FixedPatchSet2D for L0->L1, nest_patch/nest_patch_set for deeper pairs, _middle_config for the per-level config chain) and returns validated per-level configs, pair tables and composed kinds for any depth. Legacy scalar window forms are accepted so existing public shapes are unchanged. Validates each config copy because LBMConfig.omega is only computed by validate(). Not yet used by the stepper. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95 lines
4.6 KiB
Python
95 lines
4.6 KiB
Python
"""CPU geometry tests for lbm.hierarchy (no CUDA). Geometry construction only, not LBM numerics."""
|
|
import unittest
|
|
|
|
import numpy as np
|
|
from CelerisLab.config import LBMConfig
|
|
from CelerisLab.lbm import refinement as r
|
|
from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition
|
|
|
|
CFG = LBMConfig(nx=48, ny=48, viscosity=.02)
|
|
L1 = [(6, 6, 26, 26), (32, 30, 42, 42)] # L0 cells; L1 ACTIVE x/y 12..52 and x 64..84, y 60..84
|
|
L2 = [(20, 20, 44, 44), (72, 68, 76, 76)] # L1 cells; L2 ACTIVE 40..88 and x 144..152, y 136..152
|
|
L3 = [(56, 56, 72, 72)] # L2 cells
|
|
FOUR = [L1, L2, L3]
|
|
SMALL = LBMConfig(nx=16, ny=16, viscosity=.1)
|
|
|
|
|
|
class FourLevels(unittest.TestCase):
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
cls.h = build_hierarchy(CFG, FOUR)
|
|
|
|
def test_config_chain(self):
|
|
self.assertEqual(self.h.depth, 4)
|
|
for level, c in enumerate(self.h.configs):
|
|
self.assertEqual((c.ny, c.nx), (48 << level, 48 << level))
|
|
self.assertAlmostEqual(c.viscosity, .02 * 2 ** level, places=12)
|
|
self.assertEqual(self.h.kinds[level].shape, (c.ny, c.nx))
|
|
for level, p in enumerate(self.h.pairs):
|
|
self.assertAlmostEqual(p.tau_coarse, 1 / self.h.configs[level].omega, places=12)
|
|
self.assertAlmostEqual(p.tau_fine, 1 / self.h.configs[level + 1].omega, places=12)
|
|
|
|
def test_partition_and_composition(self):
|
|
h = self.h
|
|
np.testing.assert_array_equal(leaf_partition(h), np.ones((48, 48)))
|
|
for level in (1, 2):
|
|
covered, raw = h.pairs[level].covered, h.pairs[level - 1].fine_kind
|
|
self.assertTrue((h.kinds[level][covered] == r.OUTER_GHOST).all())
|
|
np.testing.assert_array_equal(h.kinds[level][~covered], raw[~covered])
|
|
np.testing.assert_array_equal(h.kinds[3], h.pairs[2].fine_kind)
|
|
|
|
def test_depth_is_list_length(self):
|
|
for depth in range(1, 5):
|
|
h = build_hierarchy(CFG, FOUR[:depth - 1])
|
|
self.assertEqual(h.depth, depth)
|
|
np.testing.assert_array_equal(leaf_partition(h), np.ones((48, 48)))
|
|
for level in range(depth - 1):
|
|
np.testing.assert_array_equal(h.kinds[level], self.h.kinds[level])
|
|
|
|
def test_illegal_coverage_reports_first_guard(self):
|
|
cases = [([L1, [(10, 20, 30, 44)]], "inherited ACTIVE"),
|
|
([L1, L2[:1] + [(54, 54, 58, 58)]], "inherited ACTIVE"),
|
|
([L1, L2, [(40, 40, 60, 60)]], "donors/receivers"),
|
|
([L1, L2, [(41, 41, 60, 60)]], "donors/receivers"),
|
|
([L1, L2, [(56, 56, 64, 64), (66, 56, 72, 64)]], "closer than")]
|
|
for levels, reason in cases:
|
|
with self.subTest(levels=levels), self.assertRaisesRegex(ValueError, reason):
|
|
build_hierarchy(CFG, levels)
|
|
build_hierarchy(CFG, [L1, L2, [(42, 42, 60, 60)]]) # minimal legal margin
|
|
|
|
|
|
class LegacyForms(unittest.TestCase):
|
|
def test_scalar_parent_and_child_match_existing_calls(self):
|
|
local = r.FixedPatch2D.from_config(SMALL, (4, 4, 12, 12))
|
|
child, kind = r.nest_patch(SMALL, local, (8, 8, 16, 16))
|
|
h = build_hierarchy(SMALL, [(4, 4, 12, 12), (8, 8, 16, 16)])
|
|
np.testing.assert_array_equal(h.kinds[0], np.where(local.covered, r.OUTER_GHOST, r.ACTIVE))
|
|
np.testing.assert_array_equal(h.kinds[1], kind)
|
|
np.testing.assert_array_equal(h.kinds[2], child.fine_kind)
|
|
for name in ("ghost_fine", "ghost_coarse", "coalesce_coarse", "coalesce_fine"):
|
|
np.testing.assert_array_equal(getattr(h.pairs[1], name), getattr(child, name))
|
|
np.testing.assert_array_equal(leaf_partition(h), np.ones((16, 16)))
|
|
|
|
def test_set_child_under_scalar_parent(self):
|
|
local = r.FixedPatch2D.from_config(SMALL, (4, 4, 12, 12))
|
|
child, kind = r.nest_patch_set(SMALL, local, [(8, 8, 16, 16)])
|
|
h = build_hierarchy(SMALL, [(4, 4, 12, 12), [(8, 8, 16, 16)]])
|
|
np.testing.assert_array_equal(h.kinds[1], kind)
|
|
np.testing.assert_array_equal(h.pairs[1].coalesce_fine, child.coalesce_fine)
|
|
np.testing.assert_array_equal(leaf_partition(h), np.ones((16, 16)))
|
|
|
|
def test_scalar_child_under_set_parent_is_rejected(self):
|
|
with self.assertRaisesRegex(ValueError, "scalar parent"):
|
|
build_hierarchy(SMALL, [[(4, 4, 12, 12)], (12, 12, 20, 20)])
|
|
|
|
def test_wall_rows_and_single_level(self):
|
|
h = build_hierarchy(SMALL, [(4, 4, 12, 12)], wall_aware=True)
|
|
self.assertTrue((h.kinds[0][[0, -1]] == r.OUTER_GHOST).all())
|
|
one = build_hierarchy(SMALL, [])
|
|
self.assertEqual(one.depth, 1)
|
|
self.assertTrue((one.kinds[0] == r.ACTIVE).all())
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|