test: DFG Re20 steady rule as agreed (10 % range, 2 % slope, window-mean verdict)
Supersedes71221837. Samples every t* 0.5 (interval-mean forces), trailing t* 50..60 window (21 samples); range <= 10 % and |slope*window| <= 2 % of each official band width. Verdict on the window mean with the distance to the nearest band edge. Our engineering rule, frozen before any run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> (cherry picked from commit869d60d78e)
This commit is contained in:
@@ -22,11 +22,14 @@ inlet is local Zou-He.
|
||||
## Bounds and steady rule (frozen before any run)
|
||||
|
||||
- End time t* = 60 (30 s physical): 240,000 refined root steps, 480,000 uniform steps.
|
||||
- One sample every t* = 1 (0.5 s) at the end of a synchronized root step; trailing
|
||||
window t* = 10 (last 5 s, 11 samples).
|
||||
- Steady: in the window, both max - min and |least-squares slope x window| of each
|
||||
of Cd, Cl, p_diff are at most 1 % of its official interval width (2e-4, 6e-6,
|
||||
4e-6). Absolute limits, so the small Cl is never a divisor. Never relaxed after a run.
|
||||
- One sample every t* = 0.5 (0.25 s) at the end of a synchronized root step; Cd and
|
||||
Cl are interval means. Trailing window t* 50..60 inclusive (21 samples).
|
||||
- Steady rule (our engineering choice, not official): in the window, for each of Cd,
|
||||
Cl, p_diff, max - min <= 10 % and |least-squares slope x window| <= 2 % of its
|
||||
official interval width (range 2e-3 / 6e-5 / 4e-5; slope 4e-4 / 1.2e-5 / 8e-6).
|
||||
Absolute limits, so the small Cl is never a divisor. Never relaxed after a run.
|
||||
- The verdict uses the window mean; the distance to the nearest interval edge is
|
||||
reported for each quantity (negative outside).
|
||||
- Force sign: fluid on cylinder, x along the inflow, y up from the bottom wall; Cd > 0
|
||||
and the official Cl > 0 (`cd_positive` is reported).
|
||||
- Not steady at t* = 60: status `NOT_STEADY`, no interval verdict. Health limits
|
||||
@@ -37,10 +40,10 @@ inlet is local Zou-He.
|
||||
|
||||
```
|
||||
python tests/validation/run_dfg_re20.py --output <new-dir> --plan
|
||||
CUDA_VISIBLE_DEVICES=<n> python tests/validation/run_dfg_re20.py --case refined \
|
||||
CUDA_VISIBLE_DEVICES=<n> python tests/validation/run_dfg_re20.py --case both \
|
||||
--collision SRT --output <new-dir> --max-wall-seconds <cap>
|
||||
```
|
||||
|
||||
Run `--case uniform` only if the refined result is outside a band. `--plan` writes `plan.json` only. A GPU run writes `plan.json` and `result.json`
|
||||
The uniform D80 control separates method error from refinement error. `--plan` writes `plan.json` only. A GPU run writes `plan.json` and `result.json`
|
||||
(per case: lattice parameters, every sample, window ranges, status, verdict).
|
||||
Host contract tests: `tests/unit/test_dfg_re20.py`.
|
||||
|
||||
@@ -21,8 +21,8 @@ class Re20Contract(unittest.TestCase):
|
||||
root, uniform = v.lattice_parameters(40), v.lattice_parameters(80)
|
||||
self.assertAlmostEqual(root["tau"], 0.56, places=12)
|
||||
self.assertAlmostEqual(uniform["tau"], 0.62, places=12)
|
||||
self.assertEqual((root["steps"], root["interval"]), (240000, 4000))
|
||||
self.assertEqual((uniform["steps"], uniform["interval"]), (480000, 8000))
|
||||
self.assertEqual((root["steps"], root["interval"]), (240000, 2000))
|
||||
self.assertEqual((uniform["steps"], uniform["interval"]), (480000, 4000))
|
||||
self.assertAlmostEqual(v.plan()["refined_fine_tau"], 0.62, places=12)
|
||||
|
||||
def test_config_only_changes_viscosity(self):
|
||||
@@ -48,21 +48,27 @@ class Re20Contract(unittest.TestCase):
|
||||
inside = dict(cd=5.58, cl=0.0106, p_diff=0.1174)
|
||||
out = v.summarize(rows(lambda i: inside))
|
||||
self.assertEqual((out["status"], out["steady"], out["window_samples"]),
|
||||
("WITHIN_INTERVALS", True, 11))
|
||||
("WITHIN_INTERVALS", True, 21))
|
||||
self.assertTrue(out["cd_positive"])
|
||||
self.assertAlmostEqual(out["edge_distance"]["cl"], 0.0002)
|
||||
self.assertTrue(all(out["verdict"].values()))
|
||||
low = v.summarize(rows(lambda i: dict(inside, cl=0.0100)))
|
||||
self.assertEqual(low["status"], "OUTSIDE_INTERVALS")
|
||||
self.assertFalse(low["verdict"]["cl"])
|
||||
|
||||
def test_drift_gives_no_verdict(self):
|
||||
# Cl drifts by more than 1 % of its interval width (6e-6) inside the window.
|
||||
def test_slow_drift_gives_no_verdict(self):
|
||||
# Range 2e-5 passes (limit 6e-5); slope * window 2e-5 exceeds 1.2e-5.
|
||||
out = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 1e-6 * i, p_diff=0.1174)))
|
||||
self.assertEqual((out["status"], out["verdict"]), ("NOT_STEADY", None))
|
||||
self.assertGreater(out["ranges"]["cl"], out["steady_limits"]["cl"])
|
||||
self.assertGreater(out["drifts"]["cl"], out["steady_limits"]["cl"])
|
||||
small = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 2e-6 * (i % 2), p_diff=0.1174)))
|
||||
self.assertTrue(small["steady"]) # 2e-6 alternation, no trend: within both limits
|
||||
self.assertLessEqual(out["ranges"]["cl"], out["range_limits"]["cl"])
|
||||
self.assertGreater(out["drifts"]["cl"], out["slope_limits"]["cl"])
|
||||
|
||||
def test_noise_range(self):
|
||||
quiet = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 2e-5 * (i % 2), p_diff=0.1174)))
|
||||
self.assertTrue(quiet["steady"]) # within the range limit, no trend
|
||||
noisy = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106 + 1e-4 * (i % 2), p_diff=0.1174)))
|
||||
self.assertEqual(noisy["status"], "NOT_STEADY")
|
||||
self.assertGreater(noisy["ranges"]["cl"], noisy["range_limits"]["cl"])
|
||||
|
||||
def test_incomplete_run_gives_no_verdict(self):
|
||||
out = v.summarize(rows(lambda i: dict(cd=5.58, cl=0.0106, p_diff=0.1174), tstar_end=30))
|
||||
|
||||
@@ -17,18 +17,20 @@ with x along the inflow and y up from the bottom wall. Cd must be positive, and
|
||||
official Cl is positive (centre 0.20 below the centreline 0.205), so a flipped sign
|
||||
shows up as Cl outside its interval.
|
||||
|
||||
Sampling. One sample every SAMPLE_TSTAR (t* = U t / D; 0.5 s), taken at the end of a
|
||||
completed root step with all levels synchronized; the step index is recorded. Cd
|
||||
and Cl are the mean over the sample interval (smooths step-parity/acoustic
|
||||
oscillation; equal to the instantaneous value at steady state); p_diff is the
|
||||
Sampling. One sample every SAMPLE_TSTAR (t* = U t / D; 0.25 s), taken at the end of
|
||||
a completed root step with all levels synchronized; the step index is recorded. Cd
|
||||
and Cl are means over the sample interval (window exchange ledger refined, per-batch
|
||||
forces uniform), which filters acoustic and step-parity oscillation; p_diff is the
|
||||
end-of-interval value at the exact surface points.
|
||||
|
||||
Steady rule (frozen before any run). Over the trailing WINDOW_TSTAR (last 5 s, 11
|
||||
samples), both the range (max - min) and |least-squares slope * window| of each of
|
||||
Cd, Cl and p_diff must not exceed 1 % of the width of its official interval. These
|
||||
are absolute limits, so the small Cl is never a divisor, and the slope term catches
|
||||
slow monotone drift. If the rule fails at TSTAR_MAX the run reports "not steady" and
|
||||
gives no interval verdict; the limits are never relaxed after a run.
|
||||
Steady rule (our engineering choice, frozen before any run; not official). Over the
|
||||
trailing WINDOW_TSTAR (t* 50..60 inclusive, 21 samples), for each of Cd, Cl and
|
||||
p_diff: range (max - min) <= 10 % and |least-squares slope * window| <= 2 % of the
|
||||
width of its official interval. Absolute limits, so the small Cl is never a divisor;
|
||||
the slope term bounds slow monotone drift that the range alone would pass. If the
|
||||
rule fails at TSTAR_MAX the run reports "not steady" and gives no interval verdict;
|
||||
limits are never relaxed after a run. The verdict uses the window mean, and the
|
||||
distance of each mean to its nearest interval edge is reported (negative outside).
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -49,11 +51,12 @@ import run_refined_dfg as rr # noqa: E402
|
||||
RE = 20.0
|
||||
U_PHYS = 0.2 # m/s mean inflow; rho = 1
|
||||
TSTAR_MAX = 60.0 # 30 s physical
|
||||
SAMPLE_TSTAR = 1.0 # 0.5 s
|
||||
WINDOW_TSTAR = 10.0 # trailing 5 s
|
||||
SAMPLE_TSTAR = 0.5 # 0.25 s
|
||||
WINDOW_TSTAR = 10.0 # trailing 5 s, 21 samples
|
||||
# Schaefer & Turek (1996), 2D-1 lower/upper bounds.
|
||||
BANDS = dict(cd=(5.57, 5.59), cl=(0.0104, 0.0110), p_diff=(0.1172, 0.1176))
|
||||
STEADY_LIMIT = {k: (hi - lo) / 100 for k, (lo, hi) in BANDS.items()}
|
||||
STEADY_RANGE = {k: (hi - lo) / 10 for k, (lo, hi) in BANDS.items()}
|
||||
STEADY_SLOPE = {k: (hi - lo) / 50 for k, (lo, hi) in BANDS.items()}
|
||||
DEVIATIONS = ("pressure-NEQ outlet (neq_extrap), not do-nothing", "local Zou-He inlet")
|
||||
|
||||
_re100_config = rr.config
|
||||
@@ -89,10 +92,12 @@ def summarize(rows):
|
||||
t = np.array([r["tstar"] for r in window])
|
||||
ranges = {k: max(r[k] for r in window) - min(r[k] for r in window) for k in BANDS}
|
||||
drifts = {k: abs(np.polyfit(t, [r[k] for r in window], 1)[0]) * WINDOW_TSTAR for k in BANDS}
|
||||
steady = all(ranges[k] <= STEADY_LIMIT[k] and drifts[k] <= STEADY_LIMIT[k] for k in BANDS)
|
||||
final = {k: rows[-1][k] for k in BANDS}
|
||||
steady = all(ranges[k] <= STEADY_RANGE[k] and drifts[k] <= STEADY_SLOPE[k] for k in BANDS)
|
||||
final = {k: float(np.mean([r[k] for r in window])) for k in BANDS}
|
||||
edge = {k: min(final[k] - BANDS[k][0], BANDS[k][1] - final[k]) for k in BANDS}
|
||||
result = dict(window_samples=len(window), ranges=ranges, drifts=drifts,
|
||||
steady_limits=STEADY_LIMIT, steady=steady, final=final, bands=BANDS,
|
||||
range_limits=STEADY_RANGE, slope_limits=STEADY_SLOPE, steady=steady,
|
||||
window_mean=final, edge_distance=edge, bands=BANDS,
|
||||
deviations=DEVIATIONS, cd_positive=final["cd"] > 0)
|
||||
if not steady:
|
||||
return dict(result, status="NOT_STEADY", verdict=None)
|
||||
@@ -182,7 +187,7 @@ def run_case(a, h, case):
|
||||
|
||||
def plan(collision="SRT"):
|
||||
return dict(re=RE, collision=collision, tstar_max=TSTAR_MAX, sample_tstar=SAMPLE_TSTAR,
|
||||
window_tstar=WINDOW_TSTAR, bands=BANDS, steady_limits=STEADY_LIMIT,
|
||||
window_tstar=WINDOW_TSTAR, bands=BANDS, range_limits=STEADY_RANGE, slope_limits=STEADY_SLOPE,
|
||||
deviations=DEVIATIONS, pressure_scale=U_PHYS**2,
|
||||
refined_root=lattice_parameters(40), refined_fine_tau=0.5 + 3 * 2 * rr.SPEED * 40 / RE,
|
||||
uniform=lattice_parameters(80),
|
||||
|
||||
Reference in New Issue
Block a user