Support bounded three-level separated patch unions
This commit is contained in:
1 parent
20fb16ded5
commit
a24e6d2742
5 files changed
+647
-24
No files matched your search
@@ -0,0 +1,178 @@
|
||||
"""Frozen GPU gates for the bounded three-level separated-union backend.
|
||||
|
||||
Run explicitly on an available GPU; CPU geometry/mocks live in tests/unit.
|
||||
Snapshots 0/1/2/8: finite public leaves, exact NaN masks, raw invalid cores;
|
||||
reversed order and aligned layout changes must be bit-identical. The scalar
|
||||
window comparison uses root physical coordinates, never equal array indices.
|
||||
These equivalence checks supplement, not replace, independent dense CPU replay.
|
||||
"""
|
||||
from dataclasses import replace
|
||||
import unittest
|
||||
|
||||
import numpy as np
|
||||
|
||||
from CelerisLab.config import LBMConfig
|
||||
from CelerisLab.lbm.descriptors import SOLID
|
||||
from CelerisLab.lbm.equilibrium import compute_feq_d2q9
|
||||
from CelerisLab.lbm.refinement import OUTER_GHOST
|
||||
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
|
||||
from test_refinement_blocked_base import raw, exact
|
||||
|
||||
CFG = LBMConfig(nx=48, ny=32, viscosity=.1)
|
||||
PARENTS = ((4, 4, 20, 28), (24, 4, 44, 28))
|
||||
CHILDREN = ((16, 16, 32, 40), (56, 16, 80, 40))
|
||||
TOPOLOGIES = {
|
||||
'twins': ((16, 16, 32, 40), (56, 16, 72, 40)),
|
||||
'asymmetric': (CHILDREN[0],),
|
||||
'multiple_children': ((16, 16, 32, 24), (16, 28, 32, 40)),
|
||||
}
|
||||
# 8-wide blocks require support edges divisible by 8, i.e. rectangle edges
|
||||
# equal 2 modulo 4 in each pair's parent frame. The default fixture is 4-only.
|
||||
ALIGNED_CFG = LBMConfig(nx=56, ny=40, viscosity=.1)
|
||||
ALIGNED_PARENTS = ((6, 6, 22, 34), (26, 6, 50, 34))
|
||||
ALIGNED_CHILDREN = ((18, 18, 34, 50), (58, 18, 90, 50))
|
||||
STOPS = (0, 1, 2, 8)
|
||||
|
||||
|
||||
def config(cfg, mode):
|
||||
return cfg if mode == 'periodic' else replace(
|
||||
cfg, velocity=0., wall_normal_velocity=.001, wall_slot_width=4,
|
||||
y_wall_bc='on_site_velocity', top_wall_bc='reservoir')
|
||||
|
||||
|
||||
def bodies():
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
return [SimObject(99, geometry=CircleGeometry(x, 14., .8), center=(x, 14.), radius=.8)
|
||||
for x in (12., 34.)]
|
||||
|
||||
|
||||
def jets(speed):
|
||||
from CelerisLab.body.jets import Jet, Tube, Parallel
|
||||
return [Jet(Tube((x-1.5, 14.), 0., 3., .3), Parallel(0.), amplitude=speed)
|
||||
for x in (12., 34.)]
|
||||
|
||||
|
||||
def frames(run):
|
||||
middle_origin = np.asarray(run.patch.fine_origin)
|
||||
return ((np.zeros(2), 1.), (middle_origin, .5),
|
||||
(middle_origin + .5*np.asarray(run.child_patch.fine_origin), .25))
|
||||
|
||||
|
||||
def physical_state(shape, origin, spacing):
|
||||
"""Same smooth non-equilibrium G at the same physical cell centers."""
|
||||
y, x = np.indices(shape, dtype=np.float64)
|
||||
x, y = origin[0] + spacing*(x+.5), origin[1] + spacing*(y+.5)
|
||||
equilibrium = np.asarray(compute_feq_d2q9(1., .013, -.007), np.float64)[:, None, None]
|
||||
q = np.arange(9)[:, None, None]
|
||||
return (equilibrium + 1e-4*np.sin(.17*x + .23*y + q)).astype(np.float32)
|
||||
|
||||
|
||||
def physical_leaves(array, origin, spacing):
|
||||
y, x = np.nonzero(np.isfinite(array[0]))
|
||||
coordinates = np.stack((origin[1]+spacing*(y+.5), origin[0]+spacing*(x+.5)), axis=-1)
|
||||
return coordinates, array[:, y, x]
|
||||
|
||||
|
||||
class ThreeLevelMultipatchGPU(unittest.TestCase):
|
||||
def run_case(self, ctx, cfg=CFG, parents=PARENTS, children=CHILDREN,
|
||||
mode='periodic', blocks=((4, 4), (4, 4)), objects=(), drive=(), switch=False,
|
||||
scalar_child=None):
|
||||
kwargs = dict(child_rectangles_in_parent=children) if scalar_child is None else dict(child=scalar_child)
|
||||
with FixedPatchStepper(config(cfg, mode), parents, context=ctx, coarse_boundary=mode,
|
||||
base_block=(4, 4), fine_block=blocks[0], child_block=blocks[1],
|
||||
child_objects=objects, child_jets=drive, **kwargs) as run:
|
||||
frame = frames(run)
|
||||
run.initialize(*(physical_state(level.shape[1:], *f)
|
||||
for level, f in zip(run._levels, frame)))
|
||||
snapshots = []
|
||||
for stop in STOPS:
|
||||
run.step(stop-run.step_count)
|
||||
if switch and stop == 2:
|
||||
for index in range(len(drive)):
|
||||
run.set_jet_amplitude(index, -.002, level=2)
|
||||
public, stored = run.download(), raw(run)
|
||||
masks = [~run.patch.covered, run._fine_leaf, run._child_leaf]
|
||||
if mode != 'periodic':
|
||||
masks[0][[0, -1]] = False
|
||||
invalid = [run.patch.covered.copy(), run._middle_kind == OUTER_GHOST,
|
||||
run.child_patch.fine_kind == OUTER_GHOST]
|
||||
if mode != 'periodic':
|
||||
invalid[0][[0, -1]] = True
|
||||
if run.child_boundary is not None:
|
||||
invalid[2] |= (run.child_boundary.flags.reshape(run.child_patch.fine_shape) & SOLID) != 0
|
||||
for level, (g, mask, dense, bad) in enumerate(zip(public, masks, stored, invalid)):
|
||||
label = f'{mode} stop={stop} level={level}'
|
||||
self.assertTrue(np.isfinite(g[:, mask]).all(), label)
|
||||
self.assertTrue(np.isnan(g[:, ~mask]).all(), label)
|
||||
self.assertTrue(np.isnan(dense[:, bad]).all(), label)
|
||||
snapshots.append((public, stored, frame))
|
||||
return snapshots
|
||||
|
||||
def compare(self, actual, expected):
|
||||
for stop, a, b in zip(STOPS, actual, expected):
|
||||
for kind in (0, 1):
|
||||
for level, (x, y) in enumerate(zip(a[kind], b[kind])):
|
||||
exact(self, x, y, f'stop={stop} kind={kind} level={level}')
|
||||
|
||||
def test_topologies_reversed_order_and_masks(self):
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
with CudaContext(0) as ctx:
|
||||
for mode in ('periodic', 'slot_reservoir'):
|
||||
for name, children in TOPOLOGIES.items():
|
||||
with self.subTest(mode=mode, topology=name):
|
||||
expected = self.run_case(ctx, children=children, mode=mode)
|
||||
actual = self.run_case(ctx, parents=PARENTS[::-1], children=children[::-1], mode=mode)
|
||||
self.compare(actual, expected)
|
||||
|
||||
def test_independently_varied_aligned_layouts(self):
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
with CudaContext(0) as ctx:
|
||||
for mode in ('periodic', 'slot_reservoir'):
|
||||
kwargs = dict(cfg=ALIGNED_CFG, parents=ALIGNED_PARENTS,
|
||||
children=ALIGNED_CHILDREN, mode=mode)
|
||||
expected = self.run_case(ctx, **kwargs)
|
||||
for middle in ((4, 4), (8, 4), (4, 8)):
|
||||
for leaf in ((4, 4), (8, 4), (4, 8)):
|
||||
self.compare(self.run_case(ctx, blocks=(middle, leaf), **kwargs), expected)
|
||||
|
||||
def test_one_element_equivalent_physical_frames(self):
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
cfg = LBMConfig(nx=16, ny=16, viscosity=.1)
|
||||
parent = (4, 4, 12, 12)
|
||||
with CudaContext(0) as ctx:
|
||||
for mode in ('periodic', 'slot_reservoir'):
|
||||
old = self.run_case(ctx, cfg=cfg, parents=parent, mode=mode, scalar_child=(8, 8, 16, 16))
|
||||
# Test both set-on-scalar and set-on-set allocated frames.
|
||||
for parents, child in ((parent, (8, 8, 16, 16)), ((parent,), (12, 12, 20, 20))):
|
||||
new = self.run_case(ctx, cfg=cfg, parents=parents, children=[child], mode=mode)
|
||||
for a, b in zip(new, old):
|
||||
for level in range(3):
|
||||
ax, av = physical_leaves(a[0][level], *a[2][level])
|
||||
bx, bv = physical_leaves(b[0][level], *b[2][level])
|
||||
np.testing.assert_array_equal(ax, bx)
|
||||
exact(self, av, bv, f'physical level={level}')
|
||||
|
||||
def test_two_l2_bodies_zero_driven_switched_order_and_layout(self):
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
with CudaContext(0) as ctx:
|
||||
for mode in ('periodic', 'slot_reservoir'):
|
||||
kwargs = dict(cfg=ALIGNED_CFG, parents=ALIGNED_PARENTS,
|
||||
children=ALIGNED_CHILDREN, mode=mode, objects=bodies())
|
||||
static = self.run_case(ctx, **kwargs)
|
||||
zero = self.run_case(ctx, drive=jets(0.), **kwargs)
|
||||
self.compare(zero, static)
|
||||
driven = self.run_case(ctx, drive=jets(.002), **kwargs)
|
||||
switched = self.run_case(ctx, drive=jets(.002), switch=True, **kwargs)
|
||||
self.assertFalse(np.array_equal(driven[-1][0][2], static[-1][0][2], equal_nan=True))
|
||||
self.assertFalse(np.array_equal(driven[-1][0][2], switched[-1][0][2], equal_nan=True))
|
||||
for drive, switch, expected in (((), False, static), (jets(0.), False, zero),
|
||||
(jets(.002), False, driven), (jets(.002), True, switched)):
|
||||
for middle, leaf in (((8, 4), (4, 8)), ((4, 8), (8, 4))):
|
||||
actual = self.run_case(ctx, blocks=(middle, leaf), drive=drive, switch=switch,
|
||||
**dict(kwargs, parents=ALIGNED_PARENTS[::-1], children=ALIGNED_CHILDREN[::-1]))
|
||||
self.compare(actual, expected)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user