Files
CelerisLab/tests/integration/test_runtime_contracts_gpu.py
T
ClaudeandClaude Opus 5.5 d318911221 cuda: compiler_v2 module becomes compiler
The package already exposed it as CelerisLab.cuda.compiler; the module now
has that name and every tip caller, doc and current-tree test imports it
directly (no alias).

Historical-revision tooling is not edited: the Taylor-Green harness
(run_case.py, hash-pinned check_projected_pairs.py, assess receipts) only
runs against a135252, whose package has cuda/compiler_v2.py, and the J
campaign runners and dated docs describe past trees. Its two current-tree
tests now import that package from git (historical_package) instead of the
tip, and the kernel-root test first checks the accepted case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 19:15:50 +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
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.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())