import sys, json, hashlib from pathlib import Path import numpy as np import pytest ROOT = Path(__file__).resolve().parents[2] sys.path.insert(0, str(ROOT / "tests/validation")) import re100_helpers as h import periodic as p @pytest.mark.parametrize("n", [20, 40, 80]) def test_units(n): x = h.parameters(n, 0.01) assert x["viscosity"] * x["dx"] ** 2 / x["dt"] == pytest.approx(0.001) assert x["mean_speed"] * x["dx"] / x["dt"] == pytest.approx(1.0) assert x["mean_speed"] * n / x["viscosity"] == pytest.approx(100) assert x["peak_mach"] == pytest.approx(0.015 * np.sqrt(3)) assert (x["top"] - x["bottom"]) * x["dx"] == pytest.approx(0.41) assert (x["nx"] - 1) * x["dx"] == pytest.approx(2.2) assert (np.array(x["center"]) - [0, 0.5]) * x["dx"] == pytest.approx([0.2, 0.2]) cfg, _, _ = h.make_config(n, 0.01, "MRT", "neq_extrap", "zou_he_local") assert cfg["method"]["inlet"]["scheme"] == "zou_he_local" def test_pressure_units(): x = h.parameters(40, 0.01) ny, nx = x["ny"], x["nx"] xx = np.arange(nx)[None, :] rho = np.broadcast_to(1 + xx * 1e-6, (ny, nx)) fluid = np.ones_like(rho, dtype=bool) assert h.pressure_probes(rho, fluid, x)["dp"] == pytest.approx( -40e-6 / 0.01**2 / 3, abs=1e-10 ) def rows(amplitude=1.0, drift=0.0): out = [] for t in np.arange(240, 300.001, 0.02): out.append( dict( tstar=t, tstar_force=t, cl=amplitude * np.sin(2 * np.pi * t / 3.3), cd=3.2 + 0.03 * np.cos(4 * np.pi * t / 3.3) + drift * t, dp=2.46 + 0.04 * np.sin(4 * np.pi * t / 3.3), mass=1.0, flux_in=1.0, flux_out=1.0, ) ) return out def test_periodic_synthetic(): a = p.assess(rows()) assert a["periodic"] assert len(a["cycles"]) > 5 assert a["cycles"][-1]["st"] == pytest.approx(1 / 3.3, rel=1e-5) assert not p.assess(rows(amplitude=0.01))["periodic"] assert not p.assess(rows(drift=0.01))["periodic"] assert not p.assess(rows()[:100])["periodic"] def test_production_unmodified(): m = json.loads( (ROOT / "tests/validation/data/dfg_re100/source_manifest.json").read_text() ) assert m["commit"] == h.PIN from source_provenance import HistoryUnavailable, historical_sha256 try: # the manifest certifies its own commit, not today's working tree actual = historical_sha256(ROOT, m["commit"], m["production_sha256"]) except HistoryUnavailable as error: pytest.skip(f"historical integrity NOT checked: {error}") assert actual == m["production_sha256"] def test_reference(): r = json.loads( (ROOT / "tests/validation/data/dfg_re100/reference_cycle.json").read_text() ) assert 0.301 < r["st"] < 0.303 assert 3.22 < r["cd_max"] < 3.23 assert 2.5 < r["dp_max"] < 2.51 assert p.compare(r, r)["reference_agreement"] def test_stale_cycles_rejected(): r = rows() level = next(x["cl"] for x in r if x["tstar"] >= 284.5) for x in r: if x["tstar"] >= 284.5: x["cl"] = level assert not p.assess(r)["periodic"] def test_min_time_is_used(monkeypatch): monkeypatch.setitem(p.GATES, "min_tstar", 290.0) assert not p.assess(rows())["periodic"] def test_nonconsecutive_cycles_rejected(monkeypatch): cycles = p.extract(rows()) bad = cycles[-6:] bad.pop(2) monkeypatch.setattr(p, "extract", lambda *a, **k: bad) assert not p.assess(rows())["periodic"]