Files
CelerisLab/tests/integration/test_state_gpu.py
T
TRON 53fad76759 test: cover TRT restart and actual-rate refinement phase
Integrate reviewed test changes from 7c1517de819741907fb945ea0ab7864734fa3856 and 340ba4e1dc6386581839c0b28c8fe8a1f4aeb70b unchanged. Document bounded 2D implementation, evidence and open gates; production source and historical manifests are unchanged.
2026-10-18 07:54:54 -07:00

187 lines
7.9 KiB
Python

"""Bounded GPU regressions for repaired layout/state contracts (D2Q9, low Ma)."""
import numpy as np
import pytest
from ._cases import make_sim
from CelerisLab.lbm.descriptors import FLUID
def physical_esopull(sim):
"""Decode physical next-pull state without changing the solver clock."""
with sim.ctx.activate():
indices = np.arange(sim.field.n,dtype=np.uint32)
scratch, values, idx_gpu = sim.field.esopull_sync_decode(
indices,sim.stepper.step_count,sim.stream)
result = values.copy()
scratch.free()
idx_gpu.free()
return result
@pytest.mark.parametrize('precision',['FP32','FP16S'])
@pytest.mark.parametrize('shifted',[False,True])
def test_esopull_field_snapshot_across_one_step(precision,shifted):
with make_sim(nx=48,ny=24,streaming='esopull',precision=precision,shifted=shifted) as sim:
sim.add_body('circle',center=(16,12),radius=3)
sim.initialize()
sim.run(3)
saved = physical_esopull(sim)
sim.snapshot()
sim.set_body(0,omega=.0003)
sim.run(1)
current_step = sim.stepper.step_count
action = sim.bodies.action.copy()
sim.restore()
assert sim.stepper.step_count == current_step == 4
np.testing.assert_array_equal(sim.bodies.action,action)
# Encoding roundtrip can lose a float32 addition/subtraction ulp when
# adding lattice weights around shifted FP16 storage; it cannot stream.
np.testing.assert_allclose(physical_esopull(sim),saved,rtol=0,atol=1e-7)
@pytest.mark.parametrize('streaming',['double_buffer','esopull'])
@pytest.mark.parametrize('precision',['FP32','FP16S'])
def test_shifted_topology_add_remove_preserves_cache_and_finite_flow(streaming,precision):
with make_sim(nx=48,ny=24,streaming=streaming,precision=precision,shifted=True) as sim:
sim.initialize()
sim.run(3)
sim.get_ddf() # Fill the same-count host cache before a geometry change.
sim.add_body('circle',center=(18,12),radius=3)
sim.sync_bodies()
assert sim.stepper.step_count == 3
assert sim.field._host_ddf_step is None
sim.run(3)
assert np.all(np.isfinite(sim.read_force(0)))
sim.get_ddf()
sim.remove_body(0)
sim.sync_bodies()
assert sim.stepper.step_count == 6
assert sim.field._host_ddf_step is None
sim.run(3)
macro = sim.get_macroscopic()
fluid = (sim.get_flags().reshape(24,48) & FLUID) != 0
assert all(np.isfinite(array[fluid]).all() for array in macro.values())
assert np.min(macro['rho'][fluid]) > .5
assert np.max(macro['rho'][fluid]) < 1.5
@pytest.mark.parametrize('collision,streaming', [
pytest.param('SRT', 'double_buffer', id='double_buffer'),
pytest.param('SRT', 'esopull', id='esopull'),
pytest.param('TRT', 'double_buffer', id='trt-double_buffer'),
])
def test_checkpoint_exact_continuation_and_controls(collision,streaming,tmp_path):
# Same collision throughout save/load/continue; this is not a method-switch restart.
with make_sim(nx=48,ny=24,streaming=streaming,shifted=True,collision=collision) as sim:
sim.add_body('circle',center=(14,12),radius=3)
sim.add_body('force_region',center=(30,12),radius=2)
sim.initialize()
sim.set_force(1,fx=1e-5,fy=-2e-6)
sim.set_body(0,omega=.0002)
sim.run(3)
saved = sim.get_ddf()
path = sim.save_checkpoint(str(tmp_path/'restart.h5'))
sim.run(4)
expected = sim.get_ddf()
sim.set_force(1,fx=0,fy=0)
sim.set_body(0,omega=0)
# Force the exact count cache collision which formerly skipped D2H.
sim.field._host_ddf_step = 3
sim.field.ddf.fill(99)
sim.load_checkpoint(path)
assert sim.stepper.step_count == 3
np.testing.assert_array_equal(sim.get_ddf(),saved)
np.testing.assert_allclose(sim.bodies.action[[5,6,7]],[.0002,1e-5,-2e-6])
sim.run(4)
np.testing.assert_array_equal(sim.get_ddf(),expected)
@pytest.mark.parametrize('streaming', ['double_buffer', 'esopull'])
@pytest.mark.parametrize('periodic_x', [False, True])
def test_checkpoint_domain_exact_continuation(streaming, periodic_x, tmp_path):
# Same low-Mach forced channel as the Poiseuille runner, bounded to 7 steps.
kwargs = dict(nx=16, ny=18, streaming=streaming, collision='MRT',
shifted=True, viscosity=.1, velocity=0.)
with make_sim(**kwargs) as source:
source.initialize()
if periodic_x:
source.field.flag.reshape(18, 16)[1:-1, [0, -1]] = FLUID
source.field.upload_flags()
source.update_runtime_params(fx=8 * .1 * .03 / 16**2)
source.run(3)
flags = source.get_flags().copy()
saved = source.get_ddf().copy()
path = source.save_checkpoint(str(tmp_path / 'domain.h5'))
source.run(4)
expected = source.get_ddf().copy()
# Restore into this instance and also a fresh default-domain instance.
source.load_checkpoint(path)
np.testing.assert_array_equal(source.get_flags(), flags)
np.testing.assert_array_equal(source.get_ddf(), saved)
source.run(4)
np.testing.assert_array_equal(source.get_ddf(), expected)
with make_sim(**kwargs) as target:
target.initialize()
target.load_checkpoint(path)
assert target.stepper.step_count == 3
np.testing.assert_array_equal(target.get_flags(), flags)
np.testing.assert_array_equal(target.get_ddf(), saved)
target.run(4)
np.testing.assert_array_equal(target.get_ddf(), expected)
@pytest.mark.parametrize('periodic_x', [False, True])
def test_checkpoint_geometry_mismatch_leaves_device_unchanged(tmp_path, periodic_x):
with make_sim(nx=48,ny=24) as source:
source.add_body('circle',center=(14,12),radius=3)
source.initialize()
if periodic_x:
source.field.flag.reshape(24, 48)[1:-1, [0, -1]] = FLUID
source.field.upload_flags()
source.run(2)
path = source.save_checkpoint(str(tmp_path/'geometry.h5'))
with make_sim(nx=48,ny=24) as target:
target.add_body('circle',center=(28,12),radius=3)
target.initialize()
target.run(3)
before = target.get_ddf()
flags = target.get_flags()
with pytest.raises(ValueError,match='geometry'):
target.load_checkpoint(path)
assert target.stepper.step_count == 3
target.field.invalidate_host_ddf_cache()
np.testing.assert_array_equal(target.get_ddf(),before)
np.testing.assert_array_equal(target.get_flags(),flags)
def test_periodic_checkpoint_restores_observation_interval(tmp_path,monkeypatch):
monkeypatch.chdir(tmp_path)
with make_sim(nx=48,ny=24) as sim:
sim.add_body('sensor',center=(24,12),radius=2)
sim.initialize()
sim.run(6,checkpoint_interval=2)
sim.load_checkpoint(str(tmp_path/'output/checkpoint_00000002.h5'))
assert sim.stepper.step_count == 2
assert sim.bodies._obs_accum_steps == 2
raw = sim.read_sensor(0,normalize=False)
assert np.linalg.norm(raw) > 0
np.testing.assert_allclose(sim.read_sensor(0),raw/2,rtol=1e-6)
def test_checkpoint_preserves_pending_and_applied_controls(tmp_path):
with make_sim(nx=48,ny=24,shifted=True) as sim:
sim.add_body('force_region',center=(24,12),radius=3)
sim.initialize()
sim.set_force(0,fx=1e-5,fy=-2e-6)
sim.run(3)
sim.set_force(0,fx=3e-5,fy=4e-6) # Host target has not been uploaded.
pending = sim.bodies.action.copy()
path = sim.save_checkpoint(str(tmp_path/'pending.h5'))
sim.run(4,upload_act=False)
expected = sim.get_ddf()
sim.set_force(0,fx=0,fy=0)
sim.load_checkpoint(path)
np.testing.assert_array_equal(sim.bodies.action,pending)
sim.run(4,upload_act=False)
np.testing.assert_array_equal(sim.get_ddf(),expected)