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.
- physmap/__init__.py +61 -0
- physmap/_paths.py +69 -0
- physmap/applicability/__init__.py +0 -0
- physmap/applicability/fixtures.py +83 -0
- physmap/applicability/screen.py +99 -0
- physmap/baselines/__init__.py +0 -0
- physmap/benchmarks/__init__.py +0 -0
- physmap/benchmarks/benchmark_report.py +405 -0
- physmap/benchmarks/benchmark_v0_4.py +424 -0
- physmap/benchmarks/compare.py +149 -0
- physmap/benchmarks/registry.py +217 -0
- physmap/benchmarks/report.py +224 -0
- physmap/cli.py +301 -0
- physmap/closures/__init__.py +48 -0
- physmap/closures/data/__init__.py +7 -0
- physmap/closures/data/closure_index.json +2997 -0
- physmap/closures/formulas.py +213 -0
- physmap/closures/geometry_classes.py +109 -0
- physmap/closures/index.py +393 -0
- physmap/closures/registry.py +313 -0
- physmap/compat/__init__.py +0 -0
- physmap/core/__init__.py +0 -0
- physmap/core/mechanism.py +69 -0
- physmap/core/signals.py +50 -0
- physmap/corpus/__init__.py +12 -0
- physmap/corpus/calibration.py +543 -0
- physmap/corpus/data/__init__.py +12 -0
- physmap/corpus/data/corpus_seed.jsonl +15 -0
- physmap/corpus/data/evidence_claims_seed.jsonl +21 -0
- physmap/corpus/data/evidence_sources_seed.jsonl +8 -0
- physmap/corpus/data/premium_coverage.json +60 -0
- physmap/corpus/evidence.py +871 -0
- physmap/explain/__init__.py +0 -0
- physmap/explain/benchmark.py +101 -0
- physmap/explain/causal.py +82 -0
- physmap/guardrail/__init__.py +38 -0
- physmap/guardrail/aggregator_observability.py +187 -0
- physmap/guardrail/classify.py +147 -0
- physmap/guardrail/configs.py +120 -0
- physmap/guardrail/corpus_regimes.py +208 -0
- physmap/guardrail/detector_conformal.py +129 -0
- physmap/guardrail/detector_density.py +74 -0
- physmap/guardrail/enums.py +69 -0
- physmap/guardrail/graph.py +73 -0
- physmap/guardrail/guardrail.py +606 -0
- physmap/guardrail/io.py +201 -0
- physmap/guardrail/regime_observability.py +519 -0
- physmap/guardrail/render.py +159 -0
- physmap/guardrail/weighting_heuristic.py +216 -0
- physmap/infra/__init__.py +23 -0
- physmap/infra/blindspot_oracle.py +356 -0
- physmap/infra/corpus_runtime.py +275 -0
- physmap/integrations/__init__.py +0 -0
- physmap/materiality/__init__.py +0 -0
- physmap/materiality/estimator.py +239 -0
- physmap/materiality/independence.py +92 -0
- physmap/materiality/surrogate_fit.py +293 -0
- physmap/observability/__init__.py +0 -0
- physmap/pipeline/__init__.py +58 -0
- physmap/pipeline/aggregators.py +199 -0
- physmap/pipeline/assessment_v06.py +509 -0
- physmap/pipeline/core.py +442 -0
- physmap/pipeline/defeasible_aggregator.py +324 -0
- physmap/pipeline/detectors.py +309 -0
- physmap/pipeline/observability.py +430 -0
- physmap/pipeline/surrogate.py +251 -0
- physmap/pipeline/validity_signal.py +273 -0
- physmap/pipeline/vehicle_spec.py +287 -0
- physmap/release.py +81 -0
- physmap/stress_tests/__init__.py +9 -0
- physmap/stress_tests/lewis_reuse.py +517 -0
- physmap/substrate/__init__.py +28 -0
- physmap/substrate/corpus_real.py +206 -0
- physmap/substrate/engine.py +209 -0
- physmap/substrate/forrest.py +249 -0
- physmap/substrate/loaders.py +2176 -0
- physmap/substrate/naca_tn1451.py +379 -0
- physmap/substrate/naca_wpd_loader.py +187 -0
- physmap/substrate/stage1_ingest.py +187 -0
- physmap/substrate/vehicle_config.py +407 -0
- physmap-0.2.0.dist-info/METADATA +270 -0
- physmap-0.2.0.dist-info/RECORD +88 -0
- physmap-0.2.0.dist-info/WHEEL +5 -0
- physmap-0.2.0.dist-info/entry_points.txt +2 -0
- physmap-0.2.0.dist-info/licenses/LICENSE +21 -0
- physmap-0.2.0.dist-info/licenses/LICENSE-CORPUS +469 -0
- physmap-0.2.0.dist-info/licenses/NOTICE +77 -0
- physmap-0.2.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""Closure registry — the keyed `{closure_id -> ClosureEntry}` map.
|
|
2
|
+
|
|
3
|
+
Each entry binds the corpus `closure_id` (the slug used in
|
|
4
|
+
`results/calibration_corpus/corpus.jsonl`) to:
|
|
5
|
+
- the executable formula (`fn`)
|
|
6
|
+
- the inputs the formula needs (`required_inputs`) — keys the substrate
|
|
7
|
+
engine populates from the row
|
|
8
|
+
- the geometry class the formula was derived for (`geometry_class`) — the
|
|
9
|
+
invariant compares this to `VehicleConfig.geometry.class_`
|
|
10
|
+
- cached validity bounds (`re_range`, `pr_range`, `ra_range`) — mirror the
|
|
11
|
+
calibration table; corpus.jsonl remains the source of truth
|
|
12
|
+
- the bound status (`status`) — mirrors `bound_status` in the calibration
|
|
13
|
+
table (`confirmed`, `claimed`, `extrapolated`, `confirmed-contested`),
|
|
14
|
+
plus the local `not-in-corpus` sentinel for reference-only formulas
|
|
15
|
+
that have no corpus entry (Petukhov, Churchill blend).
|
|
16
|
+
|
|
17
|
+
The registry is constructed at import time. Its keys MUST be a subset of
|
|
18
|
+
the calibration corpus closure_ids, except for entries marked
|
|
19
|
+
`status="not-in-corpus"`.
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
from dataclasses import dataclass, field
|
|
25
|
+
from typing import Callable
|
|
26
|
+
|
|
27
|
+
from physmap.closures.formulas import (
|
|
28
|
+
modified_sparrow_cur_nu,
|
|
29
|
+
gnielinski_nu,
|
|
30
|
+
dittus_boelter_nu,
|
|
31
|
+
petukhov_nu,
|
|
32
|
+
sieder_tate_nu,
|
|
33
|
+
pohlhausen_forced_local_nu,
|
|
34
|
+
mcadams_natural_local_nu,
|
|
35
|
+
churchill_mixed_nu,
|
|
36
|
+
aung_worku_mixed_nu,
|
|
37
|
+
pate_freestream_noise_bound,
|
|
38
|
+
marineau_entropy_shock_bound,
|
|
39
|
+
)
|
|
40
|
+
from physmap.closures.geometry_classes import (
|
|
41
|
+
NARROW_RECT_CHANNEL_ONE_SIDED,
|
|
42
|
+
CIRCULAR_PIPE,
|
|
43
|
+
FLAT_PLATE_EXTERNAL_FORCED,
|
|
44
|
+
VERTICAL_PLATE_EXTERNAL_NATURAL,
|
|
45
|
+
FLAT_PLATE_EXTERNAL_MIXED,
|
|
46
|
+
HYPERSONIC_SHARP_CONE,
|
|
47
|
+
HYPERSONIC_BLUNT_CONE,
|
|
48
|
+
assert_known_geometry,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# Bound-status vocabulary mirrors the corpus calibration table, plus one
|
|
53
|
+
# local sentinel for closures whose formula we ship but whose calibration
|
|
54
|
+
# bounds are not yet in corpus.jsonl. The ClosureValidityDetector skips
|
|
55
|
+
# entries with status="not-in-corpus" (no validity rectangle to test against).
|
|
56
|
+
ClosureStatus = str # "confirmed" | "claimed" | "extrapolated" | "confirmed-contested" | "not-in-corpus"
|
|
57
|
+
|
|
58
|
+
# Sentinel for "this formula has no calibration entry" — kept in registry
|
|
59
|
+
# only so the substrate engine can compute it for reference predictions.
|
|
60
|
+
STATUS_NOT_IN_CORPUS = "not-in-corpus"
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class ClosureEntry:
|
|
65
|
+
"""One executable closure, bridged to its corpus calibration entry."""
|
|
66
|
+
|
|
67
|
+
closure_id: str
|
|
68
|
+
fn: Callable[..., object]
|
|
69
|
+
required_inputs: tuple[str, ...] # keys substrate must pass as kwargs
|
|
70
|
+
geometry_class: str # exact string from geometry_classes.py
|
|
71
|
+
status: ClosureStatus # mirrors corpus bound_status (or sentinel)
|
|
72
|
+
re_range: tuple[float, float] | None = None # cached from corpus calibration
|
|
73
|
+
pr_range: tuple[float, float] | None = None
|
|
74
|
+
ra_range: tuple[float, float] | None = None # for natural-convection closures
|
|
75
|
+
ri_range: tuple[float, float] | None = None # for mixed-convection (richardson_number) anchors
|
|
76
|
+
bound_range: tuple[float, float] | None = None # generic validated band for a non-(Re/Pr/Ra/Ri) bound
|
|
77
|
+
# coord (freestream-noise %, S_T/X_SW); mirrors corpus
|
|
78
|
+
note: str = "" # optional human-readable rationale
|
|
79
|
+
|
|
80
|
+
def __post_init__(self) -> None:
|
|
81
|
+
assert_known_geometry(self.geometry_class)
|
|
82
|
+
if not self.closure_id:
|
|
83
|
+
raise ValueError("ClosureEntry.closure_id must be non-empty.")
|
|
84
|
+
if not self.required_inputs:
|
|
85
|
+
raise ValueError(
|
|
86
|
+
f"ClosureEntry {self.closure_id!r} declared no required_inputs; "
|
|
87
|
+
f"the substrate engine cannot dispatch the formula."
|
|
88
|
+
)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ── REGISTRY ─────────────────────────────────────────────────────────────────
|
|
92
|
+
# Keys MUST match the slug in results/calibration_corpus/corpus.jsonl, except
|
|
93
|
+
# for STATUS_NOT_IN_CORPUS entries (Petukhov, Churchill) which have no corpus
|
|
94
|
+
# row. Ranges below are CACHED from the corpus calibration table and must be
|
|
95
|
+
# kept in sync; the calibration table is the source of truth, the registry is
|
|
96
|
+
# the executable bridge.
|
|
97
|
+
|
|
98
|
+
REGISTRY: dict[str, ClosureEntry] = {
|
|
99
|
+
|
|
100
|
+
# ── narrow_rect_channel_one_sided (Forrest matched) ──────────────────────
|
|
101
|
+
"modified-sparrow-cur-asym-narrow-rect-channel-2014": ClosureEntry(
|
|
102
|
+
closure_id="modified-sparrow-cur-asym-narrow-rect-channel-2014",
|
|
103
|
+
fn=modified_sparrow_cur_nu,
|
|
104
|
+
required_inputs=("Re", "Pr"),
|
|
105
|
+
geometry_class=NARROW_RECT_CHANNEL_ONE_SIDED,
|
|
106
|
+
status="extrapolated",
|
|
107
|
+
re_range=(10000.0, 70000.0),
|
|
108
|
+
pr_range=(2.2, 5.4),
|
|
109
|
+
note="Forrest 2014 Eq. 7. Matched closure for Forrest narrow-rect mini-channel.",
|
|
110
|
+
),
|
|
111
|
+
|
|
112
|
+
# ── circular_pipe (Gnielinski/D-B/Sieder-Tate canonical triple) ──────────
|
|
113
|
+
"gnielinski-1976": ClosureEntry(
|
|
114
|
+
closure_id="gnielinski-1976",
|
|
115
|
+
fn=gnielinski_nu,
|
|
116
|
+
required_inputs=("Re", "Pr"),
|
|
117
|
+
geometry_class=CIRCULAR_PIPE,
|
|
118
|
+
status="confirmed",
|
|
119
|
+
re_range=(3000.0, 5.0e6),
|
|
120
|
+
pr_range=(0.5, 2000.0),
|
|
121
|
+
note="Gnielinski 1976. Canonical turbulent circular-pipe Nu.",
|
|
122
|
+
),
|
|
123
|
+
"gnielinski-constprop-sco2": ClosureEntry(
|
|
124
|
+
closure_id="gnielinski-constprop-sco2",
|
|
125
|
+
fn=gnielinski_nu,
|
|
126
|
+
required_inputs=("Re", "Pr"),
|
|
127
|
+
geometry_class=CIRCULAR_PIPE,
|
|
128
|
+
status="claimed",
|
|
129
|
+
re_range=(3000.0, 5.0e6),
|
|
130
|
+
pr_range=(0.5, 2000.0),
|
|
131
|
+
note=("Constant-property Gnielinski applied to supercritical CO2 (SAME Nu "
|
|
132
|
+
"formula as gnielinski-1976 -> identical surrogate prediction). "
|
|
133
|
+
"REGIME-SPECIFIC entry so its corpus validated_range can carry a "
|
|
134
|
+
"wall/bulk viscosity-ratio (property-variation) validity bound WITHOUT "
|
|
135
|
+
"mutating the shared gnielinski-1976 entry used by NACA/Forrest "
|
|
136
|
+
"(the dirker lesson). Velazquez sCO2 property-variation vehicle."),
|
|
137
|
+
),
|
|
138
|
+
"dittus-boelter-1930": ClosureEntry(
|
|
139
|
+
closure_id="dittus-boelter-1930",
|
|
140
|
+
fn=dittus_boelter_nu,
|
|
141
|
+
required_inputs=("Re", "Pr"),
|
|
142
|
+
geometry_class=CIRCULAR_PIPE,
|
|
143
|
+
status="claimed",
|
|
144
|
+
re_range=(10000.0, 1.2e6),
|
|
145
|
+
pr_range=(0.7, 160.0),
|
|
146
|
+
note="McAdams form of Dittus-Boelter. Heating default; cooling via heating=False kwarg.",
|
|
147
|
+
),
|
|
148
|
+
"dittus-boelter-buoyancy-sco2": ClosureEntry(
|
|
149
|
+
closure_id="dittus-boelter-buoyancy-sco2",
|
|
150
|
+
fn=dittus_boelter_nu,
|
|
151
|
+
required_inputs=("Re", "Pr"),
|
|
152
|
+
geometry_class=CIRCULAR_PIPE,
|
|
153
|
+
status="claimed",
|
|
154
|
+
re_range=(10000.0, 1.2e6),
|
|
155
|
+
pr_range=(0.5, 2000.0),
|
|
156
|
+
note=("Constant-property Dittus-Boelter applied to supercritical CO2 (SAME Nu formula as "
|
|
157
|
+
"dittus-boelter-1930 -> identical surrogate prediction). REGIME-SPECIFIC entry so its "
|
|
158
|
+
"corpus validated_range can carry the Liu buoyancy-parameter (Bu) validity bound WITHOUT "
|
|
159
|
+
"mutating the shared dittus-boelter-1930 entry (the dirker/velazquez lesson). Re/Pr are the "
|
|
160
|
+
"broad sCO2 turbulent envelope (Pr spikes near T_pc); the buoyancy bound Bu<=1.3e-5 is the "
|
|
161
|
+
"discriminator. Jin et al. 2023 sCO2 vertical-tube buoyancy vehicle (Liu Bu, Eq. 21)."),
|
|
162
|
+
),
|
|
163
|
+
"sieder-tate-1936": ClosureEntry(
|
|
164
|
+
closure_id="sieder-tate-1936",
|
|
165
|
+
fn=sieder_tate_nu,
|
|
166
|
+
required_inputs=("Re", "Pr"),
|
|
167
|
+
geometry_class=CIRCULAR_PIPE,
|
|
168
|
+
status="claimed",
|
|
169
|
+
re_range=(10000.0, 1.2e6),
|
|
170
|
+
pr_range=(0.7, 17000.0),
|
|
171
|
+
note="Includes mu_b/mu_w correction (defaults to 1.0 if not supplied).",
|
|
172
|
+
),
|
|
173
|
+
"petukhov-1970": ClosureEntry(
|
|
174
|
+
closure_id="petukhov-1970",
|
|
175
|
+
fn=petukhov_nu,
|
|
176
|
+
required_inputs=("Re", "Pr"),
|
|
177
|
+
geometry_class=CIRCULAR_PIPE,
|
|
178
|
+
status="claimed",
|
|
179
|
+
re_range=(10000.0, 5.0e6),
|
|
180
|
+
pr_range=(0.5, 2000.0),
|
|
181
|
+
note=("Petukhov 1970 turbulent circular-pipe Nu. Added to corpus.jsonl "
|
|
182
|
+
"as part of evidence_corpus v0.1 (commit 2897392); the registry "
|
|
183
|
+
"entry was previously tagged not-in-corpus."),
|
|
184
|
+
),
|
|
185
|
+
|
|
186
|
+
# ── circular_pipe (mixed-convection richardson_number anchor) ────────────
|
|
187
|
+
# Corpus-validity anchor for the Stage-3 MIDDLE vehicle (Dirker/Meyer/Reid
|
|
188
|
+
# water tube). main's regime framework maps MIXED_CONVECTION_HORIZONTAL_TUBE
|
|
189
|
+
# -> this closure; it carries the richardson_number bound (Ri in [0.1, 10]).
|
|
190
|
+
# No executable Nu predictor (the vehicle's surrogate is a data-driven GP);
|
|
191
|
+
# registered so the geometry-match invariant + matched lookup resolve. Fills
|
|
192
|
+
# the registry gap noted in the regime->observability mapping work.
|
|
193
|
+
"aung-worku-mixed-convection-1986": ClosureEntry(
|
|
194
|
+
closure_id="aung-worku-mixed-convection-1986",
|
|
195
|
+
fn=aung_worku_mixed_nu,
|
|
196
|
+
required_inputs=("Re", "Pr"),
|
|
197
|
+
geometry_class=CIRCULAR_PIPE,
|
|
198
|
+
status="claimed",
|
|
199
|
+
ri_range=(0.1, 10.0),
|
|
200
|
+
note=("Mixed forced-natural convection richardson_number anchor "
|
|
201
|
+
"(Ri in [0.1, 10]); corpus-validity bound only, Nu predictor not "
|
|
202
|
+
"implemented (Stage-3 dirker_water uses a GP surrogate)."),
|
|
203
|
+
),
|
|
204
|
+
|
|
205
|
+
# ── hypersonic_sharp_cone (Casper freestream-noise transition anchor) ────
|
|
206
|
+
# Corpus-validity anchor for the aerospace PHYSMAP_WINS vehicle (Casper
|
|
207
|
+
# quiet-vs-noisy hypersonic transition). Carries the freestream-noise bound
|
|
208
|
+
# (Pate-Stainback: conventional tunnels validated for RMS Pitot >= ~0.5%);
|
|
209
|
+
# fires on the flight-like QUIET deploy (~0.05%). No executable predictor
|
|
210
|
+
# (Casper uses a GP transition surrogate); registered so the geometry-match
|
|
211
|
+
# invariant + matched lookup resolve.
|
|
212
|
+
"pate-stainback-freestream-noise-hypersonic-1980": ClosureEntry(
|
|
213
|
+
closure_id="pate-stainback-freestream-noise-hypersonic-1980",
|
|
214
|
+
fn=pate_freestream_noise_bound,
|
|
215
|
+
required_inputs=("freestream_noise_pct",),
|
|
216
|
+
geometry_class=HYPERSONIC_SHARP_CONE,
|
|
217
|
+
status="claimed",
|
|
218
|
+
bound_range=(0.5, 10.0), # freestream_noise_rms_pitot_pct band (mirrors corpus.jsonl)
|
|
219
|
+
note=("Pate & Stainback freestream-disturbance transition correlation; "
|
|
220
|
+
"corpus carries the freestream_noise_rms_pitot_pct bound. Corpus-"
|
|
221
|
+
"validity anchor only — Casper uses a GP surrogate (loader "
|
|
222
|
+
"casper_hypersonic_transition)."),
|
|
223
|
+
),
|
|
224
|
+
|
|
225
|
+
# ── hypersonic_blunt_cone (Marineau entropy-layer/shock anchor) ──────────
|
|
226
|
+
# Corpus-validity anchor for the aerospace NEGATIVE-CONTROL vehicle
|
|
227
|
+
# (Marineau bluntness). Carries the entropy-layer/shock-interaction bound
|
|
228
|
+
# (S_T/X_SW >= 0.1 for the e^N / 2nd-mode regime); fires on large-bluntness
|
|
229
|
+
# deploy — but the steelman baseline ALSO fires (nose radius is a surrogate
|
|
230
|
+
# input) so PhysMAP correctly declines. No executable predictor; registered
|
|
231
|
+
# for the geometry-match invariant.
|
|
232
|
+
"marineau-entropy-layer-shock-interaction-2014": ClosureEntry(
|
|
233
|
+
closure_id="marineau-entropy-layer-shock-interaction-2014",
|
|
234
|
+
fn=marineau_entropy_shock_bound,
|
|
235
|
+
required_inputs=("st_xsw_ratio",),
|
|
236
|
+
geometry_class=HYPERSONIC_BLUNT_CONE,
|
|
237
|
+
status="claimed",
|
|
238
|
+
bound_range=(0.1, 1.0e9), # entropy_layer_shock_ratio band (mirrors corpus.jsonl)
|
|
239
|
+
note=("Marineau et al. (2014, SAND2014-4326C) entropy-layer/shock-wave "
|
|
240
|
+
"interaction transition boundary (S_T/X_SW >= 0.1); corpus carries "
|
|
241
|
+
"the entropy_layer_shock_ratio bound. Corpus-validity anchor only — "
|
|
242
|
+
"Marineau is the baseline-visible negative control (nose radius is "
|
|
243
|
+
"a surrogate input)."),
|
|
244
|
+
),
|
|
245
|
+
|
|
246
|
+
# ── flat_plate_external_forced (Lance & Smith Pohlhausen) ────────────────
|
|
247
|
+
"blasius-pohlhausen-flat-plate-forced-1921": ClosureEntry(
|
|
248
|
+
closure_id="blasius-pohlhausen-flat-plate-forced-1921",
|
|
249
|
+
fn=pohlhausen_forced_local_nu,
|
|
250
|
+
required_inputs=("Re", "Pr"),
|
|
251
|
+
geometry_class=FLAT_PLATE_EXTERNAL_FORCED,
|
|
252
|
+
status="confirmed",
|
|
253
|
+
re_range=(0.0, 5.0e5),
|
|
254
|
+
pr_range=(0.6, 50.0),
|
|
255
|
+
note=("Local form Nu_x = 0.332 Re_x^(1/2) Pr^(1/3). Substrate engine "
|
|
256
|
+
"passes Re_x as the `Re` kwarg per row."),
|
|
257
|
+
),
|
|
258
|
+
|
|
259
|
+
# ── vertical_plate_external_natural (Lance & Smith McAdams) ──────────────
|
|
260
|
+
"mcadams-vertical-plate-natural-1954": ClosureEntry(
|
|
261
|
+
closure_id="mcadams-vertical-plate-natural-1954",
|
|
262
|
+
fn=mcadams_natural_local_nu,
|
|
263
|
+
required_inputs=("Ra",),
|
|
264
|
+
geometry_class=VERTICAL_PLATE_EXTERNAL_NATURAL,
|
|
265
|
+
status="confirmed",
|
|
266
|
+
re_range=None,
|
|
267
|
+
pr_range=(0.6, 7.0),
|
|
268
|
+
ra_range=(1.0e4, 1.0e9),
|
|
269
|
+
note=("Ra_x = Gr_x * Pr. Substrate engine passes per-row Ra_x as the "
|
|
270
|
+
"`Ra` kwarg."),
|
|
271
|
+
),
|
|
272
|
+
|
|
273
|
+
# ── flat_plate_external_mixed (Lance & Smith matched closure) ────────────
|
|
274
|
+
# Churchill blend itself isn't in corpus; it's a Nu(Nu_F, Nu_N) combinator.
|
|
275
|
+
# We register it as the matched closure for the L&S vehicle so the geometry
|
|
276
|
+
# invariant has something to compare against, but flag status accordingly.
|
|
277
|
+
"churchill-mixed-convection-flat-plate": ClosureEntry(
|
|
278
|
+
closure_id="churchill-mixed-convection-flat-plate",
|
|
279
|
+
fn=churchill_mixed_nu,
|
|
280
|
+
required_inputs=("Nu_forced", "Nu_natural"),
|
|
281
|
+
geometry_class=FLAT_PLATE_EXTERNAL_MIXED,
|
|
282
|
+
status=STATUS_NOT_IN_CORPUS,
|
|
283
|
+
re_range=None,
|
|
284
|
+
pr_range=None,
|
|
285
|
+
note=("Mixed-convection blend Nu_M^n = Nu_F^n + Nu_N^n (n=3, assisting). "
|
|
286
|
+
"Not a primary closure in the corpus; composes Pohlhausen + McAdams. "
|
|
287
|
+
"Kept as L&S matched closure so the geometry invariant works."),
|
|
288
|
+
),
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def get_closure(closure_id: str) -> ClosureEntry:
|
|
293
|
+
"""Look up a closure by id; raise KeyError with a helpful message on miss."""
|
|
294
|
+
try:
|
|
295
|
+
return REGISTRY[closure_id]
|
|
296
|
+
except KeyError as exc:
|
|
297
|
+
raise KeyError(
|
|
298
|
+
f"Unknown closure_id {closure_id!r}. "
|
|
299
|
+
f"Known: {sorted(REGISTRY.keys())}. "
|
|
300
|
+
f"If you're adding a new closure, register it in "
|
|
301
|
+
f"physmap/closures/registry.py with its formula in formulas.py."
|
|
302
|
+
) from exc
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def closure_ids_for_geometry(geometry_class: str) -> list[str]:
|
|
306
|
+
"""Return all closure_ids registered for a given geometry class.
|
|
307
|
+
|
|
308
|
+
Used by the substrate engine and tests to confirm at least one matched
|
|
309
|
+
closure exists for a vehicle's geometry, and by diagnostics.
|
|
310
|
+
"""
|
|
311
|
+
assert_known_geometry(geometry_class)
|
|
312
|
+
return sorted(cid for cid, entry in REGISTRY.items()
|
|
313
|
+
if entry.geometry_class == geometry_class)
|
|
File without changes
|
physmap/core/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""A mechanism and its calibration window. Domain-neutral on purpose.
|
|
2
|
+
|
|
3
|
+
Nothing here mentions Nusselt numbers, Reynolds numbers or heat transfer. The causal
|
|
4
|
+
method is a statement about mechanisms, quantities of interest and calibration windows;
|
|
5
|
+
the moment a heat-transfer field appears in this layer, the method stops being general
|
|
6
|
+
and starts being a convection tool wearing a general name.
|
|
7
|
+
|
|
8
|
+
Adapted from the substrate ingest in the monorepo, with the heat-transfer fields
|
|
9
|
+
removed and `contribution` deliberately dropped -- see physmap.materiality for why a
|
|
10
|
+
contribution fraction is not a materiality.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
|
|
17
|
+
__all__ = ["CalibrationWindow", "Mechanism"]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@dataclass(frozen=True)
|
|
21
|
+
class CalibrationWindow:
|
|
22
|
+
"""The range of a governing variable over which a mechanism was calibrated.
|
|
23
|
+
|
|
24
|
+
An open end is None, not an infinity: "no upper bound was stated" and "the upper
|
|
25
|
+
bound is very large" are different claims about the evidence.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
variable: str
|
|
29
|
+
low: float | None = None
|
|
30
|
+
high: float | None = None
|
|
31
|
+
|
|
32
|
+
def __post_init__(self) -> None:
|
|
33
|
+
if self.low is not None and self.high is not None and self.low > self.high:
|
|
34
|
+
raise ValueError(
|
|
35
|
+
f"calibration window for {self.variable!r} is inverted: "
|
|
36
|
+
f"low={self.low} > high={self.high}"
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
def contains(self, value: float) -> bool:
|
|
40
|
+
if self.low is not None and value < self.low:
|
|
41
|
+
return False
|
|
42
|
+
if self.high is not None and value > self.high:
|
|
43
|
+
return False
|
|
44
|
+
return True
|
|
45
|
+
|
|
46
|
+
def describe(self) -> str:
|
|
47
|
+
if self.low is None and self.high is None:
|
|
48
|
+
return f"{self.variable} (no stated bound)"
|
|
49
|
+
if self.low is None:
|
|
50
|
+
return f"{self.variable} <= {self.high:g}"
|
|
51
|
+
if self.high is None:
|
|
52
|
+
return f"{self.variable} >= {self.low:g}"
|
|
53
|
+
return f"{self.low:g} <= {self.variable} <= {self.high:g}"
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass(frozen=True)
|
|
57
|
+
class Mechanism:
|
|
58
|
+
"""One mechanism at one operating point."""
|
|
59
|
+
|
|
60
|
+
mechanism_id: str
|
|
61
|
+
name: str
|
|
62
|
+
window: CalibrationWindow
|
|
63
|
+
operating_value: float
|
|
64
|
+
|
|
65
|
+
def in_calibration(self) -> bool:
|
|
66
|
+
return self.window.contains(self.operating_value)
|
|
67
|
+
|
|
68
|
+
def outside_calibration(self) -> bool:
|
|
69
|
+
return not self.in_calibration()
|
physmap/core/signals.py
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Signal kinds. The no-conflation rule, made structural.
|
|
2
|
+
|
|
3
|
+
Four different questions get asked about a prediction, and they rest on different
|
|
4
|
+
evidence. Mixing them up is the failure this project exists to prevent, so a signal
|
|
5
|
+
cannot be constructed without saying which kind it is, and nothing aggregates two
|
|
6
|
+
kinds into a single number.
|
|
7
|
+
|
|
8
|
+
closure_validity Is a closure being used outside its calibrated range?
|
|
9
|
+
observability Can the surrogate's inputs represent the governing variable?
|
|
10
|
+
causal_materiality Is the out-of-range mechanism big enough to matter for the QoI?
|
|
11
|
+
statistical_baseline Does an input-space novelty detector fire?
|
|
12
|
+
|
|
13
|
+
Each carries its own threshold and its own rationale. A `causal_materiality` signal is
|
|
14
|
+
never evidence for an `observability` claim, and the reverse is equally false.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from dataclasses import dataclass
|
|
20
|
+
from enum import Enum
|
|
21
|
+
|
|
22
|
+
__all__ = ["SignalKind", "Signal"]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SignalKind(str, Enum):
|
|
26
|
+
CLOSURE_VALIDITY = "closure_validity"
|
|
27
|
+
OBSERVABILITY = "observability"
|
|
28
|
+
CAUSAL_MATERIALITY = "causal_materiality"
|
|
29
|
+
STATISTICAL_BASELINE = "statistical_baseline"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Signal:
|
|
34
|
+
"""One signal of one kind. `value` and `threshold` are None when the signal could
|
|
35
|
+
not be computed -- which is different from computing it and getting zero."""
|
|
36
|
+
|
|
37
|
+
kind: SignalKind
|
|
38
|
+
fired: bool
|
|
39
|
+
rationale: str
|
|
40
|
+
value: float | None = None
|
|
41
|
+
threshold: float | None = None
|
|
42
|
+
|
|
43
|
+
def __post_init__(self) -> None:
|
|
44
|
+
if not isinstance(self.kind, SignalKind):
|
|
45
|
+
raise TypeError(f"kind must be a SignalKind, got {type(self.kind).__name__}")
|
|
46
|
+
if self.fired and self.value is None:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
f"{self.kind.value} signal claims to have fired with no value. A signal "
|
|
49
|
+
f"that could not be computed must not fire."
|
|
50
|
+
)
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Corpus side of the architecture refactor — calibration table + claim-centric
|
|
2
|
+
evidence corpus.
|
|
3
|
+
|
|
4
|
+
Module renames during R5 (dropping the redundant _corpus suffix):
|
|
5
|
+
calibration_corpus.py -> calibration.py
|
|
6
|
+
evidence_corpus.py -> evidence.py
|
|
7
|
+
|
|
8
|
+
The two corpora live in separate JSONL files under
|
|
9
|
+
`physmap/results/calibration_corpus/` and `physmap/results/evidence_corpus/`;
|
|
10
|
+
they are JOINED at closure_id in downstream consumers (the validity
|
|
11
|
+
detector + the EvidenceEnrichmentStage).
|
|
12
|
+
"""
|