test: apply shear-wave review corrections
Modal magnitude R=hypot(a,b) with amplitude error and separate phase; free-intercept ln R rate fit with per-interval rates, log-fit residual, t* units and finite-positive check; spurious-pressure naming with raw rms; INVALID_CONTROL vs CONTROL_SCREEN_FAIL (refined results kept), mixed-source check, raw ratios to both controls; docs narrow the cubic-term scope, startup and placement limitations. oracle.py still byte-identical. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ffff742b69
commit
10069feadb
3 files changed
+197
-101
No files matched your search
@@ -120,13 +120,15 @@ class MetricTests(unittest.TestCase):
|
||||
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.assertLess(abs(r["amplitude_error"]), 1e-12)
|
||||
self.assertLess(r["spurious_pressure_scaled"], 1e-10)
|
||||
self.assertEqual(len(r["spurious_pressure_scaled_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(abs(r["amplitude_error"]), 1e-12) # R = hypot(a, b) is phase-free
|
||||
self.assertLess(r["residual_l2"], 1e-12) # a pure phase shift stays modal
|
||||
|
||||
def test_rate_control_and_fit(self):
|
||||
@@ -138,16 +140,24 @@ class MetricTests(unittest.TestCase):
|
||||
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)
|
||||
np.testing.assert_allclose(fit["interval_rate_errors"], expected, atol=1e-9)
|
||||
self.assertLess(fit["log_fit_residual_max"], 1e-9)
|
||||
self.assertAlmostEqual(fit["exact_rate_tstar"], 5 * (2 * math.pi) ** 2 / sw.RE)
|
||||
self.assertAlmostEqual(fit["exact_rate_tstar"], 1.97392088, places=8)
|
||||
|
||||
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),
|
||||
rows = [dict(tstar=0.0, step=0, a_hat=2 * U, b_hat=0.0)]
|
||||
rows += [dict(tstar=s, step=round(s * N / U), b_hat=0.0,
|
||||
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)
|
||||
rows[-1]["a_hat"] = float("nan")
|
||||
with self.assertRaises(ValueError):
|
||||
sw.fit_rate(rows, N, NU)
|
||||
|
||||
def test_wrong_mode_control(self):
|
||||
# Swapping (2,1) for (1,2), i.e. 26.6 vs 63.4 deg, is not silently modal.
|
||||
# Composite quadrature need not give an exactly zero projection.
|
||||
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)
|
||||
@@ -155,18 +165,23 @@ class MetricTests(unittest.TestCase):
|
||||
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)
|
||||
# The full-vector residual and velocity error see it too.
|
||||
self.assertAlmostEqual(r["residual_l2"], 1e-3 / U, delta=1e-9)
|
||||
self.assertAlmostEqual(r["velocity_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
|
||||
self.assertLess(r["spurious_pressure_scaled"], 1e-10) # one global gauge
|
||||
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)
|
||||
self.assertAlmostEqual(r["spurious_pressure_scaled"], expected, delta=0.03 * expected)
|
||||
self.assertAlmostEqual(r["spurious_pressure_rms"], expected * U * U / 4,
|
||||
delta=0.03 * expected * U * U / 4)
|
||||
|
||||
def test_volume_weighting_and_double_count(self):
|
||||
bad = [np.ones_like(m) for m in self.masks]
|
||||
@@ -177,9 +192,9 @@ class MetricTests(unittest.TestCase):
|
||||
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):
|
||||
def record(n, depth, rows, explosion="constant", completed=True, source="s"):
|
||||
return dict(n=n, depth=depth, viscosity=U * n / sw.RE, rows=rows, completed=completed,
|
||||
explosion=explosion)
|
||||
explosion=explosion, source=source)
|
||||
|
||||
|
||||
def rows_for(n, rate_error=0.0, residual=1e-4, pressure=1e-3, phase=0.0):
|
||||
@@ -187,8 +202,10 @@ def rows_for(n, rate_error=0.0, residual=1e-4, pressure=1e-3, phase=0.0):
|
||||
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,
|
||||
out.append(dict(tstar=tstar, step=step, a_hat=U * math.exp(-rate * step), b_hat=0.0,
|
||||
amplitude_error=0.0, residual_l2=residual, phase_drift_abs=phase,
|
||||
spurious_pressure_scaled=pressure,
|
||||
spurious_pressure_rms=pressure * U * U / 4,
|
||||
velocity_l2=residual, mass_relative=0.0))
|
||||
return out
|
||||
|
||||
@@ -203,15 +220,30 @@ class AssessTests(unittest.TestCase):
|
||||
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)
|
||||
self.assertAlmostEqual(
|
||||
report["refined"]["constant"]["budget"]["spurious_pressure_scaled"], 0.03)
|
||||
self.assertIn("finest", report["refined"]["constant"]["control_ratios"])
|
||||
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)
|
||||
def test_control_outcomes_are_distinct(self):
|
||||
fine = record(256, 1, rows_for(256))
|
||||
refined = record(128, 2, rows_for(128))
|
||||
failing = record(128, 1, rows_for(128, residual=0.5)) # complete, > eps(128)
|
||||
report = sw.assess(failing, fine, [refined])
|
||||
self.assertEqual(report["status"], "CONTROL_SCREEN_FAIL")
|
||||
self.assertIn("constant", report["refined"]) # refined results preserved
|
||||
good = record(128, 1, rows_for(128))
|
||||
self.assertEqual(sw.assess(None, fine, [refined])["status"], "INVALID_CONTROL")
|
||||
incomplete = record(128, 1, rows_for(128), completed=False)
|
||||
self.assertEqual(sw.assess(incomplete, fine, [refined])["status"], "INVALID_CONTROL")
|
||||
mixed = record(256, 1, rows_for(256), source="other")
|
||||
self.assertEqual(sw.assess(good, mixed, [refined])["status"], "INVALID_CONTROL")
|
||||
|
||||
def test_wrong_shapes(self):
|
||||
base = record(128, 1, rows_for(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):
|
||||
|
||||
Reference in new issue
Block a user