Files
CelerisLab/tests/integration/test_runtime_contracts_gpu.py
T
ClaudeandClaude Opus 5.5 2b3c2a7529 kernels: name compilation entries by storage route (uniform.cu, blocked.cu)
kernel_v2.cu -> uniform.cu (whole-domain NX/NY, Simulation) and
blocks.cu -> blocked.cu (one block window, every blocked level), with
InitTubeFlow_v2 -> InitTubeFlow. All live callers, include comments,
CONFIG.md, lifecycle doc and current-tree tests move together; no alias.
refinement.cu keeps its name until the dense-route decision.
Historical-revision tooling that names the old files (LES source pins,
timing/J campaign runners, TG run_case, supported_settings citations at
888c8038, development notes) is unchanged on purpose.
No arithmetic change; the PTX symbol InitTubeFlow_v2 is renamed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 14:07:34 +00:00

98 lines
3.7 KiB
Python

"""GPU ownership, isolated builds and immutable allocation contracts."""
from pathlib import Path
import numpy as np
import pycuda.driver as cuda
import pytest
import CelerisLab
from CelerisLab.cuda import compiler_v2
from ._cases import make_sim
def test_source_provenance():
root = Path(__file__).resolve().parents[2] / "src/CelerisLab"
assert Path(CelerisLab.__file__).resolve().parent == root
assert Path(compiler_v2.kernel_path("uniform.cu")).resolve() == root / "lbm/kernels/uniform.cu"
def test_two_contexts_interleaved_and_non_lifo_close():
before = cuda.Context.get_current()
first = make_sim(nx=32, ny=16)
second = make_sim(nx=48, ny=24, streaming="esopull", shifted=True)
try:
assert first._module is not second._module
assert cuda.Context.get_current() == before
first.initialize()
second.initialize()
for _ in range(3):
first.run(1)
second.run(1)
assert first.get_macroscopic()["rho"].shape == (16, 32)
assert second.get_macroscopic()["rho"].shape == (24, 48)
first.close()
second.run(1)
assert np.isfinite(second.get_macroscopic()["rho"]).all()
assert cuda.Context.get_current() == before
finally:
second.close()
first.close()
def test_structural_change_rejected_before_running():
with make_sim(nx=32, ny=16) as sim:
sim.initialize()
before = sim.get_ddf().copy()
sim.lbm_cfg.nx = 64
with pytest.raises(ValueError, match="allocation|structural"):
sim.run(1)
with pytest.raises(ValueError, match="allocation|structural"):
sim._recompile()
sim.lbm_cfg.nx = 32
np.testing.assert_array_equal(sim.get_ddf(), before)
assert sim.stepper.step_count == 0
sim.run(1)
def test_viscosity_recompile_updates_device_omega():
import struct
with make_sim(nx=32, ny=16) as sim:
sim.lbm_cfg.viscosity = 0.08
sim.recompile()
assert sim.stepper.step_count == 0
sim.initialize()
with sim.ctx.activate():
ptr, _ = sim._module.get_global("d_params")
buf = np.empty(20, dtype=np.uint8)
cuda.memcpy_dtoh(buf, ptr)
omega = struct.unpack("=ffffI", buf.tobytes())[0]
assert omega == pytest.approx(sim.lbm_cfg.omega, rel=1e-7)
sim.run(1)
@pytest.mark.parametrize("streaming",["double_buffer","esopull"])
@pytest.mark.parametrize("prior_steps",[3,4])
def test_reinitialize_matches_fresh_state_and_observation_window(streaming,prior_steps):
with make_sim(nx=32,ny=16,streaming=streaming,shifted=True) as fresh, \
make_sim(nx=32,ny=16,streaming=streaming,shifted=True) as reused:
for sim in (fresh,reused):
sim.add_body("sensor",center=(16,8),radius=2)
sim.initialize()
reused.run(prior_steps)
assert reused.stepper.step_count==prior_steps
assert reused.bodies._obs_accum_steps==prior_steps
reused.initialize()
assert reused.stepper.step_count==0
assert reused.bodies._obs_accum_steps==0
np.testing.assert_array_equal(reused.bodies.obs_pinned,0)
with reused.ctx.activate():
obs=np.empty(reused.bodies.obs_total_floats,np.float32)
cuda.memcpy_dtoh(obs,reused.bodies.obs_gpu)
np.testing.assert_array_equal(obs,0)
np.testing.assert_array_equal(reused.get_ddf(),fresh.get_ddf())
for key,value in fresh.get_macroscopic().items():
np.testing.assert_array_equal(reused.get_macroscopic()[key],value)
# Equal next step proves restored parity, not only equal initialization bytes.
fresh.run(1);reused.run(1)
np.testing.assert_array_equal(reused.get_ddf(),fresh.get_ddf())