Add opt-in shifted raw remap GPU lifecycle gate
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"""Opt-in GPU raw shifted remap lifecycle gates; no precision/performance claim.
|
||||
|
||||
Run only on an authorized GPU:
|
||||
PYTHONPATH=src python tests/validation/multigrid/test_shifted_remap_gpu.py -v
|
||||
Frozen gates: bitwise raw overlap/copy and identity continuation; moved-remap
|
||||
matches the existing host fill loop exactly; finite owned leaves after stepping.
|
||||
"""
|
||||
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.lbm.population_storage import _StoredPopulations
|
||||
from CelerisLab.lbm.refinement_stepper import FixedPatchStepper
|
||||
from CelerisLab.lbm.synchronized_remap import RemapTopology, _remap_stored
|
||||
|
||||
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),)
|
||||
B=((20,16,36,40),)
|
||||
|
||||
|
||||
def make(ctx):
|
||||
return FixedPatchStepper(CFG,PARENTS,context=ctx,base_block=(4,4),
|
||||
fine_block=(4,4),child_block=(4,4),
|
||||
child_rectangles_in_parent=A)
|
||||
|
||||
|
||||
def initial(run):
|
||||
# Non-unit density, nonzero velocity and nonequilibrium perturbations.
|
||||
w=np.array([4/9,*([1/9]*4),*([1/36]*4)],np.float64)
|
||||
cx=np.array([0,1,-1,0,0,1,-1,1,-1]);cy=np.array([0,0,0,1,-1,1,-1,-1,1])
|
||||
rho,u,v=.99,.013,-.007
|
||||
cu=3*(cx*u+cy*v)
|
||||
eq=w*((rho-1)+rho*(cu+.5*cu*cu-1.5*(u*u+v*v)))
|
||||
rng=np.random.default_rng(8473)
|
||||
arrays=tuple((np.broadcast_to(eq[:,None,None],level.shape)+
|
||||
rng.normal(0,1e-7,level.shape)).astype(np.float32) for level in run._levels)
|
||||
return _StoredPopulations(arrays,True)
|
||||
|
||||
|
||||
class ShiftedRemapGPU(unittest.TestCase):
|
||||
def exact(self,a,b,label):
|
||||
self.assertEqual(a.shape,b.shape,label)
|
||||
self.assertEqual(a.dtype,b.dtype,label)
|
||||
self.assertEqual(a.tobytes(),b.tobytes(),label)
|
||||
|
||||
def snapshot_equal(self,a,b,label):
|
||||
self.assertTrue(a.shifted and b.shifted)
|
||||
self.assertEqual(len(a.values),len(b.values))
|
||||
for x,y in zip(a.values,b.values):self.exact(x,y,label)
|
||||
|
||||
def check_reset(self,run,wanted):
|
||||
for level,want in zip(run._levels,wanted):
|
||||
blocked=level.layout.to_blocked(want)
|
||||
for pointer in (level.current,level.spare):
|
||||
actual=np.empty(level.layout.blocked_shape,np.float32)
|
||||
cuda.memcpy_dtoh(actual,pointer)
|
||||
self.exact(actual,blocked,'fresh current and spare')
|
||||
|
||||
def check_finite(self,run):
|
||||
raw=run._download_raw()
|
||||
self.assertTrue(raw.shifted)
|
||||
for a,leaf in zip(raw.values,run._leaf):
|
||||
self.assertTrue(np.isfinite(a[:,leaf]).all())
|
||||
self.assertTrue(np.isnan(a[:,~leaf]).all())
|
||||
self.assertTrue((1+a[:,leaf].sum(axis=0,dtype=np.float64)>0).all())
|
||||
|
||||
def test_identity_next_step_bitwise(self):
|
||||
with CudaContext(0) as ctx,ExitStack() as owned:
|
||||
old=owned.enter_context(make(ctx))
|
||||
old._initialize_raw(initial(old));old.step(2)
|
||||
before=old._download_raw()
|
||||
new=owned.enter_context(old.remap_child(A))
|
||||
self.check_reset(new,before.values)
|
||||
self.snapshot_equal(old._download_raw(),before,'old unchanged')
|
||||
self.snapshot_equal(new._download_raw(),before,'identity raw copy')
|
||||
old.step();new.step()
|
||||
self.assertEqual((old.step_count,new.step_count),(3,3))
|
||||
self.snapshot_equal(old._download_raw(),new._download_raw(),'identity next step')
|
||||
self.check_finite(old);self.check_finite(new)
|
||||
|
||||
def test_moved_remap_matches_raw_host_and_old_remains_usable(self):
|
||||
with CudaContext(0) as ctx,ExitStack() as owned:
|
||||
old=owned.enter_context(make(ctx))
|
||||
old._initialize_raw(initial(old));old.step(2)
|
||||
before=old._download_raw();count=old.step_count
|
||||
old_mesh=RemapTopology.from_config(CFG,old.patch,A)
|
||||
new_mesh=RemapTopology.from_config(CFG,old.patch,B)
|
||||
expected=_remap_stored(old_mesh,new_mesh,before,shifted=True,
|
||||
base_count=count,level_steps=(count,2*count,4*count),phase='postcollision_g')
|
||||
new=owned.enter_context(old.remap_child(B))
|
||||
self.check_reset(new,expected.g)
|
||||
self.snapshot_equal(new._download_raw(),_StoredPopulations(expected.g,True),'host raw fill')
|
||||
for a,b,om,nm in zip(before.values,new._download_raw().values,old_mesh.leaf_masks,new_mesh.leaf_masks):
|
||||
self.exact(a[:,om&nm],b[:,om&nm],'physical same-level overlap')
|
||||
self.snapshot_equal(old._download_raw(),before,'source unchanged')
|
||||
old.step();new.step(2)
|
||||
self.assertEqual((old.step_count,new.step_count),(3,4))
|
||||
self.check_finite(old);self.check_finite(new)
|
||||
|
||||
def test_rejected_remap_does_not_mutate_source(self):
|
||||
with CudaContext(0) as ctx,make(ctx) as run:
|
||||
run._initialize_raw(initial(run));run.step(2)
|
||||
before=run._download_raw()
|
||||
with self.assertRaises(ValueError):run.remap_child([])
|
||||
self.snapshot_equal(run._download_raw(),before,'rejection preserves source')
|
||||
self.assertEqual(run.step_count,2)
|
||||
run.step();self.check_finite(run)
|
||||
|
||||
|
||||
if __name__=='__main__':unittest.main()
|
||||
Reference in New Issue
Block a user