physmap 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. physmap/__init__.py +61 -0
  2. physmap/_paths.py +69 -0
  3. physmap/applicability/__init__.py +0 -0
  4. physmap/applicability/fixtures.py +83 -0
  5. physmap/applicability/screen.py +99 -0
  6. physmap/baselines/__init__.py +0 -0
  7. physmap/benchmarks/__init__.py +0 -0
  8. physmap/benchmarks/benchmark_report.py +405 -0
  9. physmap/benchmarks/benchmark_v0_4.py +424 -0
  10. physmap/benchmarks/compare.py +149 -0
  11. physmap/benchmarks/registry.py +217 -0
  12. physmap/benchmarks/report.py +224 -0
  13. physmap/cli.py +301 -0
  14. physmap/closures/__init__.py +48 -0
  15. physmap/closures/data/__init__.py +7 -0
  16. physmap/closures/data/closure_index.json +2997 -0
  17. physmap/closures/formulas.py +213 -0
  18. physmap/closures/geometry_classes.py +109 -0
  19. physmap/closures/index.py +393 -0
  20. physmap/closures/registry.py +313 -0
  21. physmap/compat/__init__.py +0 -0
  22. physmap/core/__init__.py +0 -0
  23. physmap/core/mechanism.py +69 -0
  24. physmap/core/signals.py +50 -0
  25. physmap/corpus/__init__.py +12 -0
  26. physmap/corpus/calibration.py +543 -0
  27. physmap/corpus/data/__init__.py +12 -0
  28. physmap/corpus/data/corpus_seed.jsonl +15 -0
  29. physmap/corpus/data/evidence_claims_seed.jsonl +21 -0
  30. physmap/corpus/data/evidence_sources_seed.jsonl +8 -0
  31. physmap/corpus/data/premium_coverage.json +60 -0
  32. physmap/corpus/evidence.py +871 -0
  33. physmap/explain/__init__.py +0 -0
  34. physmap/explain/benchmark.py +101 -0
  35. physmap/explain/causal.py +82 -0
  36. physmap/guardrail/__init__.py +38 -0
  37. physmap/guardrail/aggregator_observability.py +187 -0
  38. physmap/guardrail/classify.py +147 -0
  39. physmap/guardrail/configs.py +120 -0
  40. physmap/guardrail/corpus_regimes.py +208 -0
  41. physmap/guardrail/detector_conformal.py +129 -0
  42. physmap/guardrail/detector_density.py +74 -0
  43. physmap/guardrail/enums.py +69 -0
  44. physmap/guardrail/graph.py +73 -0
  45. physmap/guardrail/guardrail.py +606 -0
  46. physmap/guardrail/io.py +201 -0
  47. physmap/guardrail/regime_observability.py +519 -0
  48. physmap/guardrail/render.py +159 -0
  49. physmap/guardrail/weighting_heuristic.py +216 -0
  50. physmap/infra/__init__.py +23 -0
  51. physmap/infra/blindspot_oracle.py +356 -0
  52. physmap/infra/corpus_runtime.py +275 -0
  53. physmap/integrations/__init__.py +0 -0
  54. physmap/materiality/__init__.py +0 -0
  55. physmap/materiality/estimator.py +239 -0
  56. physmap/materiality/independence.py +92 -0
  57. physmap/materiality/surrogate_fit.py +293 -0
  58. physmap/observability/__init__.py +0 -0
  59. physmap/pipeline/__init__.py +58 -0
  60. physmap/pipeline/aggregators.py +199 -0
  61. physmap/pipeline/assessment_v06.py +509 -0
  62. physmap/pipeline/core.py +442 -0
  63. physmap/pipeline/defeasible_aggregator.py +324 -0
  64. physmap/pipeline/detectors.py +309 -0
  65. physmap/pipeline/observability.py +430 -0
  66. physmap/pipeline/surrogate.py +251 -0
  67. physmap/pipeline/validity_signal.py +273 -0
  68. physmap/pipeline/vehicle_spec.py +287 -0
  69. physmap/release.py +81 -0
  70. physmap/stress_tests/__init__.py +9 -0
  71. physmap/stress_tests/lewis_reuse.py +517 -0
  72. physmap/substrate/__init__.py +28 -0
  73. physmap/substrate/corpus_real.py +206 -0
  74. physmap/substrate/engine.py +209 -0
  75. physmap/substrate/forrest.py +249 -0
  76. physmap/substrate/loaders.py +2176 -0
  77. physmap/substrate/naca_tn1451.py +379 -0
  78. physmap/substrate/naca_wpd_loader.py +187 -0
  79. physmap/substrate/stage1_ingest.py +187 -0
  80. physmap/substrate/vehicle_config.py +407 -0
  81. physmap-0.2.0.dist-info/METADATA +270 -0
  82. physmap-0.2.0.dist-info/RECORD +88 -0
  83. physmap-0.2.0.dist-info/WHEEL +5 -0
  84. physmap-0.2.0.dist-info/entry_points.txt +2 -0
  85. physmap-0.2.0.dist-info/licenses/LICENSE +21 -0
  86. physmap-0.2.0.dist-info/licenses/LICENSE-CORPUS +469 -0
  87. physmap-0.2.0.dist-info/licenses/NOTICE +77 -0
  88. physmap-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,517 @@
