Add bounded pinned-circle manual L2 remapping
This commit is contained in:
@@ -109,3 +109,42 @@ finite leaves, exact masks and absolute counts. They were not run on the CPU
|
||||
host. The separate independent FP64 capture/replay gate is max population
|
||||
error **3e-6**, with exact masks; remap mass/momentum allow FP32 roundoff.
|
||||
Per-population conservation is not an advancement target under collision.
|
||||
|
||||
### Bounded pinned-body extension
|
||||
|
||||
`replacement = current.remap_child(rectangles, child_objects=original_objects)`
|
||||
now also accepts exactly one stationary Circle in one of exactly two L2
|
||||
rectangles. Its **complete containing rectangle must remain identical**;
|
||||
only the separated fluid rectangle may change. Existing nesting, four-cell
|
||||
gap, full-circle containment, reverse cut-link coverage and two-hop BFL
|
||||
transfer-footprint checks still apply. This is manual remapping, not adaptive
|
||||
body tracking or runtime body editing.
|
||||
|
||||
Supply the original root edge-frame definition used at construction. Do not
|
||||
pass `current.child_boundary.objects`: those are projected fine-local copies.
|
||||
There is no retained mutable original-object registry. Before reading device
|
||||
state, remapping reprojects the supplied definition onto the old topology and
|
||||
compares flags, physical cut links, body IDs, q, lever arms, hits, BFL support,
|
||||
legacy SurfaceActuation specification and current per-link velocity against
|
||||
the existing boundary. Omission, changed geometry/actuation and changed body
|
||||
rectangle fail without device reads or allocations. Root object IDs are still
|
||||
assigned by list position, as in the constructor.
|
||||
|
||||
Jets are taken from the live L2 boundary, including the most recent successful
|
||||
`set_jet_amplitude(..., level=2)`. There is no replacement-Jets argument or
|
||||
constructor-time Jet snapshot. New blocked addresses may differ; the physical
|
||||
boundary and velocities must match exactly.
|
||||
|
||||
`RemapTopology.from_config(..., child_objects=..., child_jets=...)` prepares a
|
||||
typed `FineBoundary2D`; it derives L2 fluid leaves as ACTIVE minus SOLID. No
|
||||
caller mask is accepted. Solids, ghosts and holes are never donors and remain
|
||||
NaN, physical-fluid overlap is exact, and exposed L1 still restricts all four
|
||||
old fluid children. The helper checks the old/new body pin and boundary too.
|
||||
|
||||
Focused CPU mocks: `tests/unit/test_pinned_body_remap.py`. GPU declarations:
|
||||
`tests/validation/multigrid/test_pinned_body_remap_gpu.py`, covering no-op
|
||||
next-step bitwise identity, moved-fluid A→B→A, current-amplitude preservation,
|
||||
physical boundary/velocity equality, source independence and absolute counts.
|
||||
These GPU cases have **not been run here**. They do not establish evolution
|
||||
accuracy. A 3e-6 FP32-vs-FP64 evolution gate requires a separate independent
|
||||
BFL reference; the body-free replay gate above does not establish that claim.
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
The caller owns the CudaContext and must close this backend before that context.
|
||||
Only synchronized postcollision_g leaf populations are public. No checkpoint,
|
||||
forcing or runtime parameter mutation is offered. Manual body-free periodic
|
||||
forcing or runtime parameter mutation is offered. Bounded manual periodic
|
||||
L2 set replacement is available at completed base intervals via remap_child.
|
||||
"""
|
||||
from dataclasses import dataclass, replace
|
||||
@@ -494,29 +494,54 @@ class FixedPatchStepper:
|
||||
self._status = "failed"
|
||||
raise
|
||||
|
||||
def remap_child(self, rectangles):
|
||||
def remap_child(self, rectangles, *, child_objects=()):
|
||||
"""Return a NEW ready stepper with an L2 set replaced at this base time.
|
||||
|
||||
Only body-free periodic three-level set children are supported; L0/L1
|
||||
and block sizes stay fixed. The lock and stream synchronization certify
|
||||
Periodic three-level set children only; L0/L1 and block sizes stay fixed.
|
||||
A body run requires its original root-edge-frame ``child_objects``:
|
||||
one stationary circle pinned in one of exactly two L2 rectangles.
|
||||
The complete body rectangle stays identical; only the other fluid
|
||||
rectangle may move. Current L2 Jets (including updated amplitudes)
|
||||
are preserved. Local projected boundary objects are not valid inputs.
|
||||
The lock and stream synchronization certify
|
||||
current postcollision G and the completed 1:2:4 counts internally.
|
||||
Both objects remain independently owned: the caller must close each,
|
||||
and chooses when to stop using the unchanged old instance. Candidate
|
||||
construction/upload errors never invalidate the old instance; failure
|
||||
to synchronize/read its device state does, as with step/download.
|
||||
"""
|
||||
from .synchronized_remap import RemapTopology, remap_synchronized
|
||||
from .synchronized_remap import (RemapTopology, remap_synchronized,
|
||||
same_physical_boundary, boundary_velocity)
|
||||
|
||||
with self._lock:
|
||||
self._require("ready")
|
||||
if (self._wall_aware or len(self._levels) != 3
|
||||
or not isinstance(self.child_patch, FixedPatchSet2D)
|
||||
or self.fine_boundary is not None or self.child_boundary is not None
|
||||
or any(level.boundary is not None for level in self._levels)):
|
||||
raise ValueError("remap_child requires body-free periodic three-level L2 sets")
|
||||
or self.fine_boundary is not None
|
||||
or any(level.boundary is not None for level in self._levels[:2])):
|
||||
raise ValueError("remap_child requires periodic three-level L2 sets with no L0/L1 bodies")
|
||||
cfg = self._remap_cfg
|
||||
old = RemapTopology.from_config(cfg, self.patch, self.child_patch.bounds)
|
||||
new = RemapTopology.from_config(cfg, self.patch, rectangles)
|
||||
child_objects = tuple(child_objects)
|
||||
actual = self._boundaries[2]
|
||||
jets = () if actual is None else actual.jets
|
||||
old = RemapTopology.from_config(cfg, self.patch, self.child_patch.bounds,
|
||||
child_objects=child_objects, child_jets=jets)
|
||||
if (not same_physical_boundary(old.boundary, self.child_boundary)
|
||||
or (self.child_boundary is not None and actual is None)):
|
||||
raise ValueError("child_objects must reproduce the original root-edge-frame boundary")
|
||||
if actual is not None:
|
||||
velocity = actual.tables.get("surface_velocity")
|
||||
if velocity is None:
|
||||
velocity = boundary_velocity(cfg, self.child_patch, actual.host, jets)
|
||||
if (not same_physical_boundary(old.boundary, actual.host)
|
||||
or not np.array_equal(old.surface_velocity, velocity)):
|
||||
raise ValueError("child_objects changed boundary ownership or surface velocity")
|
||||
new = RemapTopology.from_config(cfg, self.patch, rectangles,
|
||||
child_objects=child_objects, child_jets=jets)
|
||||
if (old.pinned_rectangle != new.pinned_rectangle
|
||||
or not same_physical_boundary(old.boundary, new.boundary)
|
||||
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
|
||||
raise ValueError("remap requires the identical pinned body rectangle and boundary")
|
||||
base_block, fine_block, child_block = self._remap_blocks
|
||||
# Check sparse block alignment/launch size before any device read/allocation.
|
||||
layout = new.child.fine_layout(child_block)
|
||||
@@ -536,7 +561,8 @@ class FixedPatchStepper:
|
||||
candidate = type(self)(
|
||||
cfg, self.patch.bounds, context=self.ctx, base_block=base_block,
|
||||
fine_block=fine_block, child_block=child_block,
|
||||
child_rectangles_in_parent=new.child.bounds)
|
||||
child_rectangles_in_parent=new.child.bounds,
|
||||
child_objects=child_objects, child_jets=jets)
|
||||
try:
|
||||
candidate.initialize(*transferred.g)
|
||||
candidate._step_count = count
|
||||
|
||||
@@ -1,16 +1,21 @@
|
||||
"""CPU-only whole-volume G transfer, not a runtime adaptive solver.
|
||||
|
||||
Only body-free periodic three-level grids with a fixed L0/L1 topology are
|
||||
supported. The caller certifies that current owned leaves are quiescent,
|
||||
Periodic three-level grids with fixed L0/L1 topology are supported, including
|
||||
one stationary circle pinned inside an unchanged L2 rectangle. The caller
|
||||
certifies that current owned leaves are quiescent,
|
||||
unshifted D2Q9 postcollision G at the supplied completed base interval.
|
||||
No device buffers, boundary state, ghosts, collision or time advance occur.
|
||||
No device buffers, device boundary state, ghosts, collision or time advance
|
||||
occur; boundary preparation is host-only.
|
||||
"""
|
||||
from dataclasses import dataclass
|
||||
from dataclasses import dataclass, replace
|
||||
from numbers import Integral
|
||||
|
||||
import numpy as np
|
||||
|
||||
from .refinement import ACTIVE, FixedPatch2D, FixedPatchSet2D, nest_patch_set
|
||||
from .refinement import (ACTIVE, FixedPatch2D, FixedPatchSet2D, FineBoundary2D,
|
||||
nest_patch_set, prepare_fine_boundary)
|
||||
from .descriptors import SOLID
|
||||
from ..body.jets import surface_link_velocity
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
@@ -24,19 +29,71 @@ class RemapTopology:
|
||||
parent: object
|
||||
child: FixedPatchSet2D
|
||||
leaf_masks: tuple
|
||||
boundary: FineBoundary2D | None = None
|
||||
pinned_rectangle: tuple | None = None
|
||||
surface_velocity: np.ndarray | None = None
|
||||
|
||||
@classmethod
|
||||
def from_config(cls, cfg, parent, child_rectangles, *, coarse_boundary="periodic", objects=()):
|
||||
def from_config(cls, cfg, parent, child_rectangles, *, coarse_boundary="periodic", objects=(),
|
||||
child_objects=(), child_jets=()):
|
||||
if coarse_boundary != "periodic" or tuple(objects) or cfg.nz != 1:
|
||||
raise ValueError("remap supports only body-free periodic 2D state")
|
||||
raise ValueError("remap requires periodic 2D state; use child_objects for a pinned L2 body")
|
||||
if type(parent) not in (FixedPatch2D, FixedPatchSet2D):
|
||||
raise ValueError("parent must be a fixed patch or fixed patch set")
|
||||
parent = type(parent).from_config(cfg, parent.bounds)
|
||||
child, middle_kind = nest_patch_set(cfg, parent, child_rectangles)
|
||||
masks = (~parent.covered, middle_kind == ACTIVE, child.fine_kind == ACTIVE)
|
||||
child_objects, child_jets = tuple(child_objects), tuple(child_jets)
|
||||
if child_objects and (len(child_objects) != 1 or len(child.bounds) != 2):
|
||||
raise ValueError("body remap requires one stationary circle and two L2 rectangles")
|
||||
fine_cfg = replace(cfg, nx=parent.fine_shape[1], ny=parent.fine_shape[0],
|
||||
viscosity=2 * cfg.viscosity)
|
||||
boundary = prepare_fine_boundary(
|
||||
fine_cfg, child, child_objects, jets=child_jets,
|
||||
parent_origin=parent.fine_origin, parent_spacing=.5)
|
||||
pinned, velocity = None, None
|
||||
child_leaf = child.fine_kind == ACTIVE
|
||||
if boundary is not None:
|
||||
child_leaf &= (boundary.flags.reshape(child.fine_shape) & SOLID) == 0
|
||||
# Whole circle containment, not merely the resolved solid cells.
|
||||
obj = child_objects[0]
|
||||
cx, cy = (np.asarray(obj.geometry.center) - parent.fine_origin) * 2
|
||||
radius = obj.geometry._radius * 2
|
||||
pinned = next(r for r in child.bounds if
|
||||
r[0] <= cx-radius and cx+radius <= r[2] and
|
||||
r[1] <= cy-radius and cy+radius <= r[3])
|
||||
velocity = boundary_velocity(cfg, child, boundary, child_jets)
|
||||
if velocity is not None:
|
||||
velocity.setflags(write=False)
|
||||
masks = (~parent.covered, middle_kind == ACTIVE, child_leaf)
|
||||
for mask in masks:
|
||||
mask.setflags(write=False)
|
||||
return cls(parent, child, masks)
|
||||
return cls(parent, child, masks, boundary, pinned, velocity)
|
||||
|
||||
|
||||
def boundary_velocity(cfg, child, boundary, jets=()):
|
||||
"""Physical cut-link order; never blocked addresses or stale Jet snapshots."""
|
||||
local_cfg = replace(cfg, nx=child.fine_shape[1], ny=child.fine_shape[0],
|
||||
viscosity=4 * cfg.viscosity)
|
||||
local_cfg.validate()
|
||||
fluid, dirs, _, ids, rx, ry, _, _ = boundary.curved_host
|
||||
velocity, _ = surface_link_velocity(
|
||||
boundary.objects, local_cfg, boundary.flags, fluid, dirs, ids, rx, ry,
|
||||
jets=jets, hits=boundary.curved_hits)
|
||||
return velocity
|
||||
|
||||
|
||||
def same_physical_boundary(a, b):
|
||||
"""Compare geometry, ownership, hits and the entire BFL support stencil."""
|
||||
if a is None or b is None:
|
||||
return a is b
|
||||
if not isinstance(a, FineBoundary2D) or not isinstance(b, FineBoundary2D):
|
||||
return False
|
||||
if (tuple(o.surface_actuation for o in a.objects)
|
||||
!= tuple(o.surface_actuation for o in b.objects)):
|
||||
return False
|
||||
return all(np.array_equal(x, y) for x, y in zip(
|
||||
(a.flags, a.boundary_cells, a.curved_hits, *a.curved_host),
|
||||
(b.flags, b.boundary_cells, b.curved_hits, *b.curved_host)))
|
||||
|
||||
|
||||
@dataclass(frozen=True, eq=False)
|
||||
@@ -105,6 +162,10 @@ def remap_synchronized(old, new, old_g, *, base_count, level_steps, phase,
|
||||
or not _same_parent(old.parent, new.parent)
|
||||
or old.child.tau_fine != new.child.tau_fine):
|
||||
raise ValueError("remap requires unchanged L0/L1 geometry and relaxation")
|
||||
if (old.pinned_rectangle != new.pinned_rectangle
|
||||
or not same_physical_boundary(old.boundary, new.boundary)
|
||||
or not np.array_equal(old.surface_velocity, new.surface_velocity)):
|
||||
raise ValueError("remap requires the identical pinned body rectangle and physical boundary")
|
||||
if not isinstance(level_steps, (tuple, list)):
|
||||
raise ValueError("level_steps must be three completed integer counts")
|
||||
counts = (base_count, *level_steps)
|
||||
|
||||
@@ -0,0 +1,130 @@
|
||||
"""Bounded pinned-body transfer and host lifecycle; no CUDA numerical claim."""
|
||||
import unittest
|
||||
from unittest.mock import patch
|
||||
import numpy as np
|
||||
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject, SurfaceActuation
|
||||
from CelerisLab.body.jets import Jet, Tube, Parallel
|
||||
from CelerisLab.lbm.synchronized_remap import RemapTopology, same_physical_boundary
|
||||
from tests.unit.test_synchronized_remap import CFG, PARENTS, fields, remap
|
||||
from tests.unit.test_refinement_three_level_multipatch import mock_backend
|
||||
from CelerisLab.lbm.refinement import FixedPatchSet2D
|
||||
|
||||
A = ((16,16,32,40), (56,16,72,40))
|
||||
B = ((16,16,32,40), (60,16,76,40))
|
||||
|
||||
|
||||
def body(spec=None, center=(12.,14.)):
|
||||
return SimObject(71, geometry=CircleGeometry(*center,.8), center=center,
|
||||
radius=.8, surface_actuation=spec)
|
||||
|
||||
|
||||
def jet():
|
||||
return Jet(Tube((10.5,14.),0.,3.,.3), Parallel(0.), 'parabolic', .003)
|
||||
|
||||
|
||||
def mesh(rects, objects, jets=()):
|
||||
return RemapTopology.from_config(CFG, FixedPatchSet2D.from_config(CFG,PARENTS),
|
||||
rects, child_objects=objects, child_jets=jets)
|
||||
|
||||
|
||||
def ready(objects=None, jets=None):
|
||||
objects = (body(),) if objects is None else objects
|
||||
jets = (jet(),) if jets is None else jets
|
||||
with patch('tests.unit.test_refinement_three_level_multipatch.bodies_and_jets',
|
||||
return_value=(objects,jets)):
|
||||
run, module, allocations = mock_backend(children=A,bodies=True)
|
||||
storage = {}
|
||||
module.cuda.memcpy_htod.side_effect = lambda p,a: storage.__setitem__(id(p),np.array(a,copy=True))
|
||||
module.cuda.memcpy_dtoh.side_effect = lambda a,p: np.copyto(a,storage[id(p)])
|
||||
run.initialize(*fields(mesh(A,objects,jets),np.float32))
|
||||
run._step_count = 7
|
||||
return run,module,allocations,storage,objects
|
||||
|
||||
|
||||
class PinnedBodyRemap(unittest.TestCase):
|
||||
def test_fluid_only_transfer_poison_and_round_trip(self):
|
||||
old,new = mesh(A,(body(),)),mesh(B,(body(),))
|
||||
arrays=fields(old,np.float32)
|
||||
for a,leaf in zip(arrays,old.leaf_masks): a[:,~leaf]=12345
|
||||
out=remap(old,new,arrays)
|
||||
for a,b,before,after in zip(arrays,out.g,old.leaf_masks,new.leaf_masks):
|
||||
np.testing.assert_array_equal(a[:,before & after],b[:,before & after])
|
||||
self.assertTrue(np.isnan(b[:,~after]).all())
|
||||
self.assertTrue(same_physical_boundary(old.boundary,new.boundary))
|
||||
back=remap(new,old,out.g)
|
||||
self.assertTrue(np.isnan(back.g[2][:,~old.leaf_masks[2]]).all())
|
||||
self.assertEqual(old.pinned_rectangle,A[0])
|
||||
|
||||
def test_current_jets_and_source_independence(self):
|
||||
run,module,allocations,storage,objects=ready()
|
||||
run.set_jet_amplitude(0,.017,level=2)
|
||||
before=run.download()
|
||||
clone=run.remap_child(B,child_objects=objects)
|
||||
self.assertEqual(clone._boundaries[2].jets[0].amplitude,.017)
|
||||
np.testing.assert_array_equal(clone._boundaries[2].tables['surface_velocity'],
|
||||
run._boundaries[2].tables['surface_velocity'])
|
||||
self.assertTrue(same_physical_boundary(run.child_boundary,clone.child_boundary))
|
||||
clone.step()
|
||||
self.assertEqual((run.step_count,clone.step_count),(7,8))
|
||||
for got,want in zip(run.download(),before): np.testing.assert_array_equal(got,want)
|
||||
clone.close();run.close()
|
||||
|
||||
def test_static_body_without_actuation(self):
|
||||
run,module,allocations,storage,objects=ready(jets=())
|
||||
clone=run.remap_child(A,child_objects=objects)
|
||||
self.assertFalse(clone._boundaries[2].actuated)
|
||||
self.assertNotIn('surface_velocity',clone._boundaries[2].tables)
|
||||
for a,b in zip(run.download(),clone.download()): np.testing.assert_array_equal(a,b)
|
||||
clone.close();run.close()
|
||||
|
||||
def test_reject_before_device_reads_or_allocation(self):
|
||||
run,module,allocations,storage,objects=ready()
|
||||
start=len(allocations)
|
||||
cases=[(B,()),(B,run.child_boundary.objects),(B,(body(center=(12.1,14.)),)),
|
||||
(((20,16,36,40),B[1]),objects),(((16,20,32,40),B[1]),objects),
|
||||
((B[1],),objects)]
|
||||
module.cuda.memcpy_dtoh.reset_mock()
|
||||
for rects,objs in cases:
|
||||
with self.subTest(rects=rects),self.assertRaises(ValueError):
|
||||
run.remap_child(rects,child_objects=objs)
|
||||
self.assertEqual(len(allocations),start)
|
||||
module.cuda.memcpy_dtoh.assert_not_called()
|
||||
self.assertEqual((run._status,run.step_count),('ready',7))
|
||||
run.close()
|
||||
|
||||
def test_legacy_velocity_and_zero_velocity_ownership_rejected(self):
|
||||
for speed in (.002,0.):
|
||||
run,module,allocations,storage,objects=ready(
|
||||
(body(SurfaceActuation(0.,.7,speed)),),())
|
||||
for spec in (SurfaceActuation(0.,.7,.003),SurfaceActuation(2.,.7,speed)):
|
||||
module.cuda.memcpy_dtoh.reset_mock()
|
||||
with self.assertRaises(ValueError): run.remap_child(B,child_objects=(body(spec),))
|
||||
module.cuda.memcpy_dtoh.assert_not_called()
|
||||
clone=run.remap_child(B,child_objects=objects)
|
||||
clone.close();run.close()
|
||||
|
||||
def test_allocation_failure_keeps_old_ready_and_count(self):
|
||||
run,module,allocations,storage,objects=ready()
|
||||
before=run.download(); saved=list(allocations)
|
||||
allocate=module.cuda.mem_alloc.side_effect
|
||||
calls=0
|
||||
def fail(size):
|
||||
nonlocal calls
|
||||
calls+=1
|
||||
if calls == 3: raise RuntimeError('candidate allocation')
|
||||
return allocate(size)
|
||||
with patch.object(module.cuda,'mem_alloc',side_effect=fail):
|
||||
with self.assertRaisesRegex(RuntimeError,'allocation'):
|
||||
run.remap_child(B,child_objects=objects)
|
||||
self.assertEqual((run._status,run.step_count),('ready',7))
|
||||
for got,want in zip(run.download(),before): np.testing.assert_array_equal(got,want)
|
||||
for a in saved: a.free.assert_not_called()
|
||||
for a in allocations[len(saved):]: a.free.assert_called_once()
|
||||
run.close()
|
||||
|
||||
def test_direct_helper_rejects_changed_pinned_rectangle(self):
|
||||
old=mesh(A,(body(),))
|
||||
new=mesh(((12,16,32,40),A[1]),(body(),))
|
||||
with self.assertRaisesRegex(ValueError,'pinned'): remap(old,new,fields(old))
|
||||
@@ -185,7 +185,7 @@ class SynchronizedRemapTests(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
RemapTopology.from_config(replace(CFG, **changes), parent, A)
|
||||
for kwargs in (dict(coarse_boundary='slot_reservoir'), dict(objects=(object(),))):
|
||||
with self.assertRaisesRegex(ValueError, 'body-free periodic'):
|
||||
with self.assertRaisesRegex(ValueError, 'periodic 2D'):
|
||||
RemapTopology.from_config(CFG, parent, A, **kwargs)
|
||||
different = topology(A, cfg=replace(CFG, viscosity=.12))
|
||||
with self.assertRaisesRegex(ValueError, 'unchanged'):
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""Pinned-body GPU lifecycle gates. NOT executed by CPU test collection.
|
||||
|
||||
No evolution accuracy tolerance is asserted here: an independent BFL replay
|
||||
reference is required before a 3e-6 FP32-vs-FP64 gate can be claimed. These
|
||||
checks require exact physical boundary/velocity, remap overlap and no-op next
|
||||
step identity; they never compare blocked boundary addresses across layouts.
|
||||
"""
|
||||
from contextlib import ExitStack
|
||||
import unittest
|
||||
import numpy as np
|
||||
from CelerisLab.cuda.context import CudaContext
|
||||
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
|
||||
from CelerisLab.lbm.synchronized_remap import RemapTopology, remap_synchronized
|
||||
from CelerisLab.body.geometry.circle import CircleGeometry
|
||||
from CelerisLab.body.objects import SimObject
|
||||
from CelerisLab.body.jets import Jet, Tube, Parallel
|
||||
from test_runtime_child_remap_gpu import RuntimeChildRemapGPU, initial
|
||||
from test_refinement_three_level_multipatch_gpu import CFG, PARENTS
|
||||
from test_refinement_blocked_base import exact
|
||||
|
||||
A = ((16,16,32,40), (56,16,72,40))
|
||||
B = ((16,16,32,40), (60,16,76,40))
|
||||
|
||||
|
||||
def make(ctx):
|
||||
body = SimObject(71, geometry=CircleGeometry(12.,14.,.8),center=(12.,14.),radius=.8)
|
||||
jet = Jet(Tube((10.5,14.),0.,3.,.3),Parallel(0.),'parabolic',.003)
|
||||
run = FixedPatchStepper(CFG,PARENTS,context=ctx,base_block=(4,4),fine_block=(4,4),
|
||||
child_block=(4,4),child_rectangles_in_parent=A,
|
||||
child_objects=(body,),child_jets=(jet,))
|
||||
return run,(body,)
|
||||
|
||||
|
||||
class PinnedBodyRemapGPU(unittest.TestCase):
|
||||
check_public = RuntimeChildRemapGPU.check_public
|
||||
check_reset = RuntimeChildRemapGPU.check_reset
|
||||
|
||||
def check_boundary(self, before, after):
|
||||
for name in ('flags','boundary_cells','curved_hits'):
|
||||
exact(self,getattr(after.child_boundary,name),getattr(before.child_boundary,name),name)
|
||||
for a,b in zip(after.child_boundary.curved_host,before.child_boundary.curved_host):
|
||||
exact(self,a,b,'physical cut links')
|
||||
exact(self,after._boundaries[2].tables['surface_velocity'],
|
||||
before._boundaries[2].tables['surface_velocity'],'current per-link velocities')
|
||||
self.assertEqual(after._boundaries[2].jets[0].amplitude,.017)
|
||||
|
||||
def test_noop_next_step_bitwise(self):
|
||||
with CudaContext(0) as ctx, ExitStack() as owned:
|
||||
old,objects=make(ctx);owned.enter_context(old)
|
||||
old.initialize(*initial(old,False));old.step(2)
|
||||
old.set_jet_amplitude(0,.017,level=2)
|
||||
before=old.download()
|
||||
new=owned.enter_context(old.remap_child(A,child_objects=objects))
|
||||
self.check_boundary(old,new);self.check_reset(new,before)
|
||||
old.step();new.step()
|
||||
for a,b in zip(new.download(),old.download()): exact(self,a,b,'no-op next step bitwise')
|
||||
self.assertEqual((old.step_count,new.step_count),(3,3))
|
||||
self.check_public(new)
|
||||
|
||||
def test_moved_fluid_return_and_source_independence(self):
|
||||
with CudaContext(0) as ctx, ExitStack() as owned:
|
||||
current,objects=make(ctx);owned.enter_context(current)
|
||||
current.initialize(*initial(current,False));current.step(2)
|
||||
current.set_jet_amplitude(0,.017,level=2)
|
||||
for rects in (B,A):
|
||||
previous=current;before=previous.download();count=previous.step_count
|
||||
jets=previous._boundaries[2].jets
|
||||
old=RemapTopology.from_config(CFG,previous.patch,previous.child_patch.bounds,
|
||||
child_objects=objects,child_jets=jets)
|
||||
new=RemapTopology.from_config(CFG,previous.patch,rects,
|
||||
child_objects=objects,child_jets=jets)
|
||||
expected=remap_synchronized(old,new,before,base_count=count,
|
||||
level_steps=(count,2*count,4*count),phase='postcollision_g').g
|
||||
current=owned.enter_context(previous.remap_child(rects,child_objects=objects))
|
||||
self.check_boundary(previous,current);self.check_reset(current,expected)
|
||||
for a,b,om,nm in zip(current.download(),before,old.leaf_masks,new.leaf_masks):
|
||||
exact(self,a[:,om & nm],b[:,om & nm],'physical-fluid overlap')
|
||||
current.step(2)
|
||||
self.assertEqual((previous.step_count,current.step_count),(count,count+2))
|
||||
for a,b in zip(previous.download(),before): exact(self,a,b,'source independent')
|
||||
self.check_public(current)
|
||||
|
||||
|
||||
if __name__ == '__main__': unittest.main()
|
||||
Reference in New Issue
Block a user