"""Host geometry, receipt-corruption tests and mocked scheduling; no CUDA/flow.""" from contextlib import contextmanager, nullcontext, redirect_stdout from copy import deepcopy from dataclasses import asdict, replace import hashlib import io import json from pathlib import Path import subprocess import sys import tempfile from types import SimpleNamespace import unittest from unittest.mock import patch import numpy as np import run_case from assess_projected_pairs import (BUILD_SOURCES, CASES, NUMERICS, NUMERICAL_REVISION, SOURCE_SHA256, assess) from oracle import LIMITS, TIMES, U, rectangles REPO = Path(__file__).resolve().parents[3] OLD_REVISION = "519473a61c7b319183aa54e55b9f097499dde0bf" MOCK_REVISION = "f" * 40 @contextmanager def historical_package(): """Import CelerisLab from NUMERICAL_REVISION, the only tree run_case accepts. The harness and its pinned build-capture helper are tooling for that revision (cuda/compiler_v2.py); the current package is restored afterwards. """ import tarfile archive = subprocess.run(["git", "-C", str(REPO), "archive", "--format=tar", NUMERICAL_REVISION, "src/CelerisLab"], capture_output=True, check=True).stdout before = set(sys.modules) saved = {k: v for k, v in sys.modules.items() if k == "CelerisLab" or k.startswith("CelerisLab.")} with tempfile.TemporaryDirectory() as tmp: root = Path(tmp).resolve() with tarfile.open(fileobj=io.BytesIO(archive)) as tar: tar.extractall(root) for name in saved: del sys.modules[name] sys.path.insert(0, str(root / "src")) try: yield root finally: sys.path.remove(str(root / "src")) for name in set(sys.modules) - before | set(saved): # nothing keeps the old tree sys.modules.pop(name, None) sys.modules.update(saved) def historical_runtime_source(repo): """runtime_source as run_case records it for an accepted a135252 checkout.""" package = repo / "src/CelerisLab" return dict(checkout=str(repo), module=str(package / "__init__.py"), compiler=str(package / "cuda/compiler_v2.py"), kernel_root=str(package / "lbm/kernels")) RUNTIME = dict(device=dict(name="host mock", pci_bus_id="mock", capability=[7, 0], driver=12040, runtime=[12, 4, 0]), nvcc_path="/mock/nvcc", nvcc_version="mock nvcc", python="mock Python", numpy=np.__version__, pycuda="mock") def source(): return dict(revision=MOCK_REVISION, dirty=False, source_tree_sha256=SOURCE_SHA256, harness_sha256={"run_case.py": "a" * 64}, build_capture_sha256=run_case.BUILD_CAPTURE_SHA256) def build_metadata(size): cfg = replace(run_case.config(size, 100), nx=10, ny=10) cfg.validate() return dict(source_sha256=BUILD_SOURCES[size], config=asdict(cfg), n_objects=0, arch="sm_70", flags=["--use_fast_math"], ptx_sha256="b" * 64) def records(early=False): from CelerisLab.lbm.hierarchy import build_hierarchy from CelerisLab.lbm.refinement import ACTIVE out = [] for re, n, depth, scheme in CASES: cfg = run_case.config(n, re) refinements = [] if depth == 1 else rectangles(n, depth) h = build_hierarchy(cfg, refinements) main_steps, early_steps = run_case.sample_steps(n, early) out.append(dict( re_L=re, n=n, depth=depth, explosion=scheme, U=U, mach=float(U*np.sqrt(3)), viscosity=cfg.viscosity, tau_by_level=[0.5+3*c.viscosity for c in h.configs], refinements=refinements, numerics=NUMERICS.copy(), initialization=run_case.INITIALIZATION, source=source(), numerical_revision=NUMERICAL_REVISION, reference_harness=run_case.REFERENCE_HARNESS, actual_build_sidecars=[build_metadata(n * 2**level) for level in range(depth)], runtime_identity=deepcopy(RUNTIME), cuda_visible_devices="0", runtime_source=historical_runtime_source(REPO), completed=True, workload=run_case.workload([k == ACTIVE for k in h.kinds], 16*n), rows=[dict(step=s, tstar=s*U/n, **{k: 0.0 for k in LIMITS}) for s in main_steps], early_pressure=dict(enabled=early, rows=[ dict(step=s, tstar=s*U/n, pressure_l2=.01 if s not in main_steps else 0.0) for s in early_steps], sampled_max_pressure_l2=.01 if early else None))) return out class ProjectedPairsTests(unittest.TestCase): def test_exact_geometry_work_and_samples(self): from CelerisLab.lbm.hierarchy import build_hierarchy, leaf_partition data = records(True) self.assertEqual(sum(r["workload"]["active_collision_updates"] for r in data), 589299712) for r in data: n, depth = r["n"], r["depth"] h = build_hierarchy(run_case.config(n, 100), r["refinements"]) np.testing.assert_array_equal(leaf_partition(h), np.ones((n, n))) main, early = run_case.sample_steps(n, True) self.assertEqual(main, [0, 4*n, 8*n, 12*n, 16*n]) self.assertEqual(early, [k*(n//16) for k in range(65)]) np.testing.assert_allclose(np.array(early)*U/n, np.arange(65)*.001875, rtol=0, atol=3e-17) if depth == 2: self.assertEqual(r["refinements"], [[(16, 16, 112, 96)]]) self.assertTrue(assess(data)["passed"]) self.assertTrue(assess(records())["passed"]) def test_exact_old519_geometry_and_table_parity(self): from CelerisLab.lbm.blocks import BlockLayout2D from CelerisLab.lbm.hierarchy import build_hierarchy from CelerisLab.lbm.refinement_tables import projected_explosion_tables # Frozen from executing exact 519473a61c7b319183aa54e55b9f097499dde0bf # geometry and table definitions, after checking unchanged config, # descriptors and blocks. No historical Git object is needed at runtime. # Historical source SHA256, refinement.py then refinement_tables.py: # 08c2a0557276a98cbcaa459d9bb7a9735d8bbbf64a6114c57af2fda61481a687 # dabff948bb30496384d1a52c666b0edbc2085f9c61ed1bbc39f748502629113d frozen = { "covered": ("|b1", (128, 128), "14a1b5be54e3309bea825a00a653105951f0c805622b877a86dd453d65556df3"), "fine_kind": ("|u1", (256, 256), "891ff72c3d60686c62b1f35ffe59d054ac23b8f9d59aeb5782bc941202794cc0"), "ghost_fine": ("= 0 if not self.assertion: raise AssertionError("backward step") if fail == "resource" and self.step_count >= 16: raise MemoryError("mock resource failure") self.step_count += count calls.append(self.step_count) def download(self): return None modules = { "CelerisLab.cuda.context": SimpleNamespace(CudaContext=FakeRun), "CelerisLab.lbm.block_stepper": SimpleNamespace(PeriodicBlockStepper=FakeRun), "CelerisLab.lbm.refinement_stepper": SimpleNamespace(FixedPatchStepper=FakeRun), } def metric(arrays, masks, step, n, nu, mass): return dict(step=step, tstar=step*U/n, mass=16384., **{k: float("nan") if ((fail == "nonfinite" and step == 16) or (fail == "probe_nonfinite" and step == 1)) and k == "energy_relative" else 0.0 for k in LIMITS}) with tempfile.TemporaryDirectory() as tmp: output = Path(tmp) / "case.json" argv = ["run_case.py", "--repo", str(REPO), "--re", "100", "--n", "128", "--depth", str(depth), "--output", str(output), "--expected-revision", MOCK_REVISION, "--run-gpu"] if early: argv += ["--early-pressure", "--explosion", "projected_linear"] with patch.dict(sys.modules, modules), patch.object(sys, "argv", argv), \ patch.dict("os.environ", CUDA_VISIBLE_DEVICES="mock"), \ patch.object(run_case, "verify_source", return_value=source()) as verify, \ patch.object(run_case, "runtime_source", return_value=historical_runtime_source(REPO)), \ patch.object(run_case, "runtime_identity", return_value=deepcopy(RUNTIME)), \ patch.object(run_case, "build_capture", return_value=nullcontext()), \ patch.object(run_case, "metrics", side_effect=metric), redirect_stdout(io.StringIO()): if fail: with self.assertRaises(MemoryError if fail == "resource" else FloatingPointError): run_case.main() else: self.assertEqual(run_case.main(), 0) receipt = json.loads(output.read_text()) verify.assert_called_once_with(REPO, MOCK_REVISION) self.assertEqual(selectors, ["projected_linear" if early else "constant"] if depth == 2 else []) self.assertEqual(receipt["source"], source()) self.assertEqual(receipt["runtime_identity"], RUNTIME) self.assertEqual(receipt["completed"], not fail) self.assertEqual(calls[:2], [0, 1]) # READY probe is part of this trajectory if not fail: self.assertEqual([r["tstar"] for r in receipt["rows"]], TIMES) self.assertEqual(len(receipt["early_pressure"]["rows"]), 65 if early else 0) self.assertEqual(calls[-1], 2048) else: self.assertEqual(len(receipt["rows"]), 1) self.assertEqual(len(receipt["early_pressure"]["rows"]), {"resource": 3, "nonfinite": 2, "probe_nonfinite": 1}[fail]) if fail == "probe_nonfinite": self.assertNotIn("ready_after_step", receipt) if __name__ == "__main__": unittest.main()