Manifests and receipts unchanged. Each of the 8 group-A identity tests now extracts its manifest's recorded commit and verifies hashes there instead of against the current checkout. Tamper assertions and runner source refusals are kept; missing recorded history is an explicit failure, not a skip or repin.
108 lines
3.4 KiB
Python
108 lines
3.4 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"))
|
|
sys.path.insert(0, str(ROOT / "tests/unit"))
|
|
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(tmp_path):
|
|
from historical_tree import historical_tree
|
|
|
|
m = json.loads(
|
|
(ROOT / "tests/validation/data/dfg_re100/source_manifest.json").read_text()
|
|
)
|
|
assert m["commit"] == h.PIN
|
|
origin = historical_tree(m["commit"], tmp_path)
|
|
for name, digest in m["production_sha256"].items():
|
|
assert hashlib.sha256((origin / name).read_bytes()).hexdigest() == digest, name
|
|
|
|
|
|
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"]
|