test: shear-wave modal-extinction handling; TG mock verify_source flow arg
- shear_wave.fit_rate: finite late R=0 returns rate_fit_valid=false with null fit quantities (no clipping, no invented rate); own screens and budgets treat it as a failure (CONTROL_SCREEN_FAIL / refined FAIL), other finite metrics retained, JSON-safe. - test_projected_pairs: dry-run/execution mocks expect the explicit 'taylor_green' flow argument now passed to verify_source. - run_case: SHEAR_WAVE_SHA256 repinned; extinction test added. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
f337652683
commit
de5b0cc080
@@ -262,6 +262,11 @@ Run set: uniform N, uniform 2N, and refined depth-2 runs at root N.
|
||||
`controlled_pass` is false, so it is never a controlled-comparison pass;
|
||||
- PASS/FAIL: every refined run's `controlled_pass` (own screens and
|
||||
budgets against valid, passing controls).
|
||||
- Finite modal extinction (late R = 0) is a physical failure, not
|
||||
malformed data. The run gets `rate_fit_valid=false` with null fit
|
||||
quantities, and its other metrics are kept. Nothing is clipped and no
|
||||
rate is invented. The result is CONTROL_SCREEN_FAIL for a control, or
|
||||
FAIL for a refined run.
|
||||
- Screens are never retuned.
|
||||
|
||||
### Limitations
|
||||
|
||||
@@ -18,7 +18,7 @@ SOURCE_SHA256 = "a5dbfd804befce868067d79a23e5fd47dce7f505b3f9b1bb2145fb57cb5f217
|
||||
BUILD_CAPTURE_SHA256 = "3dbaa6547cb0a4cb1541054f3b64ad8b6e18cc70527ac6166e56b944e9bd464a"
|
||||
REFERENCE_HARNESS = "11901a7f77557a901e017a171eeae1758d584fa5"
|
||||
INITIALIZATION = "physical FP64 equilibrium G at actual cell centers; rho=1+3p"
|
||||
SHEAR_WAVE_SHA256 = "4597cf1870c1a69b4525c231825a58cf601b3fe22efd2aecf8fca76601c972e8"
|
||||
SHEAR_WAVE_SHA256 = "81d5c8ee60864372887cddf9239580c82fb6750d956038f1e3ee9de61aa4e877"
|
||||
|
||||
|
||||
def flow_functions(flow="taylor_green"):
|
||||
|
||||
@@ -155,15 +155,21 @@ def fit_rate(rows, length, viscosity):
|
||||
"""
|
||||
late = [r for r in rows if r["tstar"] >= FIT_FROM_TSTAR - 1e-12]
|
||||
amp = np.array([math.hypot(r["a_hat"], r["b_hat"]) for r in late])
|
||||
if len(late) < 3 or not (np.isfinite(amp).all() and (amp > 0).all()):
|
||||
raise ValueError("need >= 3 late samples with finite positive R")
|
||||
if len(late) < 3 or not np.isfinite(amp).all():
|
||||
raise ValueError("need >= 3 late samples with finite R")
|
||||
exact_rate = decay_rate(length, viscosity)
|
||||
to_tstar = length / U # root steps per unit t*
|
||||
if not (amp > 0).all():
|
||||
# Finite modal extinction: a physical failure, not malformed data. No
|
||||
# clipping and no invented rate; fit quantities are unavailable.
|
||||
return dict(rate_fit_valid=False, rate=None, exact_rate=exact_rate,
|
||||
rate_tstar=None, exact_rate_tstar=exact_rate * to_tstar,
|
||||
rate_error=None, interval_rate_errors=None, log_fit_residual_max=None)
|
||||
t = np.array([r["step"] for r in late], dtype=float)
|
||||
(slope, intercept) = np.polyfit(t, np.log(amp), 1)
|
||||
residual = np.log(amp) - (slope * t + intercept)
|
||||
exact_rate = decay_rate(length, viscosity)
|
||||
to_tstar = length / U # root steps per unit t*
|
||||
return dict(
|
||||
rate=float(-slope), exact_rate=exact_rate,
|
||||
rate_fit_valid=True, rate=float(-slope), exact_rate=exact_rate,
|
||||
rate_tstar=float(-slope * to_tstar),
|
||||
exact_rate_tstar=exact_rate * to_tstar, # = 5 (2 pi)^2 / Re
|
||||
rate_error=abs(-slope / exact_rate - 1),
|
||||
@@ -228,10 +234,11 @@ def summarize(record):
|
||||
velocity_l2=max(r["velocity_l2"] for r in rows),
|
||||
mass_relative=max(r["mass_relative"] for r in rows),
|
||||
)
|
||||
if not all(_number(v) for v in (out["rate"], out["rate_error"], *out["interval_rate_errors"],
|
||||
out["log_fit_residual_max"])):
|
||||
if out["rate_fit_valid"] and not all(
|
||||
_number(v) for v in (out["rate"], out["rate_error"], *out["interval_rate_errors"],
|
||||
out["log_fit_residual_max"])):
|
||||
raise ValueError("nonfinite rate fit")
|
||||
out["own_screens_pass"] = out["complete"] and all(
|
||||
out["own_screens_pass"] = out["complete"] and out["rate_fit_valid"] 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
|
||||
@@ -280,12 +287,16 @@ def assess(base_control, fine_control, refined):
|
||||
statuses.append("INVALID_RUN")
|
||||
continue
|
||||
s["valid"] = True
|
||||
# rate_error is None when a modal fit is unavailable; never a pass.
|
||||
s["budget"] = {
|
||||
key: max(BASELINE_FACTOR * base[key], floor) for key, floor in FLOORS.items()
|
||||
key: (None if base[key] is None else 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["budget_pass"] = all(
|
||||
s[key] is not None and s["budget"][key] is not None and s[key] <= s["budget"][key]
|
||||
for key in FLOORS)
|
||||
s["control_ratios"] = {
|
||||
name: {key: (s[key] / c[key] if c[key] > 0 else None)
|
||||
name: {key: (s[key] / c[key] if s[key] is not None and c[key] else None)
|
||||
for key in (*FLOORS, "spurious_pressure_rms")}
|
||||
for name, c in (("root", base), ("finest", fine))
|
||||
}
|
||||
|
||||
@@ -319,7 +319,7 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
patch.object(run_case, "runtime_identity", side_effect=AssertionError("CUDA reached")), \
|
||||
redirect_stdout(stdout):
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION)
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green")
|
||||
self.assertTrue(json.loads(stdout.getvalue())["dry_run"])
|
||||
self.assertFalse(output.exists())
|
||||
|
||||
@@ -412,7 +412,7 @@ class ProjectedPairsTests(unittest.TestCase):
|
||||
else:
|
||||
self.assertEqual(run_case.main(), 0)
|
||||
receipt = json.loads(output.read_text())
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION)
|
||||
verify.assert_called_once_with(REPO, MOCK_REVISION, "taylor_green")
|
||||
self.assertEqual(selectors, ["projected_linear" if early else "constant"] if depth == 2 else [])
|
||||
self.assertEqual(receipt["source"], source())
|
||||
self.assertEqual(receipt["runtime_identity"], RUNTIME)
|
||||
|
||||
@@ -4,6 +4,7 @@ Run from this directory with PYTHONPATH=<checkout>/src (same as test_oracle.py).
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import math
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
@@ -286,6 +287,32 @@ class AssessTests(unittest.TestCase):
|
||||
with self.assertRaises(ValueError):
|
||||
sw.assess(base, fine, [record(128, 2, rows_for(128)), record(128, 2, rows_for(128))])
|
||||
|
||||
def test_modal_extinction_is_a_failure_not_malformed(self):
|
||||
def extinct(rows):
|
||||
for row in rows[1:]:
|
||||
row.update(a_hat=0.0, b_hat=0.0, modal_amplitude=0.0, amplitude_ratio=0.0,
|
||||
amplitude_error=-1.0)
|
||||
return rows
|
||||
base = record(128, 1, rows_for(128))
|
||||
fine = record(256, 1, rows_for(256))
|
||||
refined = record(128, 2, extinct(rows_for(128)))
|
||||
report = sw.assess(base, fine, [refined])
|
||||
self.assertEqual(report["status"], "FAIL")
|
||||
own = report["refined"]["constant"]
|
||||
self.assertTrue(own["valid"])
|
||||
self.assertFalse(own["rate_fit_valid"])
|
||||
self.assertIsNone(own["rate_error"])
|
||||
self.assertIsNone(own["control_ratios"]["root"]["rate_error"])
|
||||
self.assertFalse(own["own_screens_pass"] or own["controlled_pass"] or own["budget_pass"])
|
||||
self.assertEqual(own["residual_l2"], 1e-4) # other finite metrics retained
|
||||
json.loads(json.dumps(report, allow_nan=False))
|
||||
control = sw.assess(record(128, 1, extinct(rows_for(128))), fine,
|
||||
[record(128, 2, rows_for(128))])
|
||||
self.assertEqual(control["status"], "CONTROL_SCREEN_FAIL")
|
||||
self.assertIsNone(control["refined"]["constant"]["budget"]["rate_error"])
|
||||
self.assertFalse(control["refined"]["constant"]["controlled_pass"])
|
||||
json.loads(json.dumps(control, allow_nan=False))
|
||||
|
||||
def test_wrong_shapes(self):
|
||||
base = record(128, 1, rows_for(128))
|
||||
fine = record(256, 1, rows_for(256))
|
||||
|
||||
Reference in New Issue
Block a user