From 95cb6d2b9f43c1bdd46cbf27f03f0dfc0877cfb0 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 18 Oct 2026 07:58:41 -0700 Subject: [PATCH] fix: integrate reviewed LES normalization with explicit dev flow pins --- .../development/les_dev8403956_integration.md | 62 ++++++++++ .../lbm/kernels/operators/turbulence_smag.cuh | 10 +- tests/unit/test_les_flow_runner.py | 50 ++++---- tests/unit/test_smagorinsky_normalization.py | 99 ++++++++++++++++ .../data/les_flow/source_pins_dev8403956.json | 109 ++++++++++++++++++ tests/validation/run_uniform_les_flow.py | 25 +++- 6 files changed, 326 insertions(+), 29 deletions(-) create mode 100644 docs/development/les_dev8403956_integration.md create mode 100644 tests/unit/test_smagorinsky_normalization.py create mode 100644 tests/validation/data/les_flow/source_pins_dev8403956.json diff --git a/docs/development/les_dev8403956_integration.md b/docs/development/les_dev8403956_integration.md new file mode 100644 index 0000000..8e07a9a --- /dev/null +++ b/docs/development/les_dev8403956_integration.md @@ -0,0 +1,62 @@ +# Selective LES normalization integration on dev8403956 + +This change reuses the reviewed correction from 5465832 without merging its +branch history. The only production change is the two closed-form coefficients +18 -> 18 sqrt(2) and their explanatory comments in turbulence_smag.cuh. Its SHA256 +is a3db46c2e7d27530b2d1436e65cdbed0b72cc7e047bfbeef2a640877271b51aa. +Defaults, iterative/deviatoric/capped closure, collision dispatch, AMR guards, +and all other production source files are unchanged. + +## Separate source contract; unchanged experiment + +The single existing flow runner now accepts `--experiment dev8403956`. +Its separate source_pins_dev8403956.json pins all 101 production files from the +committed 8403956 baseline; corrected mode permits only the exact reviewed +header substitution. Results record the actual checkout commit, import path, +experiment name and manifest SHA256. No arbitrary manifest-path override exists. + +Omitting the option still selects the original historical source manifest. +That manifest, the original paired receipts, scalar preparation manifests and +independent continuum oracle are unchanged. Historical baseline and corrected +receipts remain evidence for their own exact sources, not new dev receipts. +Historical guards intentionally reject this different dev source tree. Eight +production files already differed between the historical source snapshot and +8403956; neither the old manifest nor its acceptance is silently regenerated. + +The physical experiment and every numerical acceptance threshold are unchanged: +8 x 34 nodes, halfway y walls, periodic x, unforced D2Q9/SRT, shifted FP32, +double-buffer streaming, Cs=0/.16/1, checkpoints 519/1038/2075. Each case has one +actual advancing warm-up followed by complete initial-state restoration before +READY. Total launches are 6,225 measured plus three warm-ups. Cs=1 remains an +amplified diagnostic, not a physical coefficient recommendation. + +## Host checks + + PYTHONDONTWRITEBYTECODE=1 python -m unittest discover -s tests/unit -p 'test_smagorinsky_normalization.py' -v + PYTHONDONTWRITEBYTECODE=1 python -m unittest discover -s tests/unit -p 'test_les_flow_*.py' -v + +The direct-current-header test is copied unchanged from reviewed 5465832: +120 tensor parameter cases, Cs=0/equilibrium and omega guards, converged +trace-free iterative comparison, and mutation detection in both lattice +algebras. This is scalar D2/D3 algebra coverage, not 3D-flow qualification. +Flow tests retain analytic/reference checks and add current named-source, +reverse-patched baseline, variant rejection, unrelated-source tampering and +immutable historical-default checks. The temporary baseline is constructed +only inside a test directory; production is never reverse-patched. + +## Pending final-commit GPU gate + +After independent review and explicit executor authorization, run from the +exact committed candidate checkout, selecting the authorized physical GPU: + + CUDA_VISIBLE_DEVICES= PYTHONDONTWRITEBYTECODE=1 python tests/validation/run_uniform_les_flow.py --root "$PWD" --experiment dev8403956 --variant corrected --run-gpu --device 0 --output /corrected.json + +Retain all source hashes, exact commit/import/device identity, manifest hash, +three READY/reset records, complete result JSON, stdout/stderr and actual exit. +Require all frozen gates, DONE passed=true and exit 0. Compare with historical +corrected evidence without relabeling it as current evidence. The old-vs-new +normalization discrimination already exists; no new baseline GPU run is needed +unless review finds a changed exercised uniform solver path. + +No GPU result is claimed by this integration preparation. This does not expand +LES qualification to MRT, forcing, AMR LES or three-dimensional turbulent flows. diff --git a/src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh b/src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh index 0ff99e8..76e2747 100644 --- a/src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh +++ b/src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh @@ -5,7 +5,9 @@ // nu_t = (C_s * Delta)^2 * |S|, Delta = 1 (lattice unit) // omega_eff = 1 / (3*(nu + nu_t) + 0.5) // -// |S| is estimated from non-equilibrium second-order moments using tau0. +// |S| = sqrt(2 S_ij S_ij), with Delta = 1 in lattice units. +// The stress estimate uses self-consistent tau_eff; tau0 is molecular. +// Closed form uses full stress. Iteration uses deviatoric stress and a cap. // ============================================================================ #ifndef CELERIS_OPERATORS_TURBULENCE_SMAG_CUH @@ -65,12 +67,12 @@ __device__ __forceinline__ float compute_omega_smag(const float* __restrict__ f, #if LES_CLOSED_FORM // Closed-form Smagorinsky (no iteration): - // ω = 2 / (τ₀ + √(τ₀² + 18·Cs²·√Q / ρ)) + // ω = 2 / (τ₀ + √(τ₀² + 18·√2·Cs²·√Q / ρ)) // where Q = Πneq_ij · Πneq_ij (Frobenius norm squared) const float Q = pixx*pixx + piyy*piyy + 2.0f*pixy*pixy; const float sqrtQ = sqrtf(fmaxf(Q, 0.0f)); const float smag_const_sq = LES_CS * LES_CS; - const float discriminant = tau0*tau0 + 18.0f * smag_const_sq * sqrtQ / rho_safe; + const float discriminant = tau0*tau0 + 18.0f * sqrtf(2.0f) * smag_const_sq * sqrtQ / rho_safe; const float tau_eff = 0.5f * (tau0 + sqrtf(fmaxf(discriminant, tau0*tau0))); return clamp_omega(1.0f / fmaxf(tau_eff, 0.500001f)); #else @@ -140,7 +142,7 @@ __device__ __forceinline__ float compute_omega_smag(const float* __restrict__ f, + 2.0f*(pixy*pixy + pixz*pixz + piyz*piyz); const float sqrtQ = sqrtf(fmaxf(Q, 0.0f)); const float smag_const_sq = LES_CS * LES_CS; - const float discriminant = tau0*tau0 + 18.0f * smag_const_sq * sqrtQ / rho_safe; + const float discriminant = tau0*tau0 + 18.0f * sqrtf(2.0f) * smag_const_sq * sqrtQ / rho_safe; const float tau_eff = 0.5f * (tau0 + sqrtf(fmaxf(discriminant, tau0*tau0))); return clamp_omega(1.0f / fmaxf(tau_eff, 0.500001f)); #else diff --git a/tests/unit/test_les_flow_runner.py b/tests/unit/test_les_flow_runner.py index c8584ea..abb3394 100644 --- a/tests/unit/test_les_flow_runner.py +++ b/tests/unit/test_les_flow_runner.py @@ -41,45 +41,53 @@ class GuardTests(unittest.TestCase): with self.assertRaisesRegex(RuntimeError, "velocity/profile"): decode_macros(invalid, w, cx, cy) - def test_baseline_pins(self): + def test_current_corrected_pins(self): self.assertEqual( - source_guard(ROOT, "baseline")["base_commit"], - "cd3a657f9d3833e812c3cca173525f165b48710a", + source_guard(ROOT, "corrected", "dev8403956")["base_commit"], + "8403956a9afc0b0bb89265cec374392013d89016", ) def test_corrected_and_tamper_guard(self): - pins = json.loads((HERE / "data/les_flow/source_pins.json").read_text()) + pins = json.loads((HERE / "data/les_flow/source_pins_dev8403956.json").read_text()) with TemporaryDirectory() as directory: root = Path(directory) for name in pins["sources"]: dest = root / name dest.parent.mkdir(parents=True, exist_ok=True) shutil.copyfile(ROOT / name, dest) - # Rebuild the reviewed candidate from actual pinned headers, not fixtures. - candidate = root / "header_candidate" - subprocess.run( - [ - sys.executable, - str(HERE / "les/prepare.py"), - "--repo", - str(ROOT), - "--output-dir", - str(candidate), - ], - check=True, - ) - shutil.copyfile(candidate / "patched" / HEADER, root / HEADER) with patch( "run_uniform_les_flow.subprocess.check_output", return_value="isolated-fixture\n", ): - source_guard(root, "corrected") + source_guard(root, "corrected", "dev8403956") with self.assertRaisesRegex(RuntimeError, "Source pin mismatch"): - source_guard(root, "baseline") + source_guard(root, "baseline", "dev8403956") target = root / "src/CelerisLab/lbm/kernels/step/one_step_double.cu" target.write_bytes(target.read_bytes() + b"\n// tamper\n") with self.assertRaisesRegex(RuntimeError, "Source pin mismatch"): - source_guard(root, "corrected") + source_guard(root, "corrected", "dev8403956") + + def test_historical_default_and_manifest_unchanged(self): + manifest = HERE / "data/les_flow/source_pins.json" + self.assertEqual(hashlib.sha256(manifest.read_bytes()).hexdigest(), + "87ca033619da1fc02bee822bfc5681b518a92a155c3c3d1dbbe5265c96066b39") + for variant in ("baseline", "corrected"): + with self.assertRaisesRegex(RuntimeError, "Source pin mismatch"): + source_guard(ROOT, variant) + + def test_named_baseline_accepts_only_reversed_header(self): + pins = json.loads((HERE / "data/les_flow/source_pins_dev8403956.json").read_text()) + with TemporaryDirectory() as directory: + root = Path(directory) + for name in pins["sources"]: + dest = root / name + dest.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / name, dest) + subprocess.run(["git", "apply", "--reverse", str(HERE / "les/data/smagorinsky_normalization.patch")], cwd=root, check=True) + with patch("run_uniform_les_flow.subprocess.check_output", return_value="baseline-fixture\n"): + source_guard(root, "baseline", "dev8403956") + with self.assertRaisesRegex(RuntimeError, "Source pin mismatch"): + source_guard(root, "corrected", "dev8403956") def test_gpu_requires_opt_in_before_import(self): with TemporaryDirectory() as directory: diff --git a/tests/unit/test_smagorinsky_normalization.py b/tests/unit/test_smagorinsky_normalization.py new file mode 100644 index 0000000..300856c --- /dev/null +++ b/tests/unit/test_smagorinsky_normalization.py @@ -0,0 +1,99 @@ +"""Direct-current-header regression; historical candidate manifests stay frozen.""" + +import importlib.util +from pathlib import Path +import shutil +import sys +from tempfile import TemporaryDirectory +import unittest + +ROOT = Path(__file__).resolve().parents[2] +HELPERS = ROOT / "tests/validation/les" +sys.path.insert(0, str(HELPERS)) +spec = importlib.util.spec_from_file_location( + "smagorinsky_oracle", HELPERS / "test_candidate.py" +) +oracle = importlib.util.module_from_spec(spec) +spec.loader.exec_module(oracle) +HEADER = Path("src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh") +DESCRIPTORS = Path("src/CelerisLab/lbm/kernels/core/descriptors.cuh") + + +class SmagorinskyNormalization(unittest.TestCase): + def test_current_header_matches_independent_tensor_oracle(self): + with TemporaryDirectory() as directory: + build_dir = Path(directory) + for dim in (2, 3): + for cs in (0.0, 0.16): + # Compile directly against this checkout, not saved candidate headers. + closed = oracle.build( + ROOT.parent, ROOT.name, dim, cs, 1, 3, build_dir + ) + converged = oracle.build( + ROOT.parent, ROOT.name, dim, cs, 0, 200, build_dir + ) + for rho in (0.7, 1.0, 1.3): + for tau0 in (0.56, 0.8): + for kind, magnitude in ( + ("equilibrium", 0.0), + ("shear", -0.02), + ("shear", 0.02), + ("extension", -0.02), + ("extension", 0.02), + ): + stress = oracle.tensor( + dim, kind, magnitude * rho, 0, dim - 1 + ) + f, eq = oracle.state(dim, rho, stress) + expected = 1 / oracle.standard_tau( + tau0, cs, oracle.norm(stress), rho + ) + with self.subTest( + dim=dim, + cs=cs, + rho=rho, + tau=tau0, + kind=kind, + sign=magnitude, + ): + self.assertAlmostEqual( + closed(f, eq, rho, tau0), expected, delta=3e-6 + ) + self.assertAlmostEqual( + converged(f, eq, rho, tau0), + expected, + delta=3e-6, + ) + if cs == 0: + f, eq = oracle.state( + dim, 1.0, oracle.tensor(dim, "equilibrium", 0.0) + ) + self.assertAlmostEqual( + closed(f, eq, 1.0, 0.50001), 1.99, delta=3e-6 + ) + self.assertAlmostEqual( + closed(f, eq, 1.0, 200.0), 0.01, delta=3e-6 + ) + + def test_removing_sqrt_two_is_detected(self): + with TemporaryDirectory() as directory: + root = Path(directory) + for relative in (HEADER, DESCRIPTORS): + target = root / "old_convention" / relative + target.parent.mkdir(parents=True, exist_ok=True) + shutil.copyfile(ROOT / relative, target) + target = root / "old_convention" / HEADER + text = target.read_text() + factor = "18.0f * sqrtf(2.0f) * smag_const_sq" + self.assertEqual(text.count(factor), 2) + target.write_text(text.replace(factor, "18.0f * smag_const_sq")) + for dim in (2, 3): + old = oracle.build(root, "old_convention", dim, 0.16, 1, 3, root) + stress = oracle.tensor(dim, "shear", 0.02) + f, eq = oracle.state(dim, 1.0, stress) + expected = 1 / oracle.standard_tau(0.8, 0.16, oracle.norm(stress), 1.0) + self.assertGreater(abs(old(f, eq, 1.0, 0.8) - expected), 1e-3) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/validation/data/les_flow/source_pins_dev8403956.json b/tests/validation/data/les_flow/source_pins_dev8403956.json new file mode 100644 index 0000000..c334574 --- /dev/null +++ b/tests/validation/data/les_flow/source_pins_dev8403956.json @@ -0,0 +1,109 @@ +{ + "base_commit": "8403956a9afc0b0bb89265cec374392013d89016", + "production_origin_commit": "8403956a9afc0b0bb89265cec374392013d89016", + "source_experiment": "Explicit dev8403956 uniform SRT LES experiment; historical 9805383/5465832 receipts remain historical. Corrected permits only the reviewed normalization header.", + "sources": { + "src/CelerisLab/__init__.py": "21cc469bd6e970a1f9ff5970c8eea4602065e41fc881854759ee565ee55c10c6", + "src/CelerisLab/body/__init__.py": "e4e0e8f0769a94428d5b02fac790443997556cfc5515a5f1ed1595d2c694d69d", + "src/CelerisLab/body/action_smoother.py": "b1a23ee6bbd4850b172cb8738c5c3546ee465a54189e6af9d8044f023d4687dc", + "src/CelerisLab/body/coupling/__init__.py": "c931f7eb318d48408c3e2febcc6917c88c8dcc1697cbf4329b1f340d94c35763", + "src/CelerisLab/body/coupling/soa_packer.py": "8ec26fd55ef74801bbd5ab6bd3d0a7dabc750cbd498aaac2ca4c61a0108ad3c9", + "src/CelerisLab/body/ddf_patch.py": "60fcfb7441e73bf290bcff41ca3a83da3e7ddad765d7247464e8f02e3043b4e2", + "src/CelerisLab/body/geometry/__init__.py": "ada88980ab672959aca9aa3a4c4aade3219524b2ab68110cd2a2f41e3e0ca90a", + "src/CelerisLab/body/geometry/base.py": "cceca14361ca2f34d34a3e43730e1a79b8ca81be00f0dd62e884db32b479419f", + "src/CelerisLab/body/geometry/circle.py": "7605ca7c21103f1d801ac73ab8dd18dffbdac425b2dee7b8858b283628a9e436", + "src/CelerisLab/body/jets.py": "4104c24af4651f4bb4f8d8fd36fb6d88f41c6e4469822f36e22e74c9ede31dac", + "src/CelerisLab/body/manager.py": "01b653d25bd9b4333379c8bfd12383596b686d2029f2e91b1c91b618540ec6b3", + "src/CelerisLab/body/objects.py": "8e63cee8faa9cc6243bcff428be628dfad73a02a9a3b8ece7d78c0a63406a08d", + "src/CelerisLab/body/preprocess/__init__.py": "dfd9d58bc07f626ab23a70053e31b88a6d9165d2e4c76a4e9f48abe674d3d2ec", + "src/CelerisLab/body/preprocess/flag_overlay.py": "831fc9e9736b758847759a085ff133eb49ec91318ace4fda76f85960138ee300", + "src/CelerisLab/body/registry.py": "fbb7327780adbdc6da15ebdb799980daf32df22ee974af0893d7409f177bbb68", + "src/CelerisLab/body/sync_plan.py": "3a15bfe9ebeb91482dbfc68850761ef39c872cc12d2efc795babd0c01e392323", + "src/CelerisLab/common/__init__.py": "d4dbde78bc97fccc9eabac9abae8f0f15c112c10ff8e9b45bd9401d4723df1ef", + "src/CelerisLab/common/_types.py": "aad2ff1a9033d4fd68783ad7123afb47201ddc58785cb6dc0246947037072a5f", + "src/CelerisLab/common/checkpoint.py": "b2f960aecd5ac27a3a7130b809ffad4709c44d522edef0359ab952654625410b", + "src/CelerisLab/common/pathline.py": "a1b6efcc0a076d0f1dc8d49a595779426d7d5626b00072a59bac710ff02ad046", + "src/CelerisLab/common/preprocess.py": "7fc78a08857cf8e98519500b094a1d896af189b6b15658ff9b388ed51bed4515", + "src/CelerisLab/common/render.py": "0e055476257eb2bcd11e733766e52195de02f8fad228f570a3511a7c831bdc29", + "src/CelerisLab/common/streakline/__init__.py": "13ff1cfa416fdd721a08a659e76b5a5a99b8d3201add10a83b7b0bc654bc1eec", + "src/CelerisLab/common/streakline/_config.py": "a9185ef68e9c7b5d5a1dc0b04a28341e2ad91d13c145a7c106636a878cf0a605", + "src/CelerisLab/common/streakline/_integrate.py": "3fbd4368b2c8d0e7fda40dc65aac5df02b085008b4c68acd83cb7ae45d33a13b", + "src/CelerisLab/common/streakline/_render.py": "236cd637414ee318c4d66f9aed32110d8dc42a100d65fde06172f7cd0ba62c65", + "src/CelerisLab/common/streakline/_streakline.py": "1a3e08daa706b5f83cdeb337e9a945a36ddf7583c40af1d0bb9d825ac954df37", + "src/CelerisLab/config.py": "2f1bba7515f5bc08cb3a8d140bcafe7fa0689e01b3b06494697bb2956a71530e", + "src/CelerisLab/configs/CONFIG.md": "41cafdbe525366c08aa9d0416ea026ccf0026ba7123b8a9b65f968a80d928e3a", + "src/CelerisLab/configs/config_body.json": "055f261f0828ca1d4f2bb694bd15b72bbcabe31d552326208f28e37f761adab1", + "src/CelerisLab/configs/config_lbm.json": "17e4311f1f250c6ece59177a8b6ff856b69a016c7c10267e774b030a0a390865", + "src/CelerisLab/configs/config_lbm_flume_mrt_zouhe.json": "8f593321db05a563a42931603a228d61cbcba06dfbe113262d5cd0ad93c0fe79", + "src/CelerisLab/configs/config_lbm_flume_trt_regularized.json": "f4bba8e6a4339fc3c4eb14f6e2f57c4b22993c73703c773d853edfbe34736e56", + "src/CelerisLab/configs/config_lbm_three_cylinder_triangle.json": "eeb8e7149dbe4aa6b3a9562802735aa950750dfbe2d972a0727e788573afa817", + "src/CelerisLab/cuda/__init__.py": "0d01dff9f0399c8baba822aab5ca0122a135f011008ed8a499ebd9f7e5b06aea", + "src/CelerisLab/cuda/compiler_v2.py": "c4cc3ec4445c8d75e6a253dd87963877117a722bc73b9d4811806fc6045d08c1", + "src/CelerisLab/cuda/context.py": "013dd4ec6d01d2fbd11afdfb103eac6a273bd9f798d663d761b851eeb27885e9", + "src/CelerisLab/cuda/obs_layout.py": "5ffb2c3aeeaaaaeb64f2cd8febfbcf4ad9919e5414020ad753cfade0683b21c2", + "src/CelerisLab/lbm/__init__.py": "7f60fb3aaae81501b510d08c2fedff44fcb05657260eb10b6f5cc91e7204306b", + "src/CelerisLab/lbm/block_stepper.py": "00b313daddc6e63d8ab8423c9190ca7e92ad291de20a2f6b80eb53e3f2d85ec7", + "src/CelerisLab/lbm/blocks.py": "1e644c5719993f418185ad4a10b6950a30636fc912befd9eab41ff48b4cf85cd", + "src/CelerisLab/lbm/channel.py": "40d0354e5dc97bcdb7e7b5c1e7f9303a109b7134d8a3af88b8ff4cd75ab5a095", + "src/CelerisLab/lbm/curved_links.py": "b6957fa13d13f864dba4f7364e93a39905d062dbb3ee9b3f5191add81fff0732", + "src/CelerisLab/lbm/descriptors.py": "c9f025dfc14da43b989047b84cc22bd05d5cc92c9ec695c1da68fdfb788e3f2b", + "src/CelerisLab/lbm/equilibrium.py": "2fb2f2bb7ff326d23abce80fc2a4c0907b17740ea3e29fe6ae079c5044783826", + "src/CelerisLab/lbm/field.py": "0e727eacc3706add4d8d15c418e9b631050871f831698d3ac689dd5f2c701b9b", + "src/CelerisLab/lbm/hierarchy.py": "f234975074f6b6d289d573ff6ea6832feae0e27cb04f80b37fe81792b13a12e7", + "src/CelerisLab/lbm/initializers.py": "dacf9586963e862d6d0a98ef1a066036be6f91e9d423720661d8ef2c1fb2df6a", + "src/CelerisLab/lbm/kernels/blocks.cu": "2fa894b20a29f006911ac3e3aad0793cc4aaf9ccd7a67d01c6320a0836b1d8ef", + "src/CelerisLab/lbm/kernels/boundary/bounce_back.cuh": "aba3748c564ed91b161d268ef8b737b7c7be96a823063d201f3da684dd845233", + "src/CelerisLab/lbm/kernels/boundary/curved_boundary.cuh": "aeb16bfd483514fb03e15a2fed437d673c3b8b2fe68ab83edf4244ef593878ea", + "src/CelerisLab/lbm/kernels/boundary/esopull_curved_helpers.cuh": "b9b474bc4edaff05681c5443235472d04072d0d775fc0eb9c5967faef52b0258", + "src/CelerisLab/lbm/kernels/boundary/ibm_kernels.cuh": "e6909b547dc8d64cdd8a76a90660243a8f5fee535caa161b52b4dd8b7c29934b", + "src/CelerisLab/lbm/kernels/boundary/inlet/channel_stabilized.cuh": "efe077afcbb6b731256a987c2fd79a1dbc470c902f87070d60421ea6cb13d4e2", + "src/CelerisLab/lbm/kernels/boundary/inlet/common.cuh": "59b38a89439878e00de7de16b9c7b03feff443fcc5a421593f7d2536dcbb7ab8", + "src/CelerisLab/lbm/kernels/boundary/inlet/equilibrium.cuh": "f0964c811b2c41a5aaa1c6d99c490b06e850c634eb8749f1f05ef61f93c2de1d", + "src/CelerisLab/lbm/kernels/boundary/inlet/regularized.cuh": "943d4a571818bacb89c70bccfaac202691121e3e385803ce6309123a0a03ddaf", + "src/CelerisLab/lbm/kernels/boundary/inlet/zou_he_local.cuh": "e73e66b853b1e4e0e30d8f4185d832aa414ce670a1813ce2b5459f2887f71f89", + "src/CelerisLab/lbm/kernels/boundary/inlet_outlet.cuh": "de8b213a6da42c2e5e9f8966199fc66ea4541b85144a90f3f5dbad3c6ab63b97", + "src/CelerisLab/lbm/kernels/boundary/outlet/pressure_neq.cuh": "c8b2adc66d12fe602076ddc9693c5c208711fcaddb58aa9a31401a269f51c4c4", + "src/CelerisLab/lbm/kernels/boundary/planar_velocity.cuh": "501b2759d0e2e4280cb6fdd127d66a50fa89f6de48fc2cae9a7eafb11ae90bed", + "src/CelerisLab/lbm/kernels/config.h": "7d1124125e0b354558297934460e64c760fb68cdd7bf6d01a1238bb2eaab8c37", + "src/CelerisLab/lbm/kernels/config/config_grid.h": "1db3e030ebdc116cd7f02362d1fd077daac55e8f0b4329f6285948c224d3ca5d", + "src/CelerisLab/lbm/kernels/config/config_method.h": "4374e04a130f90f97d17cc20c40d2f9ea0ed0dd8603817d07f215d02266e247c", + "src/CelerisLab/lbm/kernels/config/config_objects.h": "db793af052c756561a190a3ff2cb93ee5fe28462ee4f97c6af80882b4dc8b072", + "src/CelerisLab/lbm/kernels/config/config_obs.h": "c11e2249359145b5c4cbaaf0487506fa77c1f4b82c7c169ddc574aa0babcd727", + "src/CelerisLab/lbm/kernels/config/config_physics.h": "8ac0990b168e1058588676cc05c80df8634cfd671dbc92a89246c3c372d83a4e", + "src/CelerisLab/lbm/kernels/core/descriptors.cuh": "8440bc14857da9007982650f828a23608eec9f7cc4bf772e8c8dc1239cb45356", + "src/CelerisLab/lbm/kernels/core/flags.cuh": "0025705f24710330d9d9195f713879bfb650a85a3449baef0e196b831af104ab", + "src/CelerisLab/lbm/kernels/core/layout.cuh": "1fbd820021c89ac3efcde24145996cb0147769e607e498e61f308ed3e0b13706", + "src/CelerisLab/lbm/kernels/core/obs.cuh": "b742b77c9f61e1f020fe9922c3838ae9d855e17b381154958b58f10a22938922", + "src/CelerisLab/lbm/kernels/core/params.cuh": "6e18bf730d936d940e3d85452f0714a2eaae31bf41002f9fd520ec5b372878dd", + "src/CelerisLab/lbm/kernels/core/precision.cuh": "849df14b4cce50af52399e4b8f3e997196f3ae5f1163f68e28df3e6b2507a8c4", + "src/CelerisLab/lbm/kernels/kernel_v2.cu": "c7bd3fb705b77261ba2441c52a24a7e6a7865c165cba088a1b75201c4d29c408", + "src/CelerisLab/lbm/kernels/operators/collision_mrt.cuh": "aee444eb60c47a3fd518dca975d1af05425a0818a0ec6c3fb3f6150f60c2cb44", + "src/CelerisLab/lbm/kernels/operators/collision_srt.cuh": "6b7ff842f392c8f853d2d1a97cc69bfe50f21283f73e659404da07cf6c95f5e1", + "src/CelerisLab/lbm/kernels/operators/collision_trt.cuh": "9725b9840b9ad8b4ec7be61ff5d06d3151e86d1a87585c13f2fbf934d0d2c5f6", + "src/CelerisLab/lbm/kernels/operators/forcing_guo.cuh": "969a430c1af03f32e3bd0df95d805e64f56334717c44dd4ba208ba503062a24b", + "src/CelerisLab/lbm/kernels/operators/helpers.cuh": "0b3a4d95d7d19f5fec09e5423b8f4cc99f8a226b541967425967330da5a051ec", + "src/CelerisLab/lbm/kernels/operators/macro.cuh": "2be20eb2b7d7f5522daaad7f4b57b1ff8cdf883efe4a67846a96783b2fe846a3", + "src/CelerisLab/lbm/kernels/operators/turbulence_smag.cuh": "ff7f000297ee0817e255ad9fcb6a13b647e3ddd5b206518d35f38d2895a9f501", + "src/CelerisLab/lbm/kernels/refinement.cu": "b45aad9c022cecb64f7e968ec3cae2d8385a3cc430ee409700c404a3f4dd8cf8", + "src/CelerisLab/lbm/kernels/step/aux_kernels.cu": "578a5b20a157241ee678547059abb1372dd5a589ad06bb2c15b987eb01ca1013", + "src/CelerisLab/lbm/kernels/step/block_channel.cu": "ee8a31398b8e5233d977c816e6b8e060c1eb2ec71320e594226dbca55593c0d0", + "src/CelerisLab/lbm/kernels/step/curved_double.cu": "bfa7a1df1e939020314d49755045f7a85fe4a19cc1c68dbe62653c66c5e7c2b3", + "src/CelerisLab/lbm/kernels/step/esopull_macro.cu": "c73116020ca217ede1e4de27c8269c31f9d91e33433ac8252a21e9b14187c50f", + "src/CelerisLab/lbm/kernels/step/esopull_sync.cu": "c0ff3e72900c3ab89a05dd03684e96d3b740cf1672d565727fc2af165933d087", + "src/CelerisLab/lbm/kernels/step/init_flow.cu": "305be3bb12fc0d039acec1701baabcf0e8a35e683c49c34f2af8ca9867d2b29c", + "src/CelerisLab/lbm/kernels/step/one_step_double.cu": "f9f8242b38e8e0cf8425b75e2b0215bc15363c62eaa0fb43c761b8e0931bfdc9", + "src/CelerisLab/lbm/kernels/step/one_step_esopull.cu": "aa223b2744ae6c9125673df311a96ce588032c1ba58bebb11fd33ecb990f8ed4", + "src/CelerisLab/lbm/kernels/streaming/esopull_semantic_helpers.cuh": "dd5a9091628b079efd97e53fd49e52d09dcee88529235db77a5648e02937af6e", + "src/CelerisLab/lbm/kernels/streaming/esopull_single_buffer.cuh": "77ed4ab9a0ebc5ff62a91a7c39066ef395391ae36e9040a69fb2bd8b1626dc79", + "src/CelerisLab/lbm/kernels/streaming/pull_double_buffer.cuh": "b65d741ed78b7b62f99531ba0c0ce1936663d8dc9640e7d7d16c707f1baf6549", + "src/CelerisLab/lbm/population_storage.py": "a6a86c29c52c892edfe4802fb773c387212e3f4cc86708d80e154986ddf88044", + "src/CelerisLab/lbm/raw_exchange.py": "9031a196006a0f74dee4028e6f3bb673bc19ef7959f9f04dde9e01f93293aeb3", + "src/CelerisLab/lbm/refinement.py": "100d5e46ac894f6008b61364cdad5d37c2ca7ed949d8d71405572d6366526417", + "src/CelerisLab/lbm/refinement_stepper.py": "b6b835cf97f5aa045045bb4a47cfde34b89929d3872be410cb155727dfca2d33", + "src/CelerisLab/lbm/refinement_tables.py": "6eea593aaf791a2c644f1c74df0a41f6f5e62a2fce9feae76e19cf84b615607a", + "src/CelerisLab/lbm/stepper.py": "0dcb7cd7ae87e33e3dc67a38b6aa43042fbe673e37d9ad165342ee1d695933ce", + "src/CelerisLab/lbm/synchronized_remap.py": "7400fc4beaffd4649d6a9c524d38ce5fd6fa7eeb32eb5332e4030a4b6648f197", + "src/CelerisLab/simulation.py": "0a03904dc6de88667d7af699005a289adf8b09f0976cb0d099b041f4830a8f4c" + }, + "candidate_header_sha256": "a3db46c2e7d27530b2d1436e65cdbed0b72cc7e047bfbeef2a640877271b51aa" +} diff --git a/tests/validation/run_uniform_les_flow.py b/tests/validation/run_uniform_les_flow.py index 0279a06..b037018 100644 --- a/tests/validation/run_uniform_les_flow.py +++ b/tests/validation/run_uniform_les_flow.py @@ -59,8 +59,15 @@ def decode_macros(data, w, cx, cy): return profile, *metrics -def source_guard(root, variant): - manifest = json.loads((HERE / "data/les_flow/source_pins.json").read_text()) +EXPERIMENTS = { + "historical": "source_pins.json", + "dev8403956": "source_pins_dev8403956.json", +} + + +def source_guard(root, variant, experiment="historical"): + manifest_path = HERE / "data/les_flow" / EXPERIMENTS[experiment] + manifest = json.loads(manifest_path.read_text()) for name, sha in manifest["sources"].items(): expected = ( manifest["candidate_header_sha256"] @@ -69,7 +76,7 @@ def source_guard(root, variant): ) if hashlib.sha256((root / name).read_bytes()).hexdigest() != expected: raise RuntimeError("Source pin mismatch: " + name) - return dict( + identity = dict( base_commit=manifest["base_commit"], production_origin_commit=manifest["production_origin_commit"], source_experiment=manifest["source_experiment"], @@ -80,12 +87,22 @@ def source_guard(root, variant): header_sha256=hashlib.sha256((root / HEADER).read_bytes()).hexdigest(), variant=variant, ) + if experiment != "historical": + identity.update( + experiment=experiment, + source_manifest_sha256=hashlib.sha256(manifest_path.read_bytes()).hexdigest(), + ) + return identity def main(): ap = argparse.ArgumentParser(description=__doc__) ap.add_argument("--root", type=Path, required=True) ap.add_argument("--variant", choices=["baseline", "corrected"], required=True) + ap.add_argument( + "--experiment", choices=tuple(EXPERIMENTS), default="historical", + help="Explicit source contract; historical pins remain the default", + ) ap.add_argument("--output", type=Path, required=True) ap.add_argument( "--device", type=int, default=0, help="Logical device in CUDA_VISIBLE_DEVICES" @@ -100,7 +117,7 @@ def main(): ap.error("Output exists; choose a new evidence path") args.output.parent.mkdir(parents=True, exist_ok=True) root = args.root.resolve() - pins = source_guard(root, args.variant) + pins = source_guard(root, args.variant, args.experiment) # Refuse installed-package shadowing; import only after explicit GPU opt-in. sys.path.insert(0, str(root / "src")) import CelerisLab