Gate shifted velocity Jets to all-blocked callers

This commit is contained in:
Codex
2026-10-17 00:46:49 -07:00
parent b77f2238dd
commit 9dddf5d812
7 changed files with 340 additions and 17 deletions
+5 -2
View File
@@ -189,13 +189,14 @@ def edge_to_node_frame(points):
def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry,
jets=(), hits=None):
jets=(), hits=None, *, _shifted_blocked=False):
"""Per-link prescribed surface velocity (n, 2) FP32 and aperture mask.
Host-only and storage-neutral: rows follow the given physical cut-link
order. Returns (None, None) when neither an object carries surface
actuation nor a jet is given. Jets need true FP64 ``hits`` (n, 2) and may
not share a link with each other or with a legacy aperture.
Shifted storage is privately enabled only by the all-blocked stepper.
"""
enabled = [obj for obj in objects if obj.surface_actuation is not None]
if not enabled and not jets:
@@ -219,7 +220,9 @@ def surface_link_velocity(objects, cfg, flags, fluid_idx, dir, body_id, rx, ry,
if hits is None or len(hits) != len(dir):
raise ValueError("Jets require true hit points row-aligned with the cut links")
if (cfg is None or not cfg.is_d2q9 or cfg.store_precision != "FP32"
or cfg.streaming != "double_buffer" or cfg.ddf_shifting):
or cfg.streaming != "double_buffer"
or (cfg.ddf_shifting and not (_shifted_blocked is True
and cfg.collision == "SRT" and not cfg.les_enabled))):
raise ValueError("Jets require D2Q9/FP32/unshifted/double_buffer")
shape, jet_id = jet_link_shapes(jets, hits)
driven = jet_id >= 0
+2 -2
View File
@@ -422,7 +422,7 @@ def _circle_in_coverage(covered, cx, cy, radius):
def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=(),
parent_origin=(0., 0.), parent_spacing=1.):
parent_origin=(0., 0.), parent_spacing=1., _shifted_blocked=False):
"""Project fixed circles and reject boundary/transfer overlap before CUDA.
Input centers/radii and Jets are in the root edge frame. ``parent_origin``
@@ -555,7 +555,7 @@ def prepare_fine_boundary(cfg, patch, objects, *, wall_aware=False, jets=(),
jet_cfg.validate()
fluid_idx, dirs, _, body_id, rx, ry, _, _ = packed
surface_link_velocity(local, jet_cfg, flags, fluid_idx, dirs, body_id, rx, ry,
jets=jets, hits=hits)
jets=jets, hits=hits, _shifted_blocked=_shifted_blocked)
return FineBoundary2D(tuple(local), _readonly(flags, np.uint16),
tuple(_readonly(a, a.dtype) for a in packed),
_readonly(sorted(boundary), np.uint32),
+12 -6
View File
@@ -287,11 +287,12 @@ class FixedPatchStepper:
pair = pairs[level - 1]
if level == 1:
host = prepare_fine_boundary(cfg, pair, level_objects[1],
wall_aware=self._wall_aware, jets=level_jets[1])
wall_aware=self._wall_aware, jets=level_jets[1],
_shifted_blocked=self._ddf_shifting)
else:
host = prepare_fine_boundary(self._level_cfgs[level - 1], pair, level_objects[level],
jets=level_jets[level], parent_origin=origin,
parent_spacing=spacing)
parent_spacing=spacing, _shifted_blocked=self._ddf_shifting)
self._boundary_hosts.append(host)
origin = tuple(o + spacing * f for o, f in zip(origin, pair.fine_origin))
spacing /= 2
@@ -309,7 +310,8 @@ class FixedPatchStepper:
self._level_jets = level_jets
self._level_boundaries = [None] + [
_prepare_blocked_boundary(self._boundary_hosts[level], self._layouts[level],
self._level_cfgs[level], level_jets[level])
self._level_cfgs[level], level_jets[level],
_shifted_blocked=self._ddf_shifting)
for level in range(1, depth)]
self._leaf = [kind == ACTIVE for kind in self.hierarchy.kinds]
for level in range(1, depth):
@@ -558,6 +560,7 @@ class FixedPatchStepper:
with self._lock:
self._require("ready")
self._check_storage()
pairs = self.hierarchy.pairs
if (self._wall_aware or self.hierarchy.depth != 3
or not isinstance(pairs[1], FixedPatchSet2D)
@@ -569,19 +572,22 @@ class FixedPatchStepper:
actual = self._level_boundaries[2]
jets = () if actual is None else actual.jets
old = RemapTopology.from_config(cfg, pairs[0], pairs[1].bounds,
child_objects=child_objects, child_jets=jets)
child_objects=child_objects, child_jets=jets,
_shifted_blocked=self._ddf_shifting)
if (not same_physical_boundary(old.boundary, self._boundary_hosts[2])
or (self._boundary_hosts[2] 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, pairs[1], actual.host, jets)
velocity = boundary_velocity(cfg, pairs[1], actual.host, jets,
_shifted_blocked=self._ddf_shifting)
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, pairs[0], rectangles,
child_objects=child_objects, child_jets=jets)
child_objects=child_objects, child_jets=jets,
_shifted_blocked=self._ddf_shifting)
if (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 physical boundary and BFL support")
+2 -2
View File
@@ -87,7 +87,7 @@ class _BlockedBoundary:
accumulate: object = None
def _prepare_blocked_boundary(boundary, layout, cfg, jets):
def _prepare_blocked_boundary(boundary, layout, cfg, jets, *, _shifted_blocked=False):
if boundary is None or layout is None:
return None
fluid_idx, dirs, q, body_id, rx, ry, _, fallback = boundary.curved_host
@@ -102,6 +102,6 @@ def _prepare_blocked_boundary(boundary, layout, cfg, jets):
cfg.validate()
tables["surface_velocity"], tables["aperture"] = surface_link_velocity(
boundary.objects, cfg, boundary.flags, fluid_idx, dirs, body_id, rx, ry,
jets=jets, hits=boundary.curved_hits)
jets=jets, hits=boundary.curved_hits, _shifted_blocked=_shifted_blocked)
return _BlockedBoundary(boundary, tables, jets, actuated,
jet_link_shapes(jets, boundary.curved_hits) if jets else None)
+6 -5
View File
@@ -39,7 +39,7 @@ class RemapTopology:
@classmethod
def from_config(cls, cfg, parent, child_rectangles, *, coarse_boundary="periodic", objects=(),
child_objects=(), child_jets=()):
child_objects=(), child_jets=(), _shifted_blocked=False):
if coarse_boundary != "periodic" or tuple(objects) or cfg.nz != 1:
raise ValueError("remap requires periodic 2D state; use child_objects for a pinned L2 body")
if type(parent) not in (FixedPatch2D, FixedPatchSet2D):
@@ -51,7 +51,7 @@ class RemapTopology:
viscosity=2 * cfg.viscosity)
boundary = prepare_fine_boundary(
fine_cfg, child, child_objects, jets=child_jets,
parent_origin=parent.fine_origin, parent_spacing=.5)
parent_origin=parent.fine_origin, parent_spacing=.5, _shifted_blocked=_shifted_blocked)
pinned, velocity = (), None
child_leaf = child.fine_kind == ACTIVE
if boundary is not None:
@@ -67,7 +67,8 @@ class RemapTopology:
if match is not None:
containing.add(match)
pinned = tuple(sorted(containing))
velocity = boundary_velocity(cfg, child, boundary, child_jets)
velocity = boundary_velocity(cfg, child, boundary, child_jets,
_shifted_blocked=_shifted_blocked)
if velocity is not None:
velocity.setflags(write=False)
masks = (~parent.covered, middle_kind == ACTIVE, child_leaf)
@@ -76,7 +77,7 @@ class RemapTopology:
return cls(parent, child, masks, boundary, pinned, velocity)
def boundary_velocity(cfg, child, boundary, jets=()):
def boundary_velocity(cfg, child, boundary, jets=(), *, _shifted_blocked=False):
"""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)
@@ -84,7 +85,7 @@ def boundary_velocity(cfg, child, boundary, jets=()):
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)
jets=jets, hits=boundary.curved_hits, _shifted_blocked=_shifted_blocked)
return velocity
+105
View File
@@ -0,0 +1,105 @@
"""Shifted velocity Jets: bounded opt-in and host lifecycle, no CUDA numerics."""
from dataclasses import replace
import unittest
from unittest.mock import patch
import numpy as np
from CelerisLab.body.jets import Jet, Tube, Parallel
from CelerisLab.body.objects import SurfaceActuation
from CelerisLab.lbm.synchronized_remap import RemapTopology
from CelerisLab.lbm.refinement import FixedPatchSet2D
from CelerisLab.lbm.population_storage import _StoredPopulations
from tests.unit import test_refinement_three_level_multipatch as backend
from tests.unit.test_pinned_body_remap import A, B, body, jet, mesh
from tests.unit.test_shifted_storage import shifted_fields
CFG=replace(backend.CFG,ddf_shifting=True)
def topology(objects, jets, *, enabled=False, cfg=CFG):
return RemapTopology.from_config(cfg,FixedPatchSet2D.from_config(cfg,backend.PARENTS),A,
child_objects=objects,child_jets=jets,_shifted_blocked=enabled)
def ready():
objects,jets=(body(),),(jet(),)
with patch.object(backend,'CFG',CFG),patch.object(backend,'bodies_and_jets',return_value=(objects,jets)):
run,module,allocations=backend.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_raw(_StoredPopulations(shifted_fields(mesh(A,objects,jets)),True))
run._step_count=7
return run,module,allocations,storage,objects
class ShiftedJets(unittest.TestCase):
def test_default_guard_and_storage_neutral_host_rows(self):
with self.assertRaisesRegex(ValueError,'unshifted'):
topology((body(),),(jet(),))
shifted=topology((body(),),(jet(),),enabled=True)
unshifted=topology((body(),),(jet(),),cfg=backend.CFG)
np.testing.assert_array_equal(shifted.surface_velocity,unshifted.surface_velocity)
self.assertEqual(shifted.surface_velocity.dtype,np.float32)
legacy=body(SurfaceActuation(.7,.85,.002))
with self.assertRaisesRegex(ValueError,'unshifted'):
topology((legacy,),(),enabled=True)
def test_rejections_before_device_allocation(self):
module=backend.host_module()
moving=body();moving.state.omega=.01
cases=[((moving,),(jet(),)),((body(),),(jet(),jet())),
((body(),),(Jet(Tube((100.,100.),0.,1.,.1),Parallel(0.)),)),
((body(center=(8.,14.)),),(jet(),)),
((body(),body(center=(34.,14.))),
(Jet(Tube((10.,14.),0.,26.,.3),Parallel(0.)),))]
for objects,jets in cases:
with self.subTest(objects=len(objects),jets=len(jets)),self.assertRaises(ValueError):
module.FixedPatchStepper(CFG,backend.PARENTS,context=None,
base_block=(4,4),fine_block=(4,4),child_block=(4,4),
child_rectangles_in_parent=A,child_objects=objects,child_jets=jets)
for blocks in ((None,None,None),((4,4),None,(4,4))):
with self.assertRaises(ValueError):
module.FixedPatchStepper(CFG,backend.PARENTS,context=None,
base_block=blocks[0],fine_block=blocks[1],child_block=blocks[2],
child_rectangles_in_parent=A,child_objects=(body(),),child_jets=(jet(),))
module.cuda.mem_alloc.assert_not_called()
module.compiler.compile_kernel.assert_not_called()
def test_switch_upload_only_then_raw_remap_keeps_live_velocity(self):
run,module,allocations,storage,objects=ready();clone=None
try:
boundary=run._level_boundaries[2]
before=run._download_raw()
original=boundary.tables['surface_velocity'].copy()
module.cuda.memcpy_htod.reset_mock()
run.set_jet_amplitude(0,-jet().amplitude,level=2)
module.cuda.memcpy_htod.assert_called_once()
self.assertIs(module.cuda.memcpy_htod.call_args.args[0],boundary.device['surface_velocity'])
np.testing.assert_array_equal(boundary.tables['surface_velocity'],-original)
self.assertEqual(run.step_count,7)
for a,b in zip(before.values,run._download_raw().values):self.assertEqual(a.tobytes(),b.tobytes())
clone=run.remap_child(B,child_objects=objects)
self.assertEqual(clone._level_jets[2][0].amplitude,-jet().amplitude)
np.testing.assert_array_equal(clone._level_boundaries[2].tables['surface_velocity'],
boundary.tables['surface_velocity'])
self.assertEqual(clone.step_count,7)
saved=run._download_raw()
with self.assertRaises(ValueError):run.remap_child(B,child_objects=(body(center=(12.1,14.)),))
for a,b in zip(saved.values,run._download_raw().values):self.assertEqual(a.tobytes(),b.tobytes())
finally:
if clone is not None:clone.close()
run.close()
def test_invalid_amplitude_does_not_upload_or_commit(self):
run,module,_,_,_=ready()
try:
before=run._level_boundaries[2].tables['surface_velocity'].copy()
module.cuda.memcpy_htod.reset_mock()
for amplitude in (float('nan'),float('inf'),1.):
with self.assertRaises(ValueError):run.set_jet_amplitude(0,amplitude,level=2)
module.cuda.memcpy_htod.assert_not_called()
np.testing.assert_array_equal(before,run._level_boundaries[2].tables['surface_velocity'])
finally:run.close()
if __name__=='__main__':unittest.main()
@@ -0,0 +1,208 @@
"""Shifted velocity-Jet GPU gates. Run only on an authorized device.
PYTHONPATH=src python tests/validation/multigrid/test_shifted_jets_gpu.py -v
Uniform initial curved-body states rho=.99/1.01; amplitude 0,+.002,-.001,0;
four-level substep ledger; three-level live-Jet raw remap. No pressure actuator.
The census is an implementation-exchange ledger, not an independent physical
traction/nozzle-thrust model. Frozen inherited caps: raw rows rtol=2e-6/atol=2e-7; fluid ledger
rtol=2e-4/atol=8e-5. Observer on/off, interval accumulation and raw copies exact.
Report actual residuals too: passing old loose caps alone is not an accuracy gain.
"""
from contextlib import ExitStack
import unittest
import numpy as np
import pycuda.driver as cuda
from CelerisLab.config import LBMConfig
from CelerisLab.cuda.context import CudaContext
from CelerisLab.body.geometry.circle import CircleGeometry
from CelerisLab.body.objects import SimObject
from CelerisLab.body.jets import Jet, Tube, Parallel
from CelerisLab.lbm.descriptors import D2Q9_EX, D2Q9_EY, SOLID
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
from CelerisLab.lbm.population_storage import _StoredPopulations
from CelerisLab.lbm.synchronized_remap import RemapTopology, _remap_stored
CX,CY=np.asarray(D2Q9_EX),np.asarray(D2Q9_EY)
OPP=np.array([np.flatnonzero((CX==-x)&(CY==-y))[0] for x,y in zip(CX,CY)])
W=np.array([4/9,*([1/9]*4),*([1/36]*4)],np.float64)
SPEEDS=(0.,.002,-.001,0.)
def circle(center,radius):
return SimObject(17,geometry=CircleGeometry(*center,radius),center=center,radius=radius)
def drive(center,radius):
return Jet(Tube(center,0.,radius*1.5,radius*.32),Parallel(0.),amplitude=0.)
def initial(run,rho,u=.013,v=-.007):
cu=3*(CX*u+CY*v)
g=W*((rho-1)+rho*(cu+.5*cu*cu-1.5*(u*u+v*v)))
return _StoredPopulations(tuple(np.broadcast_to(g[:,None,None],l.shape).astype(np.float32).copy()
for l in run._levels),True)
def download(pointer,shape,dtype=np.float32):
out=np.empty(shape,dtype);cuda.memcpy_dtoh(out,pointer);return out
def raw_totals(a,leaf):
g=a[:,leaf].astype(np.float64)
# Only differences at the same fixed leaf mask are used, so mass's unit
# background cancels. Momentum never requires adding weights.
return np.array([(g*CX[:,None]).sum(),(g*CY[:,None]).sum(),g.sum()])
def census(run,index,buffer):
"""Independent physical link/face census of actual staged receiver windows."""
level=run._levels[index];layout=level.layout;b=level.boundary
stored=download(buffer,layout.blocked_shape);dense=layout.to_dense(stored)
leaf=run._leaf[index];ny,nx=leaf.shape
solid=(b.host.flags.reshape(ny,nx)&SOLID)!=0
y,x=np.nonzero(leaf);owner=layout.owner[y,x].astype(np.intp)
cells=int(np.prod(layout.local_shape));block,cell=np.divmod(owner,cells)
flat=stored.reshape(layout.nblocks,9,cells);external=np.zeros(3,np.float64)
for q,(cx,cy) in enumerate(zip(CX,CY)):
sy,sx=y-cy,x-cx;dy,dx=y+cy,x+cx
incoming=flat[block,q,cell-cy*layout.local_shape[1]-cx].astype(np.float64)+W[q]
outgoing=flat[block,q,cell].astype(np.float64)+W[q]
entering=~leaf[sy,sx]&~solid[sy,sx];leaving=~leaf[dy,dx]&~solid[dy,dx]
external+=np.array([cx,cy,1])*(incoming[entering].sum()-outgoing[leaving].sum())
receiver,direction,_,_,rx,ry,_,_=b.host.curved_host
owner=layout.owner.ravel()[receiver].astype(np.intp);block,cell=np.divmod(owner,cells)
outgoing=flat[block,direction,cell].astype(np.float64)
source=cell+CY[direction]*layout.local_shape[1]+CX[direction]
reflected=flat[block,OPP[direction],source].astype(np.float64)
physical_sum=outgoing+reflected+2*W[direction]
jx,jy=CX[direction]*physical_sum,CY[direction]*physical_sum
rows=np.column_stack((jx,jy,rx*jy-ry*jx,reflected-outgoing))
return raw_totals(dense,leaf),external,rows
class Ledger:
def __init__(self,test,run,index):
self.test,self.run,self.index=test,run,index
self.samples=[];self.max_row=0.;self.max_balance=0.
level=run._levels[index];self.original=level.kernel;b=level.boundary
def audited(src,dst,*args,**kwargs):
run._stream.synchronize()
before,external,independent=census(run,index,src)
raw=download(b.last_raw,(len(b.tables['cell']),4))
np.testing.assert_allclose(raw,independent,rtol=2e-6,atol=2e-7)
self.max_row=max(self.max_row,float(abs(raw.astype(float)-independent).max()))
self.original(src,dst,*args,**kwargs);run._stream.synchronize()
after=level.layout.to_dense(download(dst,level.layout.blocked_shape))
change=raw_totals(after,run._leaf[index])-before
expected=external+np.array([-independent[:,0].sum(),-independent[:,1].sum(),independent[:,3].sum()])
np.testing.assert_allclose(change,expected,rtol=2e-4,atol=8e-5)
self.max_balance=max(self.max_balance,float(abs(change-expected).max()))
self.samples.append(raw.copy())
level.kernel=audited
def step(self):
run=self.run;self.samples.clear();run.step()
b=run._level_boundaries[self.index];h=run._exchange_spacing[self.index]
self.test.assertEqual(len(self.samples),2**self.index)
expected=np.zeros_like(self.samples[0],dtype=np.float64)
weights=np.array([h*h,h*h,h*h*h,h*h])
for raw in self.samples:expected+=raw.astype(np.float64)*weights
actual=download(b.interval_integral,expected.shape,np.float64)
np.testing.assert_array_equal(actual,expected)
obs=run.read_raw_exchange(0,level=self.index)
for name in ('impulse','angular_impulse','mass_into_fluid'):
np.testing.assert_allclose(obs['total'][name],np.asarray(obs['wall'][name])+obs['aperture'][name],rtol=0,atol=2e-13)
self.test.assertEqual(obs['sample_count'],2**self.index)
for a,leaf in zip(run._download_raw().values,run._leaf):
self.test.assertTrue(np.isfinite(a[:,leaf]).all())
self.test.assertTrue((1+a[:,leaf].sum(axis=0,dtype=np.float64)>0).all())
return obs
def report(self,label):
print(f'{label}: max_abs_raw_row={self.max_row:.9g}, max_abs_fluid_ledger={self.max_balance:.9g}',flush=True)
class ShiftedJetsGPU(unittest.TestCase):
def exact_raw(self,a,b):
self.assertTrue(a.shifted and b.shifted)
for x,y in zip(a.values,b.values):self.assertEqual(x.tobytes(),y.tobytes())
def test_matching_whole_circle_parallel_velocity_preserves_uniform_state(self):
cfg=LBMConfig(nx=32,ny=24,viscosity=.1,ddf_shifting=True)
center=(16.,12.);radius=2.15;obj=circle(center,radius)
# Rectangle contains every circle hit; prescribed surface velocity equals
# the uniform fluid velocity. Geometry itself remains fixed.
whole=Jet(Tube((center[0]-radius-1,center[1]),0.,2*radius+2,radius+1),
Parallel(0.),amplitude=.013)
for rho in (.99,1.01):
with self.subTest(rho=rho),CudaContext(0) as ctx:
with FixedPatchStepper(cfg,(8,6,24,18),context=ctx,base_block=(4,4),fine_block=(4,4),
fine_objects=(obj,),jets=(whole,),observe_raw_exchange=True) as run:
run._initialize_raw(initial(run,rho,u=.013,v=0.))
expected=run._download_raw();ledger=Ledger(self,run,1)
self.assertTrue(run._level_boundaries[1].tables['aperture'].all())
for _ in range(4):ledger.step()
error=max(float(abs(a[:,leaf]-b[:,leaf]).max()) for a,b,leaf in
zip(run._download_raw().values,expected.values,run._leaf))
self.assertLessEqual(error,1e-6) # inherited uniform-state cap
print(f'matched whole-circle rho={rho}: max_abs_raw_state={error:.9g}',flush=True)
ledger.report(f'matched whole-circle rho={rho}')
def test_uniform_initial_curved_rho_both_signs_switch_and_observer(self):
cfg=LBMConfig(nx=32,ny=24,viscosity=.1,ddf_shifting=True)
obj=circle((16.,12.),2.15);j=drive((16.,12.),2.15)
for rho in (.99,1.01):
with self.subTest(rho=rho),CudaContext(0) as ctx,ExitStack() as owned:
settings=dict(bounds=(8,6,24,18),context=ctx,base_block=(4,4),fine_block=(4,4),fine_objects=(obj,),jets=(j,))
measured=owned.enter_context(FixedPatchStepper(cfg,observe_raw_exchange=True,**settings))
plain=owned.enter_context(FixedPatchStepper(cfg,**settings))
measured._initialize_raw(initial(measured,rho));plain._initialize_raw(initial(plain,rho))
ledger=Ledger(self,measured,1)
for speed in SPEEDS:
before=measured._download_raw();count=measured.step_count
measured.set_jet_amplitude(0,speed);plain.set_jet_amplitude(0,speed)
self.exact_raw(before,measured._download_raw());self.assertEqual(measured.step_count,count)
ledger.step();plain.step()
self.exact_raw(measured._download_raw(),plain._download_raw())
ledger.report(f'L1 rho={rho}')
def test_four_level_interval_ledger(self):
cfg=LBMConfig(nx=48,ny=48,viscosity=.02,ddf_shifting=True)
obj=circle((16.,16.),.5);j=drive((16.,16.),.5)
with CudaContext(0) as ctx,ExitStack() as owned:
settings=dict(context=ctx,refinements=[[(6,6,26,26)],[(20,20,44,44)],[(56,56,72,72)]],
level_blocks=[(4,4)]*4,level_objects=[(),(),(),(obj,)],level_jets=[(),(),(),(j,)])
measured=owned.enter_context(FixedPatchStepper(cfg,observe_raw_exchange=True,**settings))
plain=owned.enter_context(FixedPatchStepper(cfg,**settings))
measured._initialize_raw(initial(measured,1.01));plain._initialize_raw(initial(plain,1.01))
ledger=Ledger(self,measured,3)
for speed in SPEEDS:
measured.set_jet_amplitude(0,speed,level=3);plain.set_jet_amplitude(0,speed,level=3)
ledger.step();plain.step();self.exact_raw(measured._download_raw(),plain._download_raw())
ledger.report('L3 rho=1.01')
def test_live_jet_remap_then_both_ledgers_continue(self):
cfg=LBMConfig(nx=48,ny=32,viscosity=.1,ddf_shifting=True)
parents=((4,4,20,28),(24,4,44,28));a=((16,16,32,40),(56,16,72,40));b=(a[0],(60,16,76,40))
obj=circle((12.,14.),.8);j=drive((12.,14.),.8)
with CudaContext(0) as ctx,ExitStack() as owned:
old=owned.enter_context(FixedPatchStepper(cfg,parents,context=ctx,base_block=(4,4),fine_block=(4,4),
child_block=(4,4),child_rectangles_in_parent=a,child_objects=(obj,),child_jets=(j,),observe_raw_exchange=True))
old._initialize_raw(initial(old,.99));ledger=Ledger(self,old,2)
for speed in (.002,-.001):old.set_jet_amplitude(0,speed,level=2);ledger.step()
before=old._download_raw();count=old.step_count
topology=lambda rects:RemapTopology.from_config(cfg,old.patch,rects,child_objects=(obj,),
child_jets=old._level_jets[2],_shifted_blocked=True)
expected=_remap_stored(topology(a),topology(b),before,shifted=True,base_count=count,
level_steps=(count,2*count,4*count),phase='postcollision_g')
new=owned.enter_context(old.remap_child(b,child_objects=(obj,)))
self.exact_raw(before,old._download_raw())
self.exact_raw(_StoredPopulations(expected.g,True),new._download_raw())
self.assertEqual(new._level_jets[2][0].amplitude,-.001)
np.testing.assert_array_equal(new._level_boundaries[2].tables['surface_velocity'],old._level_boundaries[2].tables['surface_velocity'])
newledger=Ledger(self,new,2);ledger.step();newledger.step()
self.assertEqual((old.step_count,new.step_count),(count+1,count+1))
ledger.report('L2 source');newledger.report('L2 remapped')
if __name__=='__main__':unittest.main()