test: add oblique (2,1) shear-wave diagnostic with composite modal fit

Test-only. New shear_wave.py (exact field, equilibrium initializer reusing
frozen oracle constants/geometry, area-weighted composite metrics with a
2x2 Gram-matrix modal fit, late-time rate fit, preregistered assess with
max(2*control, floor) budgets). run_case.py gains --flow (default
taylor_green, unchanged receipt). oracle.py byte-identical (pinned by test).
CPU manufactured checks in test_shear_wave.py; docs record caveats.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ClaudeandClaude Opus 5.5 committed 2026-10-19 00:16:21 +00:00
1 parent eec4677db1
commit ffff742b69
4 files changed
+550 -3

No files matched your search

+22 -3
View File
@@ -20,6 +20,16 @@ REFERENCE_HARNESS = "11901a7f77557a901e017a171eeae1758d584fa5"
INITIALIZATION = "physical FP64 equilibrium G at actual cell centers; rho=1+3p"
def flow_functions(flow="taylor_green"):
"""(initial, metrics, limits, initialization); the default is the frozen TG oracle."""
if flow == "taylor_green":
return initial, metrics, LIMITS, INITIALIZATION
if flow == "shear_wave":
import shear_wave
return shear_wave.initial, shear_wave.metrics, shear_wave.LIMITS, shear_wave.INITIALIZATION
raise ValueError(f"unknown flow {flow!r}")
def sample_steps(n, early_pressure=False):
"""Root synchronization steps; early ladder matches the 707afe6 diagnostic."""
main = [round(tstar * n / U) for tstar in TIMES]
@@ -151,7 +161,11 @@ def main():
p.add_argument("--explosion", choices=["constant", "projected_linear"], default="constant")
p.add_argument("--early-pressure", action="store_true",
help="also save 65 scalar pressure samples at t*=k*.001875, k=0..64")
p.add_argument("--flow", choices=["taylor_green", "shear_wave"], default="taylor_green")
args = p.parse_args()
if args.flow == "shear_wave" and (args.re != 100 or args.depth == 3 or args.early_pressure):
p.error("shear_wave requires --re 100, depth 1 or 2 and no --early-pressure")
initial, metrics, limits, initialization = flow_functions(args.flow)
if args.output.exists():
p.error("Output already exists; choose a new path to retain prior evidence")
if args.explosion != "constant" and args.depth != 2:
@@ -197,7 +211,7 @@ def main():
mach=U * np.sqrt(3),
viscosity=cfg.viscosity,
tau_by_level=[0.5 + 3 * c.viscosity for c in h.configs],
initialization=INITIALIZATION,
initialization=initialization,
numerics=dict(lattice_model=cfg.lattice_model, collision=cfg.collision,
store_precision=cfg.store_precision, ddf_shifting=cfg.ddf_shifting,
streaming=cfg.streaming, les_enabled=cfg.les_enabled,
@@ -205,13 +219,18 @@ def main():
sample_phase="postcollision G at root synchronization"),
cuda_visible_devices=selected,
device_id=0,
limits=LIMITS,
limits=limits,
workload=workload(masks, round(TIMES[-1] * args.n / U)),
rows=[],
early_pressure=dict(enabled=args.early_pressure, rows=[],
sampled_max_pressure_l2=None),
completed=False,
)
if args.flow != "taylor_green":
import shear_wave
report.update(flow=args.flow, mode=list(shear_wave.MODE),
polarization=shear_wave.E.tolist(), angle_deg=shear_wave.ANGLE_DEG,
phase=shear_wave.PHI, eps=shear_wave.eps(args.n))
args.output.parent.mkdir(parents=True, exist_ok=True)
def save():
temporary = args.output.with_suffix(args.output.suffix + ".tmp")
@@ -301,7 +320,7 @@ def main():
completed=True,
seconds=time.monotonic() - started,
screening_pass=all(
row[key] <= limit for row in report["rows"] for key, limit in LIMITS.items()
row[key] <= limit for row in report["rows"] for key, limit in limits.items()
),
)
save()
+197
View File
@@ -0,0 +1,197 @@
"""CPU-only oblique shear-wave reference and composite-leaf diagnostics.
Convention: integer mode MODE=(2,1) on the L x L periodic root domain, so the
wavevector k = (2*pi/L)*(2,1) points 26.565 deg from +x (mode (1,2) would be
63.435 deg). Velocity is polarized along E = (-1,2)/sqrt(5), perpendicular to k.
The frozen Taylor-Green oracle is reused for constants and geometry only.
"""
import math
import numpy as np
from oracle import CX, CY, W, U, TIMES, centers
MODE = (2, 1)
PHI = 0.0
E = np.array([-MODE[1], MODE[0]], dtype=float) / math.hypot(*MODE)
ANGLE_DEG = math.degrees(math.atan2(MODE[1], MODE[0]))
RE = 100 # Re = U L / nu, the same nu as Taylor-Green Re100 at equal N
FIT_FROM_TSTAR = 0.12 # late-time samples only; reduces startup sensitivity
INITIALIZATION = (
"physical FP64 equilibrium G at actual cell centers; rho=1 (exact p const)"
)
# Per-row screens. phase_drift_abs, residual_l2 and the run-level rate screen
# use eps(n) and are preregistered diagnostic choices, not error bounds.
LIMITS = dict(velocity_l2=0.05, pressure_rel=0.02, mass_relative=1e-5)
FLOORS = dict(rate_error=0.002, residual_l2=0.002, pressure_rel=0.02)
BASELINE_FACTOR = 2.0
def wavevector(length):
return 2 * np.pi / length * np.array(MODE, dtype=float)
def decay_rate(length, viscosity):
"""Exact amplitude decay rate per root step: nu |k|^2."""
k = wavevector(length)
return viscosity * float(k @ k)
def eps(n):
"""(|k| h_root)^2 with h_root = 1: dimensionless, coefficient one."""
k = wavevector(n)
return float(k @ k)
def exact(x, y, t, length, viscosity, amplitude=U):
k = wavevector(length)
a = amplitude * np.exp(-decay_rate(length, viscosity) * t)
s = np.sin(k[0] * x + k[1] * y + PHI)
p = np.zeros_like(s)
return a * E[0] * s, a * E[1] * s, p # kinematic pressure, constant
def equilibrium(rho, ux, uy):
# Same second-order D2Q9 equilibrium as oracle.initial.
cu = CX[:, None, None] * ux + CY[:, None, None] * uy
return (
W[:, None, None]
* rho
* (1 + 3 * cu + 4.5 * cu * cu - 1.5 * (ux * ux + uy * uy))
)
def initial(shape, level, length, viscosity):
x, y = centers(shape, level)
ux, uy, p = exact(x, y, 0.0, length, viscosity)
return equilibrium(1 + 3 * p, ux, uy)
def _leaves(arrays, masks):
accum = []
for level, (g, mask) in enumerate(zip(arrays, masks)):
if g.shape != (9, *mask.shape):
raise ValueError("population/mask shape mismatch")
values = g[:, mask].astype(float)
if not np.isfinite(values).all():
raise FloatingPointError("nonfinite leaf populations")
rho = values.sum(axis=0)
if (rho <= 0).any():
raise FloatingPointError("nonpositive leaf density")
x, y = centers(mask.shape, level)
area = np.full(rho.size, 0.25**level)
lev = np.full(rho.size, level)
accum.append((rho, CX @ values / rho, CY @ values / rho, x[mask], y[mask], area, lev))
return [np.concatenate(v) for v in zip(*accum)]
def metrics(arrays, masks, t, length, viscosity, initial_mass=None):
rho, ux, uy, x, y, w, lev = _leaves(arrays, masks)
volume = w.sum()
if not np.isclose(volume, length**2, rtol=0, atol=1e-10):
raise ValueError("leaf partition does not cover domain once")
k = wavevector(length)
theta = k[0] * x + k[1] * y + PHI
s, c = np.sin(theta), np.cos(theta)
v = ux * E[0] + uy * E[1]
# Weighted least squares on the actual composite quadrature (no
# continuous-orthogonality assumption).
gram = np.array([[w @ (s * s), w @ (s * c)], [w @ (s * c), w @ (c * c)]])
a_hat, b_hat = np.linalg.solve(gram, [w @ (v * s), w @ (v * c)])
ex, ey, _ = exact(x, y, t, length, viscosity)
exact_norm = w @ (ex * ex + ey * ey)
fit = a_hat * s + b_hat * c
longitudinal = (ux * k[0] + uy * k[1]) / math.sqrt(k @ k)
amplitude = U * math.exp(-decay_rate(length, viscosity) * t)
scale = amplitude * amplitude / 4 # fixed pressure scale, see docs
p = (rho - 1) / 3
p = p - w @ p / volume # one global gauge
mass = float(w @ rho)
return dict(
tstar=t * U / length,
mass=mass,
mass_relative=(
0.0 if initial_mass is None else abs(mass - initial_mass) / initial_mass
),
a_hat=float(a_hat),
b_hat=float(b_hat),
amplitude_exact=amplitude,
amplitude_ratio=float(a_hat / amplitude),
phase_drift=float(math.atan2(b_hat, a_hat)),
phase_drift_abs=abs(float(math.atan2(b_hat, a_hat))),
velocity_l2=float(
np.sqrt(w @ ((ux - ex) ** 2 + (uy - ey) ** 2) / exact_norm)
),
residual_l2=float(
np.sqrt(w @ ((ux - fit * E[0]) ** 2 + (uy - fit * E[1]) ** 2) / exact_norm)
),
longitudinal_l2=float(np.sqrt(w @ (longitudinal**2) / exact_norm)),
pressure_rel=float(np.sqrt(w @ (p * p) / volume) / scale),
pressure_max_rel=float(np.max(np.abs(p)) / scale),
pressure_rel_by_level=[
float(np.sqrt(w[lev == i] @ (p[lev == i] ** 2) / w[lev == i].sum()) / scale)
for i in range(len(masks))
],
rho_min=float(rho.min()),
rho_max=float(rho.max()),
max_speed=float(np.max(np.hypot(ux, uy))),
leaf_cells=int(len(rho)),
)
def fit_rate(rows, length, viscosity):
"""Least-squares decay rate of ln(a_hat) over late samples."""
late = [r for r in rows if r["tstar"] >= FIT_FROM_TSTAR - 1e-12]
if len(late) < 2 or any(r["a_hat"] <= 0 for r in late):
raise ValueError("need >= 2 late samples with positive amplitude")
t = np.array([r["step"] for r in late], dtype=float)
slope = np.polyfit(t, np.log([r["a_hat"] for r in late]), 1)[0]
exact_rate = decay_rate(length, viscosity)
return dict(rate=float(-slope), exact_rate=exact_rate,
rate_error=abs(-slope / exact_rate - 1))
def summarize(record):
rows = record["rows"]
n = record["n"]
out = fit_rate(rows, n, record["viscosity"])
out.update(
eps=eps(n),
complete=record["completed"] and len(rows) == len(TIMES),
residual_l2=max(r["residual_l2"] for r in rows),
phase_drift_abs=max(r["phase_drift_abs"] for r in rows),
pressure_rel=max(r["pressure_rel"] for r in rows),
velocity_l2=max(r["velocity_l2"] for r in rows),
mass_relative=max(r["mass_relative"] for r in rows),
)
out["absolute_pass"] = out["complete"] and all(
out[key] <= out["eps"] for key in ("rate_error", "phase_drift_abs", "residual_l2")
) and all(out[key] <= limit for key, limit in LIMITS.items())
return out
def assess(base_control, fine_control, refined):
"""Uniform root-spacing and finest-spacing controls versus refined runs."""
n = base_control["n"]
if (base_control["depth"], fine_control["depth"], fine_control["n"]) != (1, 1, 2 * n):
raise ValueError("controls must be uniform N and uniform 2N")
if any(r["depth"] != 2 or r["n"] != n for r in refined):
raise ValueError("refined runs must be depth 2 at root N")
base, fine = summarize(base_control), summarize(fine_control)
report = dict(base_control=base, fine_control=fine, refined={}, status="PASS")
if not (base["absolute_pass"] and fine["absolute_pass"]):
report["status"] = "INVALID_CONTROL"
for r in refined:
s = summarize(r)
s["budget"] = {
key: max(BASELINE_FACTOR * base[key], floor) for key, floor in FLOORS.items()
}
s["budget_pass"] = all(s[key] <= s["budget"][key] for key in FLOORS)
s["fine_control_ratio"] = {
key: (s[key] / fine[key] if fine[key] > 0 else None) for key in FLOORS
}
s["pass"] = s["absolute_pass"] and s["budget_pass"]
report["refined"][r["explosion"]] = s
if report["status"] == "PASS" and not s["pass"]:
report["status"] = "FAIL"
return report
@@ -0,0 +1,247 @@
"""Standalone CPU manufactured checks for the oblique shear wave.
Run from this directory with PYTHONPATH=<checkout>/src (same as test_oracle.py).
"""
import hashlib
import math
import unittest
from pathlib import Path
import numpy as np
import oracle
import run_case
import shear_wave as sw
from oracle import CX, CY, U, TIMES, centers, rectangles
N = 32
NU = U * N / sw.RE
HERE = Path(__file__).resolve().parent
ORACLE_SHA256_EEC4677 = "83e4d7425d45538278e9fb181af450a36358bd9a5ed6be57ff611dd9ae5eb35b"
def composite_masks(n):
"""Level-wide union masks for rectangles(n, 2): root complement + fine box."""
(x0, y0, x1, y1), = rectangles(n, 2)[0]
root = np.ones((n, n), dtype=bool)
root[y0:y1, x0:x1] = False
fine = np.zeros((2 * n, 2 * n), dtype=bool)
fine[2 * y0:2 * y1, 2 * x0:2 * x1] = True
return [root, fine]
def populations(masks, field, t=0.0):
"""Equilibrium G of an arbitrary (ux, uy, rho) field on every level."""
out = []
for level, m in enumerate(masks):
x, y = centers(m.shape, level)
ux, uy, rho = field(x, y, t)
out.append(sw.equilibrium(rho, ux, uy))
return out
def exact_field(phase=0.0, rate_factor=1.0, mode=sw.MODE, longitudinal=0.0, ripple=0.0,
offset=0.0):
k = 2 * np.pi / N * np.array(mode, dtype=float)
e = np.array([-mode[1], mode[0]], dtype=float) / math.hypot(*mode)
rate = rate_factor * sw.decay_rate(N, NU)
kh = k / np.linalg.norm(k)
def field(x, y, t):
theta = k[0] * x + k[1] * y + phase
a = U * np.exp(-rate * t)
ux = a * e[0] * np.sin(theta) + longitudinal * kh[0] * np.sin(theta)
uy = a * e[1] * np.sin(theta) + longitudinal * kh[1] * np.sin(theta)
rho = 1 + offset + ripple * np.cos(theta)
return ux, uy, rho
return field
class ConventionTests(unittest.TestCase):
def test_mode_angle_and_polarization(self):
k = sw.wavevector(N)
self.assertAlmostEqual(sw.ANGLE_DEG, 26.565051177077994)
self.assertAlmostEqual(math.degrees(math.atan2(k[1], k[0])), sw.ANGLE_DEG)
self.assertAlmostEqual(float(sw.E @ k), 0.0)
self.assertAlmostEqual(float(sw.E @ sw.E), 1.0)
self.assertAlmostEqual(sw.eps(128), 5 * (2 * math.pi / 128) ** 2)
def test_navier_stokes_and_vanishing_advection(self):
# Independent central differences: du/dt = nu lap u, (u.grad)u = 0, div u = 0.
x = np.array([2.7, 8.1, 13.3])
y = np.array([3.2, 7.4, 21.6])
t, h = 10.0, 1e-3
f = lambda x, y, t: np.array(sw.exact(x, y, t, N, NU)[:2])
z = f(x, y, t)
dt = (f(x, y, t + h) - f(x, y, t - h)) / (2 * h)
dx = (f(x + h, y, t) - f(x - h, y, t)) / (2 * h)
dy = (f(x, y + h, t) - f(x, y - h, t)) / (2 * h)
lap = (f(x + h, y, t) + f(x - h, y, t) + f(x, y + h, t) + f(x, y - h, t) - 4 * z) / h**2
np.testing.assert_allclose(dt, NU * lap, atol=1e-9)
np.testing.assert_allclose(z[0] * dx + z[1] * dy, 0.0, atol=1e-12)
np.testing.assert_allclose(dx[0] + dy[1], 0.0, atol=1e-12)
self.assertTrue(np.all(sw.exact(x, y, t, N, NU)[2] == 0))
class InitialTests(unittest.TestCase):
def test_moments_on_each_level(self):
for level, m in enumerate(composite_masks(N)):
g = sw.initial(m.shape, level, N, NU)
x, y = centers(m.shape, level)
ux, uy, _ = sw.exact(x, y, 0.0, N, NU)
rho = g.sum(axis=0)
np.testing.assert_allclose(rho, 1.0, atol=1e-14)
np.testing.assert_allclose(np.tensordot(CX, g, 1) / rho, ux, atol=1e-15)
np.testing.assert_allclose(np.tensordot(CY, g, 1) / rho, uy, atol=1e-15)
def test_equilibrium_matches_frozen_oracle_formula(self):
# Same equilibrium as oracle.initial when fed the TG field.
shape = (8, 8)
x, y = centers(shape, 1)
ux, uy, p = oracle.exact(x, y, 0.0, 4.0, 0.01)
np.testing.assert_array_equal(
sw.equilibrium(1 + 3 * p, ux, uy), oracle.initial(shape, 1, 4.0, 0.01)
)
class MetricTests(unittest.TestCase):
def setUp(self):
self.masks = composite_masks(N)
def sample(self, field, t):
return sw.metrics(populations(self.masks, field, t), self.masks, t, N, NU, None)
def test_exact_field_on_composite_partition(self):
for t in (0.0, 100.0, 512.0):
r = self.sample(exact_field(), t)
self.assertLess(r["velocity_l2"], 1e-12)
self.assertLess(r["residual_l2"], 1e-12)
self.assertLess(r["longitudinal_l2"], 1e-12)
self.assertLess(r["phase_drift_abs"], 1e-12)
self.assertAlmostEqual(r["amplitude_ratio"], 1.0, places=12)
self.assertLess(r["pressure_rel"], 1e-10)
self.assertEqual(len(r["pressure_rel_by_level"]), 2)
self.assertEqual(r["leaf_cells"], int(sum(m.sum() for m in self.masks)))
def test_phase_control(self):
r = self.sample(exact_field(phase=0.01), 0.0)
self.assertAlmostEqual(r["phase_drift"], 0.01, delta=1e-9)
self.assertLess(r["residual_l2"], 1e-12) # a pure phase shift stays modal
def test_rate_control_and_fit(self):
for factor, expected in ((1.0, 0.0), (1.01, 0.01)):
field = exact_field(rate_factor=factor)
rows = []
for tstar in TIMES:
step = round(tstar * N / U)
rows.append(dict(self.sample(field, step), step=step))
fit = sw.fit_rate(rows, N, NU)
self.assertAlmostEqual(fit["rate_error"], expected, delta=1e-9)
def test_fit_ignores_startup_sample(self):
rows = [dict(tstar=0.0, step=0, a_hat=2 * U)]
rows += [dict(tstar=s, step=round(s * N / U),
a_hat=U * math.exp(-sw.decay_rate(N, NU) * round(s * N / U)))
for s in TIMES[1:]]
self.assertLess(sw.fit_rate(rows, N, NU)["rate_error"], 1e-12)
def test_wrong_mode_control(self):
# Swapping (2,1) for (1,2), i.e. 26.6 vs 63.4 deg, is not silently modal.
r = self.sample(exact_field(mode=(1, 2)), 0.0)
self.assertLess(abs(r["amplitude_ratio"]), 0.05)
self.assertGreater(r["residual_l2"], 0.95)
def test_longitudinal_control(self):
r = self.sample(exact_field(longitudinal=1e-3), 0.0)
self.assertAlmostEqual(r["longitudinal_l2"], 1e-3 / U, delta=1e-9)
self.assertLess(r["phase_drift_abs"], 1e-12)
self.assertAlmostEqual(r["amplitude_ratio"], 1.0, places=12)
def test_pressure_gauge_and_ripple(self):
r = self.sample(exact_field(offset=1e-4), 0.0)
self.assertLess(r["pressure_rel"], 1e-10) # one global gauge removes offsets
ripple = 3e-5
r = self.sample(exact_field(ripple=ripple), 0.0)
# p = ripple/3 cos(theta); rms over a whole number of periods = ripple/(3 sqrt 2).
# Composite midpoint quadrature is not exactly periodic: 3% tolerance.
expected = ripple / (3 * math.sqrt(2)) / (U * U / 4)
self.assertAlmostEqual(r["pressure_rel"], expected, delta=0.03 * expected)
def test_volume_weighting_and_double_count(self):
bad = [np.ones_like(m) for m in self.masks]
with self.assertRaises(ValueError):
sw.metrics(populations(bad, exact_field()), bad, 0.0, N, NU)
# Unweighted (count-based) averaging would change the fit; weights matter.
r = self.sample(exact_field(), 0.0)
self.assertEqual(r["leaf_cells"], N * N - self.masks[1].sum() // 4 + self.masks[1].sum())
def record(n, depth, rows, explosion="constant", completed=True):
return dict(n=n, depth=depth, viscosity=U * n / sw.RE, rows=rows, completed=completed,
explosion=explosion)
def rows_for(n, rate_error=0.0, residual=1e-4, pressure=1e-3, phase=0.0):
rate = (1 + rate_error) * sw.decay_rate(n, U * n / sw.RE)
out = []
for tstar in TIMES:
step = round(tstar * n / U)
out.append(dict(tstar=tstar, step=step, a_hat=U * math.exp(-rate * step),
residual_l2=residual, phase_drift_abs=phase, pressure_rel=pressure,
velocity_l2=residual, mass_relative=0.0))
return out
class AssessTests(unittest.TestCase):
def test_budget_form_is_max_not_sum(self):
base = record(128, 1, rows_for(128, rate_error=1e-3, pressure=0.015))
fine = record(256, 1, rows_for(256, rate_error=2e-4))
# rate budget = max(2*1e-3, 0.002) = 0.002; 0.0025 would pass a sum form.
refined = record(128, 2, rows_for(128, rate_error=0.0025))
report = sw.assess(base, fine, [refined])
self.assertEqual(report["status"], "FAIL")
self.assertAlmostEqual(report["refined"]["constant"]["budget"]["rate_error"], 0.002)
# pressure budget = max(2*0.015, 0.02) = 0.03 (control-dominated).
self.assertAlmostEqual(report["refined"]["constant"]["budget"]["pressure_rel"], 0.03)
ok = record(128, 2, rows_for(128, rate_error=0.0015), explosion="projected_linear")
self.assertEqual(sw.assess(base, fine, [ok])["status"], "PASS")
def test_invalid_control_and_wrong_shapes(self):
base = record(128, 1, rows_for(128, residual=0.5)) # > eps(128)
fine = record(256, 1, rows_for(256))
refined = record(128, 2, rows_for(128))
self.assertEqual(sw.assess(base, fine, [refined])["status"], "INVALID_CONTROL")
with self.assertRaises(ValueError):
sw.assess(base, record(128, 1, rows_for(128)), [refined])
with self.assertRaises(ValueError):
sw.assess(record(128, 1, rows_for(128)), fine, [record(256, 2, rows_for(256))])
def test_incomplete_record_fails(self):
base = record(128, 1, rows_for(128))
fine = record(256, 1, rows_for(256))
refined = record(128, 2, rows_for(128), completed=False)
self.assertEqual(sw.assess(base, fine, [refined])["status"], "FAIL")
class DefaultPathTests(unittest.TestCase):
def test_frozen_oracle_is_byte_identical(self):
digest = hashlib.sha256((HERE / "oracle.py").read_bytes()).hexdigest()
self.assertEqual(digest, ORACLE_SHA256_EEC4677)
def test_default_flow_is_frozen_taylor_green(self):
init, met, limits, text = run_case.flow_functions()
self.assertIs(init, oracle.initial)
self.assertIs(met, oracle.metrics)
self.assertIs(limits, oracle.LIMITS)
self.assertEqual(text, run_case.INITIALIZATION)
self.assertEqual(run_case.flow_functions("taylor_green"), (init, met, limits, text))
shear = run_case.flow_functions("shear_wave")
self.assertIs(shear[0], sw.initial)
self.assertIs(shear[1], sw.metrics)
with self.assertRaises(ValueError):
run_case.flow_functions("tg")
if __name__ == "__main__":
unittest.main()