Historical-evidence pins (dfg_re100 / jet_circle_cv / refined_dfg manifests) now compare each recorded digest with the file at the recorded commit, read with git, instead of with today's working tree. Missing history (no git, shallow clone, installed copy) raises HistoryUnavailable and the test is reported as skipped with that reason, never passed. Receipt/plan logic tests take the selected manifest directly, so their assertions run again; the pinned modes' mismatch rejections stay as negative controls. V1 (run_jet_circle_cv) keeps its pinned mode and gains --current-checkout: it records HEAD, the src tree and per-file hashes labelled as current checkout, never as the frozen qualification, and requires a clean tree. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
108 lines
3.5 KiB
Python
108 lines
3.5 KiB
Python
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"]
|