1
+ """Controlled model-reuse stress test: Lewis (1992) Test 35A.
2
+
3
+ WHAT IT IS
4
+ The surrogate was trained for forced convection, where gravity did not vary and was not an
5
+ input. It was then reused in vertical heated flow, where buoyancy became material. A
6
+ mixed-convection surrogate designed for this regime should include Richardson number, Grashof
7
+ number, or equivalent physical information.
8
+
9
+ THE CLAIM
10
+ PhysMAP detects when model reuse activates a physically relevant mechanism outside the
11
+ surrogate's observable input space. An input-only OOD detector cannot identify a change absent
12
+ from its input contract.
13
+
14
+ WHAT IT IS NOT
15
+ Not a claim that OOD detectors fail in general -- the input-based OOD detector does exactly
16
+ its job here. Not a suggestion that gravity should be left out: a mixed-convection surrogate
17
+ built correctly exposes the relevant physics. Not a claim about NVIDIA PhysicsNeMo, whose
18
+ out-of-distribution check and physics checks are distinct and were not run.
19
+
20
+ HOW
21
+ Design M (headline): the surrogate is trained on thirteen gravity-off CFD runs, one of them at
22
+ 35A's own operating point, so every visible deployment input is an exact training input.
23
+ Design A3 (secondary): the same without that run, so 35A falls between training runs.
24
+
25
+ Both are scored twice with IDENTICAL visible inputs: against gravity-off CFD at 35A -- the
26
+ accurate control -- and against Lewis's measurement, where buoyancy is active. The OOD
27
+ detector is the seven-vehicle benchmark's own, imported unchanged. PhysMAP's output is the
28
+ causal path: the applicability screen, matched-ablation materiality and the flag rule.
29
+
30
+ STATUS
31
+ Development demonstration. ONE run, already inspected during development; its stations are
32
+ not independent cases; no performance metric is computed or implied. theta is unlocked, so
33
+ materiality is reported as continuous values and theta = 0.10 appears only as an illustration.
34
+
35
+ Reproduces cfd/lewis_head_to_head.py's pre-declared design M to the bit from the CFD-derived
36
+ inputs banked in data/stress_tests/lewis_reuse/.
37
+ """
38
+
39
+ from __future__ import annotations
40
+
41
+ import csv
42
+ import json
43
+ import math
44
+ import tempfile
45
+ from pathlib import Path
46
+
47
+ import numpy as np
48
+
49
+ from physmap._paths import checkout_path
50
+
51
+ STRESS_TEST_ID = "lewis-reuse"
52
+ BANKED_RECORD = ("results", "lewis35A_head_to_head", "stress_test_lewis_reuse.json")
53
+
54
+ FRAMING = (
55
+ "The surrogate was trained for forced convection, where gravity did not vary and was not "
56
+ "an input. It was then reused in vertical heated flow, where buoyancy became material. A "
57
+ "mixed-convection surrogate designed for this regime should include Richardson number, "
58
+ "Grashof number, or equivalent physical information."
59
+ )
60
+ CLAIM = (
61
+ "PhysMAP detects when model reuse activates a physically relevant mechanism outside the "
62
+ "surrogate's observable input space. An input-only OOD detector cannot identify a change "
63
+ "absent from its input contract."
64
+ )
65
+ NOT_CLAIMED = (
66
+ "OOD detectors do not fail in general; this one does exactly its job.",
67
+ "Gravity should not be omitted: a mixed-convection surrogate built correctly exposes Ri, Gr "
68
+ "or equivalent physical information.",
69
+ "Nothing is claimed about NVIDIA PhysicsNeMo; its OOD and physics checks are distinct and "
70
+ "were not run.",
71
+ )
72
+ STATUS = (
73
+ "Development demonstration. One run (Lewis 35A), already inspected during development. "
74
+ "Its stations are not independent cases. No performance metric is computed or implied."
75
+ )
76
+
77
+ SURROGATE_INPUTS = ("Re", "Pr", "x_over_D")
78
+ WITHHELD = ("gravity", "Ri", "Gr", "wall heat flux", "flow direction")
79
+ ILLUSTRATIVE_THETA = 0.10 # the original study's value, recorded before any Lewis work
80
+ # Lewis disowns three stations on his own authority, for reasons unrelated to buoyancy.
81
+ DISOWNED = {0.31: "axial wall conduction (Lewis Sec 7.1.1)",
82
+ 0.85: "axial wall conduction (Lewis Sec 7.1.1)",
83
+ 159.33: "'suspect' -- flange heat loss (Lewis Sec 7.1.1)"}
84
+
85
+
86
+ # ── inputs ───────────────────────────────────────────────────────────────────
87
+
88
+ def _load_inputs() -> dict:
89
+ cfd = json.loads(checkout_path("data", "stress_tests", "lewis_reuse", "cfd_profiles.json",
90
+ what="the Lewis stress test's banked CFD profiles").read_text())
91
+ manifest = json.loads(checkout_path("data", "stress_tests", "lewis_reuse", "manifest.json",
92
+ what="the Lewis stress test's manifest").read_text())
93
+ lewis = json.loads(checkout_path("data", "lewis1992", "test_35A_reduction.json",
94
+ what="Lewis Test 35A reduction").read_text())
95
+ ib = lewis["dimensionless_by_basis"]["inlet_bulk"]
96
+ return {"cfd": cfd, "manifest": manifest,
97
+ "Re": ib["Re"], "Pr": ib["Pr"], "Gr_q": ib["Gr_q_heat_flux_based"],
98
+ "measured": [(p["x_over_d"], p["Nu"]) for p in lewis["local_nu_inlet_bulk_basis"]]}
99
+
100
+
101
+ def _training_rows(inp: dict, design: str) -> list[dict]:
102
+ rows = []
103
+ for run in inp["cfd"]["training_runs"]:
104
+ for x, nu in zip(run["x_over_D"], run["Nu"]):
105
+ rows.append({"run": run["case"], "Re": run["inlet_bulk"]["Re"],
106
+ "Pr": run["inlet_bulk"]["Pr"], "x_over_D": x, "Nu": nu})
107
+ if design == "M":
108
+ m = inp["cfd"]["matched_training_run"]
109
+ # Labelled with the EVALUATED inputs, as pre-declared: its polynomial-evaluated inlet
110
+ # values differ by -0.062 % (Re) and +0.141 % (Pr), inside Lewis's own property spread.
111
+ for x, nu in zip(m["x_over_D"], m["Nu"]):
112
+ rows.append({"run": m["case"], "Re": inp["Re"], "Pr": inp["Pr"], "x_over_D": x,
113
+ "Nu": nu})
114
+ return rows
115
+
116
+
117
+ # ── the surrogate: a forced-convection GP on gravity-off CFD ─────────────────
118
+
119
+ def _X(rows):
120
+ return np.array([[math.log10(r["Re"]), r["Pr"], math.log10(r["x_over_D"])] for r in rows])
121
+
122
+
123
+ def _fit_surrogate(rows):
124
+ from sklearn.gaussian_process import GaussianProcessRegressor
125
+ from sklearn.gaussian_process.kernels import ConstantKernel, Matern, WhiteKernel
126
+ X, y = _X(rows), np.log([r["Nu"] for r in rows])
127
+ mu, sd = X.mean(0), X.std(0)
128
+ kern = ConstantKernel(1.0) * Matern(length_scale=[1.0, 1.0, 1.0], nu=2.5) + WhiteKernel(1e-6)
129
+ gp = GaussianProcessRegressor(kernel=kern, normalize_y=True, n_restarts_optimizer=3,
130
+ random_state=0).fit((X - mu) / sd, y)
131
+ return lambda rs: np.exp(gp.predict((_X(rs) - mu) / sd))
132
+
133
+
134
+ # ── the input-based OOD detector: the benchmark's own, unchanged ─────────────
135
+
136
+ def _write_csv(rows, path: Path, pred) -> None:
137
+ with path.open("w", newline="") as fh:
138
+ w = csv.writer(fh)
139
+ w.writerow(["Re", "Pr", "x_over_D", "truth", "prediction"])
140
+ for r, p in zip(rows, pred):
141
+ w.writerow([r["Re"], r["Pr"], r["x_over_D"], r["Nu"], float(p)])
142
+
143
+
144
+ def _ood(train, train_pred, tests: dict) -> dict:
145
+ """Fit the benchmark's detector tuple once per operating percentile, exactly as run_cell
146
+ does, and assess every test set with it. Each test set is written to its OWN file with its
147
+ own truth column, so an unchanged score also shows that truth never reached the detector."""
148
+ from physmap.benchmarks.benchmark_v0_4 import _DETECTORS, OPERATING_PERCENTILES
149
+ from physmap.guardrail.configs import ColumnMap
150
+ from physmap.guardrail.enums import DetectorKind, Regime
151
+ from physmap.guardrail.guardrail import CredibilityGuardrail
152
+
153
+ colmap = ColumnMap(inputs=list(SURROGATE_INPUTS), truth="truth", prediction="prediction")
154
+ out = {name: {} for name in tests}
155
+ with tempfile.TemporaryDirectory() as td:
156
+ tr = Path(td) / "train.csv"
157
+ _write_csv(train, tr, train_pred)
158
+ files = {}
159
+ for name, (rows, pred) in tests.items():
160
+ files[name] = Path(td) / f"{name}.csv"
161
+ _write_csv(rows, files[name], pred)
162
+ for pct in OPERATING_PERCENTILES:
163
+ # UNLISTED: the shipped corpus has no laminar vertical-tube closure, so the
164
+ # guardrail's closure layer is left inactive rather than fired on Re < 3000 by a
165
+ # turbulent closure for a reason unrelated to buoyancy.
166
+ g = CredibilityGuardrail(surrogate_inputs=list(SURROGATE_INPUTS),
167
+ regime=Regime.UNLISTED, detectors=_DETECTORS,
168
+ operating_pct=float(pct))
169
+ g.fit(tr, columns=colmap)
170
+ for name, f in files.items():
171
+ res = []
172
+ for a in g.assess(f, columns=colmap):
173
+ d = a.signals[DetectorKind.DISTANCE_TO_TRAINING]
174
+ v = a.signals[DetectorKind.GP_VARIANCE]
175
+ res.append({
176
+ "distance": {"score": float(d.score), "threshold": float(d.threshold),
177
+ "fired": bool(d.fired)},
178
+ "gp_variance": {"score": float(v.score), "threshold": float(v.threshold),
179
+ "fired": bool(v.fired)},
180
+ "fired": bool(d.fired or v.fired)})
181
+ out[name][str(pct)] = res
182
+ return out
183
+
184
+
185
+ # ── PhysMAP: the causal path ─────────────────────────────────────────────────
186
+
187
+ def _physmap(inp: dict, n: int) -> dict:
188
+ from physmap.applicability.screen import screen_case
189
+ from physmap.core.mechanism import CalibrationWindow, Mechanism
190
+ from physmap.materiality.estimator import (
191
+ AblationInputs, AblationProvenance, estimate_materiality, materiality_signal)
192
+ from physmap.release import EvidenceState
193
+
194
+ screen = screen_case("lewis-35A", "local_Nusselt_number", qoi_decomposes=True,
195
+ mechanisms_separable=True, has_calibration_window=True,
196
+ ablation_available=True, evidence_state=EvidenceState.MEASURED)
197
+ # The surrogate's training never saw gravity, so its calibrated window for buoyancy is
198
+ # Ri = 0 exactly. Derived from the training data, not chosen.
199
+ window = CalibrationWindow("richardson_number", 0.0, 0.0)
200
+ ri_on = inp["Gr_q"] / inp["Re"] ** 2
201
+ full = inp["cfd"]["ablation_full"]["Nu"]
202
+ removed = inp["cfd"]["ablation_removed"]["Nu"]
203
+ states = {}
204
+ for state, ri, pairs in (("gravity_on", ri_on, list(zip(full, removed))),
205
+ ("gravity_off", 0.0, list(zip(removed, removed)))):
206
+ mech = Mechanism("buoyancy-vertical-pipe-aiding", "buoyancy (Richardson number)",
207
+ window, ri)
208
+ rows = []
209
+ for q_full, q_abl in pairs[:n]:
210
+ r = estimate_materiality(mech, "local_Nusselt_number",
211
+ AblationInputs(q_full, q_abl,
212
+ AblationProvenance.MATCHED_ABLATION),
213
+ evidence_state=EvidenceState.MEASURED)
214
+ rows.append({"materiality": r.value, "status": r.status.value,
215
+ "flag_at_illustrative_theta": materiality_signal(
216
+ mech, r, theta=ILLUSTRATIVE_THETA).fired})
217
+ states[state] = {"Ri": ri, "outside_calibration": mech.outside_calibration(),
218
+ "stations": rows}
219
+ return {"applicability": screen.applicability.value, "reason_code": screen.reason_code.value,
220
+ "window": window.describe(), "states": states}
221
+
222
+
223
+ # ── one design, both gravity states ──────────────────────────────────────────
224
+
225
+ def _design(inp: dict, design: str) -> dict:
226
+ train = _training_rows(inp, design)
227
+ surrogate = _fit_surrogate(train)
228
+ xds = [x for x, _ in inp["measured"]]
229
+ deploy = [{"Re": inp["Re"], "Pr": inp["Pr"], "x_over_D": x} for x in xds]
230
+ truth = {"gravity_on": [nu for _, nu in inp["measured"]],
231
+ "gravity_off": inp["cfd"]["ablation_removed"]["Nu"]}
232
+ tests = {s: ([dict(d, Nu=t) for d, t in zip(deploy, truth[s])], None) for s in truth}
233
+ pred = surrogate(tests["gravity_on"][0])
234
+ tests = {s: (rows, pred) for s, (rows, _) in tests.items()}
235
+ ood = _ood(train, surrogate(train), tests)
236
+
237
+ seen = {(r["Re"], r["Pr"], r["x_over_D"]) for r in train}
238
+ overlap = [(d["Re"], d["Pr"], d["x_over_D"]) in seen for d in deploy]
239
+ return {"design": design, "training_runs": len({r["run"] for r in train}),
240
+ "training_rows": len(train),
241
+ "visible_inputs_exactly_in_training": overlap,
242
+ "surrogate_prediction": [float(p) for p in pred],
243
+ "truth": truth, "ood": ood}
244
+
245
+
246
+ # ── the record ───────────────────────────────────────────────────────────────
247
+
248
+ def run() -> dict:
249
+ """Run the stress test and return its full record. Asserts nothing; `check` does."""
250
+ import warnings
251
+ from physmap.benchmarks.benchmark_v0_4 import OPERATING_PERCENTILES, REFERENCE_PCT
252
+ inp = _load_inputs()
253
+ xds = [x for x, _ in inp["measured"]]
254
+ comparable = [x not in DISOWNED for x in xds]
255
+ # scikit-learn's L-BFGS hyperparameter search warns when it stops at an iteration cap.
256
+ # The warning is silenced here and ONLY here, and the fit is not taken on trust for it:
257
+ # the control accuracy this record carries measures the surrogate against gravity-off
258
+ # CFD at every deployment station, and `check` fails the run if that ever degrades.
259
+ with warnings.catch_warnings():
260
+ warnings.filterwarnings("ignore", module="sklearn")
261
+ M, A3 = _design(inp, "M"), _design(inp, "A3")
262
+ pm = _physmap(inp, len(xds))
263
+ ref = str(int(REFERENCE_PCT))
264
+ on_mat = [s["materiality"] for s in pm["states"]["gravity_on"]["stations"]]
265
+ comp_mat = [m for m, c in zip(on_mat, comparable) if c]
266
+
267
+ def stations(d: dict) -> list[dict]:
268
+ out = []
269
+ for i, x in enumerate(xds):
270
+ p = d["surrogate_prediction"][i]
271
+ out.append({
272
+ "x_over_D": x, "comparable": comparable[i], "why_not": DISOWNED.get(x),
273
+ "surrogate_prediction": p,
274
+ "control_gravity_off_truth": d["truth"]["gravity_off"][i],
275
+ "control_error_pct": 100.0 * (p / d["truth"]["gravity_off"][i] - 1.0),
276
+ "experiment_Nu": d["truth"]["gravity_on"][i],
277
+ "experimental_error_pct": 100.0 * (p / d["truth"]["gravity_on"][i] - 1.0),
278
+ "ood_gravity_off": {pc: d["ood"]["gravity_off"][pc][i] for pc in d["ood"]["gravity_off"]},
279
+ "ood_gravity_on": {pc: d["ood"]["gravity_on"][pc][i] for pc in d["ood"]["gravity_on"]},
280
+ "materiality_gravity_off": pm["states"]["gravity_off"]["stations"][i]["materiality"],
281
+ "materiality_gravity_on": on_mat[i],
282
+ "physmap_flag_gravity_off_at_illustrative_theta":
283
+ pm["states"]["gravity_off"]["stations"][i]["flag_at_illustrative_theta"],
284
+ "physmap_flag_gravity_on_at_illustrative_theta":
285
+ pm["states"]["gravity_on"]["stations"][i]["flag_at_illustrative_theta"],
286
+ })
287
+ return out
288
+
289
+ def ood_summary(d: dict) -> dict:
290
+ per_pct = {}
291
+ for pc in d["ood"]["gravity_on"]:
292
+ per_pct[pc] = {s: sum(1 for i, c in enumerate(comparable)
293
+ if c and d["ood"][s][pc][i]["fired"])
294
+ for s in ("gravity_off", "gravity_on")}
295
+ return {"comparable_stations_fired_by_pct": per_pct,
296
+ "identical_between_gravity_states": d["ood"]["gravity_off"] == d["ood"]["gravity_on"]}
297
+
298
+ headline, secondary = stations(M), stations(A3)
299
+ return {
300
+ "stress_test": STRESS_TEST_ID,
301
+ "kind": "controlled model-reuse stress test",
302
+ "framing": FRAMING, "claim": CLAIM, "not_claimed": list(NOT_CLAIMED), "status": STATUS,
303
+ "input_contract": {
304
+ "surrogate_inputs": list(SURROGATE_INPUTS),
305
+ "ood_detector_inputs": list(SURROGATE_INPUTS),
306
+ "ood_detector_features": ["log10_Re", "Pr", "x_over_D"],
307
+ "withheld_from_both": list(WITHHELD),
308
+ "deployment_inputs": {"Re": inp["Re"], "Pr": inp["Pr"], "x_over_D": xds},
309
+ "design_M_every_visible_input_exactly_in_training":
310
+ all(M["visible_inputs_exactly_in_training"]),
311
+ "design_A3_visible_inputs_in_training": sum(A3["visible_inputs_exactly_in_training"]),
312
+ },
313
+ "matched_ablation": {**inp["manifest"]["matched_ablation"],
314
+ "full": {k: inp["cfd"]["ablation_full"][k] for k in
315
+ ("case", "gravity_on", "iterations", "energy_closure_pct",
316
+ "final_initial_residuals",
317
+ "reversed_cells_in_heated_section")},
318
+ "removed": {k: inp["cfd"]["ablation_removed"][k] for k in
319
+ ("case", "gravity_on", "iterations", "energy_closure_pct",
320
+ "final_initial_residuals",
321
+ "reversed_cells_in_heated_section")}},
322
+ "inputs_sha256": inp["manifest"]["cfd_profiles_sha256"],
323
+ "ood_detector": {"object": "physmap.benchmarks.benchmark_v0_4._DETECTORS, unchanged",
324
+ "operating_percentiles": list(OPERATING_PERCENTILES),
325
+ "reference_pct": REFERENCE_PCT},
326
+ "physmap": {"applicability": pm["applicability"], "reason_code": pm["reason_code"],
327
+ "calibration_window": pm["window"],
328
+ "Ri_gravity_on": pm["states"]["gravity_on"]["Ri"],
329
+ "outside_calibration": {s: pm["states"][s]["outside_calibration"]
330
+ for s in pm["states"]}},
331
+ "headline_design_M": {"training_runs": M["training_runs"],
332
+ "training_rows": M["training_rows"],
333
+ "ood": ood_summary(M), "stations": headline},
334
+ "secondary_design_A3": {"training_runs": A3["training_runs"],
335
+ "training_rows": A3["training_rows"],
336
+ "ood": ood_summary(A3), "stations": secondary},
337
+ "threshold_dependence": [
338
+ {"result": "OOD scores identical between gravity off and gravity on",
339
+ "threshold": "none", "depends_on_unlocked_threshold": False,
340
+ "note": "exact equality of every score"},
341
+ {"result": "every visible deployment input is a training input (design M)",
342
+ "threshold": "none", "depends_on_unlocked_threshold": False,
343
+ "note": "exact set membership"},
344
+ {"result": "materiality: 0 with gravity off, continuous values with gravity on",
345
+ "threshold": "none", "depends_on_unlocked_threshold": False,
346
+ "note": "reported as values"},
347
+ {"result": "control and experimental errors",
348
+ "threshold": "none", "depends_on_unlocked_threshold": False,
349
+ "note": "reported as values; no right/wrong label, the tolerance being unlocked"},
350
+ {"result": "OOD fired or quiet at a station",
351
+ "threshold": f"operating percentile -- shipped reference {REFERENCE_PCT:g}, fixed "
352
+ f"in code before this work",
353
+ "depends_on_unlocked_threshold": False,
354
+ "note": "the full sweep is reported; the fired/quiet pattern is identical between "
355
+ "gravity states at every percentile"},
356
+ {"result": "PhysMAP flags nothing with gravity off",
357
+ "threshold": "none", "depends_on_unlocked_threshold": False,
358
+ "note": "holds for every theta: materiality is 0 and Ri = 0 is inside the window"},
359
+ {"result": "PhysMAP flags at least one comparable station with gravity on",
360
+ "threshold": "theta -- UNLOCKED", "depends_on_unlocked_threshold": True,
361
+ "holds_for_theta_up_to": max(comp_mat)},
362
+ {"result": "which stations PhysMAP flags",
363
+ "threshold": "theta -- UNLOCKED", "depends_on_unlocked_threshold": True,
364
+ "note": f"a station flags for theta up to its own materiality; shown at "
365
+ f"theta = {ILLUSTRATIVE_THETA:.2f}, illustrative only"},
366
+ ],
367
+ }
368
+
369
+
370
+ def check(record: dict) -> list[str]:
371
+ """The stress test's own assertions. Returns the failures; empty means all hold."""
372
+ fails = []
373
+ ic = record["input_contract"]
374
+ worst = max(abs(s["control_error_pct"]) for s in record["headline_design_M"]["stations"])
375
+ if worst > 0.5:
376
+ fails.append(f"design M: the surrogate misses the accurate gravity-off control by "
377
+ f"{worst:.2f} %, so it is no longer a trustworthy forced-convection model")
378
+ if not ic["design_M_every_visible_input_exactly_in_training"]:
379
+ fails.append("design M: a visible deployment input is not an exact training input")
380
+ if ic["surrogate_inputs"] != ic["ood_detector_inputs"]:
381
+ fails.append("the OOD detector does not receive exactly the surrogate's inputs")
382
+ for key in ("headline_design_M", "secondary_design_A3"):
383
+ if not record[key]["ood"]["identical_between_gravity_states"]:
384
+ fails.append(f"{key}: OOD scores changed between gravity off and gravity on")
385
+ for s in record["headline_design_M"]["stations"]:
386
+ if s["materiality_gravity_off"] != 0.0:
387
+ fails.append(f"x/D {s['x_over_D']}: materiality with gravity off is not zero")
388
+ return fails
389
+
390
+
391
+ def load_banked() -> dict:
392
+ return json.loads(checkout_path(*BANKED_RECORD,
393
+ what="the banked Lewis stress-test record").read_text())
394
+
395
+
396
+ #: How closely a recomputed record must match the bank. The benchmark matrix uses 1e-9 relative,
397
+ #: which absorbs last-bit BLAS noise and nothing more. This test also re-optimises a
398
+ #: Gaussian-process surrogate's hyperparameters, and another numpy/BLAS build or CPU moves that
399
+ #: optimum a little. Measured against the bank (macOS x86-64, numpy 2.2): macOS arm64 with
400
+ #: numpy 2.4, Linux arm64 with numpy 2.5, and Linux x86-64 with the bank's own numpy 2.2 moved
401
+ #: surrogate predictions by up to 1.1e-7 relative, OOD GP-variance scores by up to 7.4e-6
402
+ #: relative, and the error percentages by up to 1.3e-5 percentage points -- errors are
403
+ #: differences of two near-equal numbers, so they get an absolute tolerance. Both tolerances sit at least ten times under the digits the command
404
+ #: prints (errors to 0.01, scores to 0.001 and 0.0001). What decides the result is compared
405
+ #: exactly whatever the tolerance: every fired/quiet flag, every PhysMAP flag, every count and
406
+ #: label. `check` separately asserts the exact input overlap and the exact identity of the OOD
407
+ #: scores between the two gravity states of one run.
408
+ BANK_REL_TOL = 1e-4 # every float but the error percentages
409
+ BANK_ERROR_ABS_TOL = 1e-3 # the error percentages, in percentage points
410
+ BANK_TOLERANCE_NOTE = "1e-4 relative, and 0.001 percentage points for errors"
411
+
412
+
413
+ def bank_tolerance(path: str) -> tuple[float, float]:
414
+ """(relative, absolute) tolerance for one field of the record."""
415
+ if path.endswith("_error_pct"):
416
+ return 0.0, BANK_ERROR_ABS_TOL
417
+ return BANK_REL_TOL, 0.0
418
+
419
+
420
+ def compare_with_bank(record: dict):
421
+ """The command's drift check: `record` against the committed bank, at the tolerances
422
+ above."""
423
+ from physmap.benchmarks.compare import compare_records
424
+ return compare_records(record, load_banked(), tolerance=bank_tolerance,
425
+ tolerance_note=BANK_TOLERANCE_NOTE)
426
+
427
+
428
+ def render(record: dict) -> str:
429
+ """Deterministic report. Continuous values first; the illustrative threshold last."""
430
+ from physmap.benchmarks.benchmark_v0_4 import REFERENCE_PCT
431
+ ref = str(int(REFERENCE_PCT))
432
+ L = []
433
+ L.append("CONTROLLED MODEL-REUSE STRESS TEST -- Lewis (1992) Test 35A")
434
+ L.append("")
435
+ L.append(_wrap(record["framing"]))
436
+ L.append("")
437
+ L.append("Claim: " + _wrap(record["claim"], indent=7).lstrip())
438
+ L.append("")
439
+ L.append(_wrap(record["status"]))
440
+ L.append("")
441
+ ic = record["input_contract"]
442
+ L.append("INPUT CONTRACT")
443
+ L.append(f" surrogate receives {', '.join(ic['surrogate_inputs'])}")
444
+ L.append(f" OOD detector receives {', '.join(ic['ood_detector_inputs'])}")
445
+ L.append(f" neither receives {', '.join(ic['withheld_from_both'])}")
446
+ L.append(f" deployment Re {ic['deployment_inputs']['Re']}, "
447
+ f"Pr {ic['deployment_inputs']['Pr']}, x/D at Lewis's 12 stations")
448
+ L.append(" every visible deployment input exactly matches a training input (design M): "
449
+ + ("YES" if ic["design_M_every_visible_input_exactly_in_training"] else "NO"))
450
+ L.append("")
451
+ ma = record["matched_ablation"]
452
+ L.append("MATCHED ABLATION")
453
+ L.append(f" provenance {ma['provenance']}; {ma['cases'][0]} vs {ma['cases'][1]}")
454
+ L.append(f" only difference: {ma['only_difference'].split(';')[0]}")
455
+ L.append(f" energy closure {ma['full']['energy_closure_pct']:+.2f} % (full), "
456
+ f"{ma['removed']['energy_closure_pct']:+.2f} % (removed), against the inlet-property "
457
+ f"energy balance; {ma['iterations']} iterations each")
458
+ L.append("")
459
+ L.append("HEADLINE -- design M: every visible deployment input is a training input")
460
+ L.append(f" {'x/D':>7} | {'surrogate error':^21} | {'input-based OOD detector':^27} | "
461
+ f"{'PhysMAP materiality':^19}")
462
+ L.append(f" {'':>7} | {'control':>9} {'experiment':>11} | {'distance':>9} {'GP var':>8} "
463
+ f"{'@' + ref:>8} | {'grav off':>8} {'grav on':>9}")
464
+ for s in record["headline_design_M"]["stations"]:
465
+ on = s["ood_gravity_on"][ref]
466
+ mark = " " if s["comparable"] else "*"
467
+ L.append(f" {s['x_over_D']:>7.2f} {mark} | {s['control_error_pct']:>+8.2f}% "
468
+ f"{s['experimental_error_pct']:>+10.1f}% | {on['distance']['score']:>9.3f} "
469
+ f"{on['gp_variance']['score']:>8.4f} {('fires' if on['fired'] else 'quiet'):>8} | "
470
+ f"{s['materiality_gravity_off']:>8.3f} {s['materiality_gravity_on']:>9.3f}")
471
+ thr = record["headline_design_M"]["stations"][0]["ood_gravity_on"][ref]
472
+ L.append(f" * disowned by Lewis. OOD thresholds at the reference percentile {ref}: "
473
+ f"distance {thr['distance']['threshold']:.3f}, GP variance "
474
+ f"{thr['gp_variance']['threshold']:.3f}.")
475
+ ident = record["headline_design_M"]["ood"]["identical_between_gravity_states"]
476
+ L.append(" The OOD columns are the SAME with gravity off and on -- every score, every "
477
+ "percentile: " + ("YES" if ident else "NO"))
478
+ sweep = record["headline_design_M"]["ood"]["comparable_stations_fired_by_pct"]
479
+ n_comp = sum(1 for s in record["headline_design_M"]["stations"] if s["comparable"])
480
+ L.append(" Comparable stations where it fires, by operating percentile (both states): "
481
+ + ", ".join(f"{pc}: {v['gravity_on']}/{n_comp}" for pc, v in sweep.items()))
482
+ L.append(" (At low percentiles it fires on its own training data too -- see "
483
+ "design_M_low_pct_mechanism.json.)")
484
+ L.append("")
485
+ L.append("SECONDARY -- design A3, the visible operating point falls BETWEEN training runs")
486
+ sec = record["secondary_design_A3"]["ood"]
487
+ n_comp = sum(1 for s in record["secondary_design_A3"]["stations"] if s["comparable"])
488
+ L.append(f" OOD fires at {sec['comparable_stations_fired_by_pct'][ref]['gravity_off']} of "
489
+ f"{n_comp} comparable stations with gravity off (surrogate accurate) and "
490
+ f"{sec['comparable_stations_fired_by_pct'][ref]['gravity_on']} of {n_comp} with "
491
+ f"gravity on; scores identical between them: "
492
+ + ("YES" if sec["identical_between_gravity_states"] else "NO"))
493
+ L.append(" PhysMAP's materiality is the same as in the headline: it follows the physics, "
494
+ "not the training design.")
495
+ L.append("")
496
+ L.append("THRESHOLDS -- which results depend on one")
497
+ for t in record["threshold_dependence"]:
498
+ extra = (f"holds for theta <= {t['holds_for_theta_up_to']:.3f}"
499
+ if "holds_for_theta_up_to" in t else t.get("note", ""))
500
+ L.append(f" {t['result']}")
501
+ L.append(f" threshold: {t['threshold']}. {extra}")
502
+ flagged = [s["x_over_D"] for s in record["headline_design_M"]["stations"]
503
+ if s["comparable"] and s["physmap_flag_gravity_on_at_illustrative_theta"]]
504
+ L.append(f" At the ILLUSTRATIVE theta = {ILLUSTRATIVE_THETA:.2f} (not a verdict; theta is "
505
+ f"unlocked) PhysMAP would flag x/D {', '.join(f'{x:g}' for x in flagged)} with "
506
+ f"gravity on, and nothing with gravity off.")
507
+ L.append("")
508
+ L.append("NOT CLAIMED")
509
+ for n in record["not_claimed"]:
510
+ L.append(" - " + _wrap(n, indent=4).lstrip())
511
+ return "\n".join(L)
512
+
513
+
514
+ def _wrap(text: str, width: int = 92, indent: int = 0) -> str:
515
+ import textwrap
516
+ return textwrap.fill(text, width=width, initial_indent=" " * indent,
517
+ subsequent_indent=" " * indent)
@@ -0,0 +1,28 @@
1
+ """Substrate machinery — engine + configs + per-vehicle adapters.
2
+
3
+ The architecture refactor's substrate-side surface, grouped under one
4
+ subpackage. Read order for newcomers:
5
+
6
+ vehicle_config — VehicleConfig dataclass + strict-schema YAML loader.
7
+ engine — build_substrate(config, registry, corpus) with the
8
+ geometry-match invariant (the Sparrow-Cur lesson
9
+ encoded as code).
10
+ loaders — LOADERS dispatch registry; one adapter per vehicle.
11
+ stage1_ingest — Row + Mechanism dataclasses + the independence guard.
12
+ corpus_real — SubstrateMeta dataclass + per-row physics-coords helper.
13
+
14
+ Legacy per-vehicle modules (post-Cleanup-5; CSV helpers + thin shims to
15
+ the engine path):
16
+ forrest — Forrest mini-channel (cell_assignment + dims only).
17
+ lance_smith — L&S transient (CSV I/O helpers + lance_smith_to_rows shim).
18
+ naca_tn1451 — NACA TN-1451 (NACARow + from_synthetic_shape).
19
+ naca_wpd_loader — NACA WPD CSV loader (called by the engine adapter).
20
+
21
+ Module renames during R3:
22
+ substrate_engine.py -> engine.py
23
+ substrate_loaders.py -> loaders.py
24
+ forrest_substrate.py -> forrest.py
25
+ lance_smith_substrate.py -> lance_smith.py
26
+ naca_tn1451_substrate.py -> naca_tn1451.py
27
+ (vehicle_config, stage1_ingest, corpus_real, naca_wpd_loader keep names.)
28
+ """