test: shear-wave row validation, controlled-pass semantics, bounds
- shear_wave: validate_rows before screens (exact scheduled steps/t*, no duplicates, all numeric/list fields finite, nonnegative error fields, rho_min>0; signed amplitude/phase/interval errors keep their sign); INVALID_RUN for invalid refined rows; own_screens_pass vs controlled_pass so a CONTROL_SCREEN_FAIL set never reports a controlled pass; empty or duplicate refined schemes rejected; pressure-budget redundancy stated. - run_case: SHEAR_WAVE_SHA256 repinned to the updated module. - tests: divergence/advection bounds use a(t) and the derived h^2 term; invalid-row mutations; signed errors allowed; controlled_pass checks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3554cb2fde
commit
f337652683
4 files changed
+132
-32
No files matched your search
@@ -243,15 +243,25 @@ Run set: uniform N, uniform 2N, and refined depth-2 runs at root N.
|
||||
- velocity_l2 <= 0.05, mass <= 1e-5, spurious_pressure_scaled <= 0.02.
|
||||
- Budgets keep the `assess.py` form, `refined <= max(2*control_N, floor)`:
|
||||
- floors are 0.002 for rate and residual, 0.02 for pressure;
|
||||
- because the pressure ceiling and floor are both 0.02, the comparative
|
||||
pressure budget adds no constraint unless the root control exceeds 0.01;
|
||||
- the comparative pressure budget is always redundant:
|
||||
`max(2*control, 0.02)` is never below the 0.02 absolute ceiling;
|
||||
- raw ratios to both the root and finest controls, including raw
|
||||
pressure rms, are always reported.
|
||||
- Every record is validated before any screen is applied:
|
||||
- exact scheduled steps and t*, with no duplicates;
|
||||
- every numeric field finite, including list fields and density;
|
||||
- error quantities nonnegative and rho_min > 0;
|
||||
- signed quantities (amplitudes, `amplitude_error`, `phase_drift`,
|
||||
per-interval rate errors) keep their sign.
|
||||
- Outcomes are kept distinct:
|
||||
- INVALID_CONTROL: a control is missing, incomplete or nonfinite, or the
|
||||
records come from mixed sources;
|
||||
- CONTROL_SCREEN_FAIL: complete, finite controls that fail a screen.
|
||||
Refined results are still computed and retained.
|
||||
- INVALID_CONTROL: a control or refined run is missing, a control is
|
||||
incomplete or invalid, or the records come from mixed sources;
|
||||
- INVALID_RUN: a refined record fails validation;
|
||||
- CONTROL_SCREEN_FAIL: complete, valid controls fail their own screens.
|
||||
Each refined run's `own_screens_pass` is still reported, but
|
||||
`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).
|
||||
- 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 = "49ab589d15d964d38ed90a49758e159c67a516191c7501c19fe2732e19742395"
|
||||
SHEAR_WAVE_SHA256 = "4597cf1870c1a69b4525c231825a58cf601b3fe22efd2aecf8fca76601c972e8"
|
||||
|
||||
|
||||
def flow_functions(flow="taylor_green"):
|
||||
|
||||
@@ -23,8 +23,8 @@ INITIALIZATION = (
|
||||
# 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, spurious_pressure_scaled=0.02, mass_relative=1e-5)
|
||||
# With equal absolute ceiling and floor (0.02) the comparative pressure budget
|
||||
# adds no constraint unless the root control exceeds 0.01; raw ratios are kept.
|
||||
# The comparative pressure budget max(2*control, 0.02) is never below the 0.02
|
||||
# absolute ceiling, so it is always redundant; raw control ratios are kept.
|
||||
FLOORS = dict(rate_error=0.002, residual_l2=0.002, spurious_pressure_scaled=0.02)
|
||||
BASELINE_FACTOR = 2.0
|
||||
|
||||
@@ -175,13 +175,51 @@ def fit_rate(rows, length, viscosity):
|
||||
)
|
||||
|
||||
|
||||
NONNEGATIVE = ("tstar", "mass", "mass_relative", "amplitude_exact", "modal_amplitude",
|
||||
"phase_drift_abs", "velocity_l2", "residual_l2", "longitudinal_l2",
|
||||
"spurious_pressure_rms", "spurious_pressure_scaled",
|
||||
"spurious_pressure_max_scaled", "rho_min", "rho_max", "max_speed")
|
||||
SIGNED = ("a_hat", "b_hat", "amplitude_ratio", "amplitude_error", "phase_drift")
|
||||
|
||||
|
||||
def _number(value):
|
||||
return (isinstance(value, (int, float)) and not isinstance(value, bool)
|
||||
and math.isfinite(value))
|
||||
|
||||
|
||||
def validate_rows(record):
|
||||
"""Exact schedule, finite numbers (incl. lists) and nonnegative error fields.
|
||||
|
||||
Signed quantities (amplitudes, amplitude/phase errors) keep their sign.
|
||||
Raises ValueError so callers can report an invalid run before any screen.
|
||||
"""
|
||||
n, rows = record["n"], record["rows"]
|
||||
steps = [round(tstar * n / U) for tstar in TIMES]
|
||||
if not isinstance(rows, list) or len(rows) != len(steps):
|
||||
raise ValueError("rows do not match the scheduled samples")
|
||||
for row, step in zip(rows, steps):
|
||||
if type(row.get("step")) is not int or row["step"] != step:
|
||||
raise ValueError(f"unscheduled or duplicate step {row.get('step')!r}")
|
||||
if not _number(row.get("tstar")) or abs(row["tstar"] - step * U / n) > 3e-17:
|
||||
raise ValueError(f"wrong sample time at step {step}")
|
||||
if not all(_number(row.get(key)) for key in (*NONNEGATIVE, *SIGNED)):
|
||||
raise ValueError(f"missing or nonfinite diagnostic at step {step}")
|
||||
if any(row[key] < 0 for key in NONNEGATIVE) or row["rho_min"] <= 0:
|
||||
raise ValueError(f"negative error quantity or density at step {step}")
|
||||
levels = row.get("spurious_pressure_scaled_by_level")
|
||||
if (not isinstance(levels, list) or len(levels) != record["depth"]
|
||||
or not all(_number(v) and v >= 0 for v in levels)):
|
||||
raise ValueError(f"invalid per-level pressure at step {step}")
|
||||
|
||||
|
||||
def summarize(record):
|
||||
validate_rows(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),
|
||||
complete=record["completed"] is True,
|
||||
residual_l2=max(r["residual_l2"] for r in rows),
|
||||
phase_drift_abs=max(r["phase_drift_abs"] for r in rows),
|
||||
spurious_pressure_scaled=max(r["spurious_pressure_scaled"] for r in rows),
|
||||
@@ -190,7 +228,10 @@ def summarize(record):
|
||||
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(
|
||||
if 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[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
|
||||
@@ -199,36 +240,46 @@ def summarize(record):
|
||||
def _usable(record):
|
||||
try:
|
||||
out = summarize(record)
|
||||
except (KeyError, ValueError):
|
||||
except (KeyError, TypeError, ValueError):
|
||||
return None
|
||||
finite = all(math.isfinite(v) for v in out.values() if isinstance(v, float))
|
||||
return out if out["complete"] and finite else None
|
||||
return out if out["complete"] else None
|
||||
|
||||
|
||||
def assess(base_control, fine_control, refined):
|
||||
"""Uniform root-spacing and finest-spacing controls versus refined runs.
|
||||
|
||||
INVALID_CONTROL: missing, incomplete, nonfinite or mixed-source controls.
|
||||
CONTROL_SCREEN_FAIL: complete finite controls that fail a screen; refined
|
||||
results are still computed and kept.
|
||||
INVALID_CONTROL: missing, incomplete, invalid-row or mixed-source controls.
|
||||
INVALID_RUN: a refined record has invalid rows (schedule, NaN, sign).
|
||||
CONTROL_SCREEN_FAIL: complete valid controls that fail their own screens;
|
||||
refined own-screen results are kept, but no controlled comparison passes.
|
||||
PASS/FAIL: every refined run's controlled_pass (own screens and budgets).
|
||||
"""
|
||||
if base_control is None or fine_control is None:
|
||||
return dict(status="INVALID_CONTROL", reason="missing control")
|
||||
if base_control is None or fine_control is None or not refined:
|
||||
return dict(status="INVALID_CONTROL", reason="missing control or refined run")
|
||||
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")
|
||||
if len({r.get("explosion") for r in refined}) != len(refined):
|
||||
raise ValueError("duplicate refined explosion scheme")
|
||||
sources = {repr(r.get("source")) for r in [base_control, fine_control, *refined]}
|
||||
base, fine = _usable(base_control), _usable(fine_control)
|
||||
if base is None or fine is None or len(sources) != 1:
|
||||
return dict(status="INVALID_CONTROL",
|
||||
reason="incomplete/nonfinite control or mixed source")
|
||||
report = dict(base_control=base, fine_control=fine, refined={}, status="PASS")
|
||||
if not (base["absolute_pass"] and fine["absolute_pass"]):
|
||||
report["status"] = "CONTROL_SCREEN_FAIL"
|
||||
reason="incomplete/invalid control or mixed source")
|
||||
controls_pass = base["own_screens_pass"] and fine["own_screens_pass"]
|
||||
report = dict(base_control=base, fine_control=fine, refined={},
|
||||
controls_pass=controls_pass)
|
||||
statuses = []
|
||||
for r in refined:
|
||||
s = summarize(r)
|
||||
try:
|
||||
s = summarize(r)
|
||||
except (KeyError, TypeError, ValueError) as error:
|
||||
report["refined"][r["explosion"]] = dict(valid=False, reason=str(error))
|
||||
statuses.append("INVALID_RUN")
|
||||
continue
|
||||
s["valid"] = True
|
||||
s["budget"] = {
|
||||
key: max(BASELINE_FACTOR * base[key], floor) for key, floor in FLOORS.items()
|
||||
}
|
||||
@@ -238,8 +289,13 @@ def assess(base_control, fine_control, refined):
|
||||
for key in (*FLOORS, "spurious_pressure_rms")}
|
||||
for name, c in (("root", base), ("finest", fine))
|
||||
}
|
||||
s["pass"] = s["absolute_pass"] and s["budget_pass"]
|
||||
s["controlled_pass"] = controls_pass and s["own_screens_pass"] and s["budget_pass"]
|
||||
report["refined"][r["explosion"]] = s
|
||||
if report["status"] == "PASS" and not s["pass"]:
|
||||
report["status"] = "FAIL"
|
||||
statuses.append("PASS" if s["controlled_pass"] else "FAIL")
|
||||
if "INVALID_RUN" in statuses:
|
||||
report["status"] = "INVALID_RUN"
|
||||
elif not controls_pass:
|
||||
report["status"] = "CONTROL_SCREEN_FAIL"
|
||||
else:
|
||||
report["status"] = "PASS" if all(v == "PASS" for v in statuses) else "FAIL"
|
||||
return report
|
||||
@@ -85,11 +85,13 @@ class ConventionTests(unittest.TestCase):
|
||||
# Central differences do not cancel exactly: each derivative carries
|
||||
# -(k_j h)^2/6 relative truncation, so div_h = -A cos(theta) h^2/6 sum_j E_j k_j^3
|
||||
# + O(h^4) (nonzero for an oblique k). Bound it with that leading term plus roundoff.
|
||||
trunc = U * h * h / 6 * float(np.abs(sw.E * k**3).sum()) * 1.01
|
||||
a = U * math.exp(-sw.decay_rate(N, NU) * t)
|
||||
trunc = a * h * h / 6 * float(np.abs(sw.E * k**3).sum()) * 1.01
|
||||
# Argument rounding: eps*max|x|/h*U*|k| ~ 2.2e-16*21.6/1e-3*0.03*0.44 ~ 6e-14.
|
||||
roundoff = 1e-13
|
||||
self.assertLess(np.max(np.abs(dx[0] + dy[1])), trunc + roundoff)
|
||||
self.assertLess(np.max(np.abs(z[0] * dx + z[1] * dy)), U * trunc + roundoff)
|
||||
# Discrete advection: same truncation scaled by |u| <= a.
|
||||
self.assertLess(np.max(np.abs(z[0] * dx + z[1] * dy)), a * trunc + roundoff)
|
||||
# Halving h reduces the divergence truncation about fourfold.
|
||||
hh = h / 2
|
||||
div2 = ((f(x + hh, y, t) - f(x - hh, y, t))[0] + (f(x, y + hh, t) - f(x, y - hh, t))[1]) / (2 * hh)
|
||||
@@ -206,6 +208,8 @@ class MetricTests(unittest.TestCase):
|
||||
|
||||
|
||||
def record(n, depth, rows, explosion="constant", completed=True, source="s"):
|
||||
for row in rows:
|
||||
row["spurious_pressure_scaled_by_level"] = [row["spurious_pressure_scaled"]] * depth
|
||||
return dict(n=n, depth=depth, viscosity=U * n / sw.RE, rows=rows, completed=completed,
|
||||
explosion=explosion, source=source)
|
||||
|
||||
@@ -215,7 +219,11 @@ 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), b_hat=0.0,
|
||||
amp = U * math.exp(-rate * step)
|
||||
out.append(dict(tstar=step * U / n, step=step, a_hat=amp, b_hat=0.0,
|
||||
amplitude_ratio=1.0, modal_amplitude=amp, amplitude_exact=amp,
|
||||
phase_drift=phase, mass=float(n * n), rho_min=0.999, rho_max=1.001,
|
||||
max_speed=amp, longitudinal_l2=0.0, spurious_pressure_max_scaled=pressure,
|
||||
amplitude_error=0.0, residual_l2=residual, phase_drift_abs=phase,
|
||||
spurious_pressure_scaled=pressure,
|
||||
spurious_pressure_rms=pressure * U * U / 4,
|
||||
@@ -245,7 +253,9 @@ class AssessTests(unittest.TestCase):
|
||||
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
|
||||
own = report["refined"]["constant"]
|
||||
self.assertTrue(own["own_screens_pass"]) # refined own screens kept
|
||||
self.assertFalse(own["controlled_pass"]) # but no controlled comparison passes
|
||||
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)
|
||||
@@ -253,6 +263,29 @@ class AssessTests(unittest.TestCase):
|
||||
mixed = record(256, 1, rows_for(256), source="other")
|
||||
self.assertEqual(sw.assess(good, mixed, [refined])["status"], "INVALID_CONTROL")
|
||||
|
||||
def test_invalid_rows_are_rejected_before_screens(self):
|
||||
base = record(128, 1, rows_for(128))
|
||||
fine = record(256, 1, rows_for(256))
|
||||
for mutate in (
|
||||
lambda rows: rows[2].update(rho_min=float("nan")),
|
||||
lambda rows: rows[2].update(spurious_pressure_scaled_by_level=[0.0, float("nan")]),
|
||||
lambda rows: rows[2].update(step=rows[1]["step"], tstar=rows[1]["tstar"]),
|
||||
lambda rows: rows[2].update(residual_l2=-1e-6),
|
||||
lambda rows: rows[2].pop("longitudinal_l2"),
|
||||
):
|
||||
refined = record(128, 2, rows_for(128))
|
||||
mutate(refined["rows"])
|
||||
self.assertEqual(sw.assess(base, fine, [refined])["status"], "INVALID_RUN")
|
||||
# Legitimate signs stay allowed: negative amplitude and phase errors.
|
||||
refined = record(128, 2, rows_for(128))
|
||||
for row in refined["rows"]:
|
||||
row.update(amplitude_error=-1e-3, phase_drift=-1e-4, phase_drift_abs=1e-4)
|
||||
report = sw.assess(base, fine, [refined])
|
||||
self.assertEqual(report["status"], "PASS")
|
||||
self.assertEqual(sw.assess(base, fine, [])["status"], "INVALID_CONTROL")
|
||||
with self.assertRaises(ValueError):
|
||||
sw.assess(base, fine, [record(128, 2, rows_for(128)), record(128, 2, rows_for(128))])
|
||||
|
||||
def test_wrong_shapes(self):
|
||||
base = record(128, 1, rows_for(128))
|
||||
fine = record(256, 1, rows_for(256))
|
||||
@@ -307,7 +340,8 @@ def receipt(n, depth, explosion="constant"):
|
||||
steps = run_case.sample_steps(n)[0]
|
||||
rows = rows_for(n)
|
||||
for row, step in zip(rows, steps):
|
||||
row.update(step=step, tstar=step * U / n)
|
||||
row.update(step=step, tstar=step * U / n,
|
||||
spurious_pressure_scaled_by_level=[row["spurious_pressure_scaled"]] * depth)
|
||||
cells = [n * n] if depth == 1 else [8704, 30720]
|
||||
hops = [16 * n * 2**level for level in range(depth)]
|
||||
builds = []
|
||||
|
||||
Reference in new issue
Block a user