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,543 @@
|
|
|
1
|
+
"""PhysMAP Calibration-Boundary Corpus — schema, validators, loader, CLI.
|
|
2
|
+
|
|
3
|
+
The data asset that makes the causal-layer guardrail real: a geometry-agnostic
|
|
4
|
+
catalog of CFD closure validity envelopes, indexed by `closure_id`, with per-bound
|
|
5
|
+
provenance and a `confirmed/claimed/extrapolated` status that is the asset.
|
|
6
|
+
|
|
7
|
+
Spec: docs/specs/PhysMAP_CalibrationBoundary_Corpus_Spec_v0_1.md
|
|
8
|
+
Storage: a single JSONL file (one closure entry per line, line-diffable).
|
|
9
|
+
This module: schema (dataclasses + controlled vocabs), validators (the 7 QC gates +
|
|
10
|
+
corpus-wide invariants), loader API (load / index_by_id / get_validated_range /
|
|
11
|
+
is_in_calibration), and a CLI (`python -m physmap.corpus.calibration validate`).
|
|
12
|
+
|
|
13
|
+
The integration seam: `Mechanism.in_calibration()` in stage1_ingest.py uses hardcoded
|
|
14
|
+
`calib_lo/hi`. The corpus-backed replacement is `is_in_calibration(corpus_index,
|
|
15
|
+
closure_id, coord, operating_value)`. The swap is mechanical once the corpus has
|
|
16
|
+
Tier 1 coverage — deliberately not done yet.
|
|
17
|
+
|
|
18
|
+
Discipline: correct-and-partial beats complete-and-unverified. A `confirmed` bound
|
|
19
|
+
requires a primary-source citation (gate 7); the validator REJECTS any `confirmed`
|
|
20
|
+
without it. Mass-loading training-knowledge entries as `claimed` is allowed and
|
|
21
|
+
expected; passing them off as `confirmed` is forbidden.
|
|
22
|
+
|
|
23
|
+
Torch-free; depends only on the standard library.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from __future__ import annotations
|
|
27
|
+
|
|
28
|
+
import argparse
|
|
29
|
+
import importlib.resources as _ir
|
|
30
|
+
import importlib.util as _iu
|
|
31
|
+
import json
|
|
32
|
+
import os
|
|
33
|
+
import sys
|
|
34
|
+
from dataclasses import asdict, dataclass, field
|
|
35
|
+
from datetime import date
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
|
|
38
|
+
# ── controlled vocabularies (mirror stage1_ingest.TRUTH_SOURCES pattern) ──────
|
|
39
|
+
|
|
40
|
+
CLOSURE_FAMILIES = {
|
|
41
|
+
# two-phase / boiling
|
|
42
|
+
"flow-boiling", "pool-boiling", "chf-dnb",
|
|
43
|
+
"rpi-sub-closure", "interfacial-force",
|
|
44
|
+
# single-phase
|
|
45
|
+
"single-phase-convection", "rans-turbulence", "wall-function",
|
|
46
|
+
# medical
|
|
47
|
+
"hemolysis",
|
|
48
|
+
# multiphysics
|
|
49
|
+
"cavitation", "condensation",
|
|
50
|
+
"combustion-kinetics", "radiation",
|
|
51
|
+
"non-newtonian-rheology", "porous-media", "species-diffusion",
|
|
52
|
+
# hypersonic / high-speed
|
|
53
|
+
"boundary-layer-transition",
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
BOUND_STATUSES = {"confirmed", "confirmed-contested", "claimed", "extrapolated"}
|
|
57
|
+
|
|
58
|
+
# The open SEED tier collapses every bound to this single status (the firewall
|
|
59
|
+
# strips the confirmed/claimed narration). It is NOT a premium status — the
|
|
60
|
+
# strict validator rejects it unless seed_tier=True is passed (opt-in), so the
|
|
61
|
+
# premium corpus's confirmed-citation discipline stays byte-identical.
|
|
62
|
+
SEED_BOUND_STATUS = "seed"
|
|
63
|
+
|
|
64
|
+
SOLVER_NAMES = {"openfoam", "fluent", "starccm+", "cfx", "none"}
|
|
65
|
+
|
|
66
|
+
# Geometry descriptors that MUST NOT appear in physics_coordinates per gate 6
|
|
67
|
+
# (geometry lives only in regime_context as traceability, never as a coord).
|
|
68
|
+
_FORBIDDEN_COORD_NAMES = {
|
|
69
|
+
"geometry", "shape", "diameter", "length", "channel_width",
|
|
70
|
+
"pipe_diameter", "channel_height", "aspect_ratio_geometry",
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
# Provenance shape requirement: a `confirmed` bound MUST carry both a primary_source
|
|
74
|
+
# flag AND a non-empty citation (gate 7 — handbook restatement ≠ primary confirmation).
|
|
75
|
+
# `provenance` is a dict; the expected keys are documented here, not strict-validated
|
|
76
|
+
# beyond the gate-7 requirement so curators can carry extra context fields.
|
|
77
|
+
_PROVENANCE_PRIMARY_KEY = "primary_source"
|
|
78
|
+
_PROVENANCE_CITATION_KEYS = ("citation", "doi", "page", "table", "figure", "url", "year")
|
|
79
|
+
|
|
80
|
+
# DEFAULT_PATH is the CANONICAL PREMIUM location and DOES NOT EXIST IN THIS
|
|
81
|
+
# REPOSITORY. The premium corpus is the commercial moat and is not published
|
|
82
|
+
# here, so resolution below always falls through to the bundled 15-closure seed
|
|
83
|
+
# unless $PHYSMAP_CORPUS points somewhere else. That is the intended public
|
|
84
|
+
# behaviour, not a misconfiguration. The path is kept so a premium holder can
|
|
85
|
+
# drop the file in and have it picked up with no code change.
|
|
86
|
+
# DEFAULT_PATH is the CANONICAL PREMIUM location — the full moat corpus in the
|
|
87
|
+
# dev/editable tree. It is NOT shipped in the core wheel (the package-data glob
|
|
88
|
+
# excludes results/calibration_corpus). Curation tooling and dev tests operate
|
|
89
|
+
# on this path directly; runtime loading goes through resolve_corpus_path().
|
|
90
|
+
DEFAULT_PATH = Path(__file__).resolve().parent.parent / "results" / "calibration_corpus" / "corpus.jsonl"
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
# ── open-core corpus resolution (env → premium → bundled seed) ─────────────────
|
|
94
|
+
PHYSMAP_CORPUS_ENV = "PHYSMAP_CORPUS"
|
|
95
|
+
_PREMIUM_PACKAGE = "physmap_corpus_premium" # optional private package (full corpus)
|
|
96
|
+
_PREMIUM_FILENAME = "corpus_premium.jsonl"
|
|
97
|
+
_SEED_PACKAGE = "physmap.corpus.data" # bundled in the core wheel
|
|
98
|
+
_SEED_FILENAME = "corpus_seed.jsonl"
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def _seed_path() -> Path:
|
|
102
|
+
"""The bundled, firewalled SEED corpus (always present in the core wheel)."""
|
|
103
|
+
return Path(str(_ir.files(_SEED_PACKAGE) / _SEED_FILENAME))
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
def _premium_package_path() -> Path | None:
|
|
107
|
+
"""Full corpus from the optional `physmap_corpus_premium` package, or None."""
|
|
108
|
+
if _iu.find_spec(_PREMIUM_PACKAGE) is None:
|
|
109
|
+
return None
|
|
110
|
+
try:
|
|
111
|
+
p = Path(str(_ir.files(_PREMIUM_PACKAGE) / _PREMIUM_FILENAME))
|
|
112
|
+
return p if p.is_file() else None
|
|
113
|
+
except (FileNotFoundError, ModuleNotFoundError, NotADirectoryError):
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def resolve_corpus_path() -> Path:
|
|
118
|
+
"""Resolve the active calibration corpus, in precedence order:
|
|
119
|
+
|
|
120
|
+
1. ``$PHYSMAP_CORPUS`` — explicit path override.
|
|
121
|
+
2. Premium if present — the canonical dev/editable file (DEFAULT_PATH), or
|
|
122
|
+
the installed ``physmap_corpus_premium`` package.
|
|
123
|
+
3. The bundled seed — ``physmap/corpus/data/corpus_seed.jsonl``.
|
|
124
|
+
|
|
125
|
+
No network calls, no license checks: tier detection is purely "is the
|
|
126
|
+
premium file present". In an editable/dev checkout step 2 hits DEFAULT_PATH,
|
|
127
|
+
so every existing consumer sees the full corpus unchanged; in a core-only
|
|
128
|
+
wheel install the canonical file is absent and resolution falls to the seed.
|
|
129
|
+
"""
|
|
130
|
+
env = os.environ.get(PHYSMAP_CORPUS_ENV)
|
|
131
|
+
if env:
|
|
132
|
+
return Path(env).expanduser()
|
|
133
|
+
if DEFAULT_PATH.is_file():
|
|
134
|
+
return DEFAULT_PATH
|
|
135
|
+
pkg = _premium_package_path()
|
|
136
|
+
if pkg is not None:
|
|
137
|
+
return pkg
|
|
138
|
+
return _seed_path()
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def active_tier(path: str | Path | None = None) -> str:
|
|
142
|
+
"""Return "seed" or "premium" for the resolved (or given) corpus.
|
|
143
|
+
|
|
144
|
+
Content-based: the seed firewall collapses EVERY bound to status "seed", so
|
|
145
|
+
a corpus whose first entry is all-"seed" is the seed tier. This is correct
|
|
146
|
+
even for an explicit ``$PHYSMAP_CORPUS`` override regardless of filename.
|
|
147
|
+
"""
|
|
148
|
+
p = Path(path) if path is not None else resolve_corpus_path()
|
|
149
|
+
try:
|
|
150
|
+
with open(p, encoding="utf-8") as f:
|
|
151
|
+
for line in f:
|
|
152
|
+
line = line.strip()
|
|
153
|
+
if not line:
|
|
154
|
+
continue
|
|
155
|
+
statuses = {b.get("bound_status")
|
|
156
|
+
for b in json.loads(line).get("validated_range", [])}
|
|
157
|
+
return "seed" if statuses == {SEED_BOUND_STATUS} else "premium"
|
|
158
|
+
except (FileNotFoundError, json.JSONDecodeError):
|
|
159
|
+
pass
|
|
160
|
+
return "premium"
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
# ── schema dataclasses ────────────────────────────────────────────────────────
|
|
164
|
+
|
|
165
|
+
@dataclass
|
|
166
|
+
class CoordinateBound:
|
|
167
|
+
"""One per coordinate of a closure's validity envelope."""
|
|
168
|
+
coord: str # e.g. "mass_flux", "reynolds_number"
|
|
169
|
+
min: float | None # None = unbounded below
|
|
170
|
+
max: float | None # None = unbounded above
|
|
171
|
+
unit: str # SI preferred; "dimensionless" for Re/Pr
|
|
172
|
+
bound_status: str # ∈ BOUND_STATUSES
|
|
173
|
+
provenance: dict # see _PROVENANCE_* constants above
|
|
174
|
+
contested_note: str | None = None # if sources disagree on this coord
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
@dataclass
|
|
178
|
+
class ClosureEntry:
|
|
179
|
+
"""One named closure: validity envelope + provenance + status + scope filter."""
|
|
180
|
+
closure_id: str # immutable slug: family-name-year
|
|
181
|
+
closure_family: str # ∈ CLOSURE_FAMILIES
|
|
182
|
+
closure_name: str # human name
|
|
183
|
+
physics_coordinates: list[str] # ordered list of coord names
|
|
184
|
+
validated_range: list[CoordinateBound] # one per coord; order matches
|
|
185
|
+
fluid_context: list[str] # ["water","R12","blood","air",...]
|
|
186
|
+
regime_context: str | None # traceability only; geometry-OK here
|
|
187
|
+
contested: bool # whole-entry disagreement flag
|
|
188
|
+
solver_availability: list[str] # ⊂ SOLVER_NAMES
|
|
189
|
+
weakener_link: list[str] # UofA weakener-pattern ids
|
|
190
|
+
last_reviewed: str # ISO date YYYY-MM-DD
|
|
191
|
+
reviewer: str # who curated/verified
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
# ── (de)serialization ─────────────────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
def entry_from_dict(d: dict) -> ClosureEntry:
|
|
197
|
+
"""Build a ClosureEntry from a JSON-loaded dict (handles nested CoordinateBound)."""
|
|
198
|
+
bounds_raw = d.get("validated_range", [])
|
|
199
|
+
bounds = [CoordinateBound(**b) for b in bounds_raw]
|
|
200
|
+
payload = {**d, "validated_range": bounds}
|
|
201
|
+
return ClosureEntry(**payload)
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def entry_to_dict(entry: ClosureEntry) -> dict:
|
|
205
|
+
return asdict(entry)
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ── per-entry validation (the 7 QC gates) ─────────────────────────────────────
|
|
209
|
+
|
|
210
|
+
def validate(entry: ClosureEntry, *, seed_tier: bool = False) -> list[str]:
|
|
211
|
+
"""Return a list of human-readable error strings; empty list = entry is valid.
|
|
212
|
+
|
|
213
|
+
Implements the spec's 7 per-entry QC gates plus shape checks.
|
|
214
|
+
|
|
215
|
+
seed_tier=True (opt-in) validates a sanitized SEED file: it additionally
|
|
216
|
+
accepts bound_status "seed". Everything else is unchanged, so premium
|
|
217
|
+
validation is byte-identical to before (gate 7 only ever fires on
|
|
218
|
+
confirmed/confirmed-contested, which the seed never carries).
|
|
219
|
+
"""
|
|
220
|
+
errs: list[str] = []
|
|
221
|
+
allowed_statuses = BOUND_STATUSES | {SEED_BOUND_STATUS} if seed_tier else BOUND_STATUSES
|
|
222
|
+
eid = entry.closure_id or "<no closure_id>"
|
|
223
|
+
|
|
224
|
+
# Shape: closure_id present, immutable slug
|
|
225
|
+
if not entry.closure_id or not isinstance(entry.closure_id, str):
|
|
226
|
+
errs.append(f"{eid}: closure_id missing or not a string")
|
|
227
|
+
elif " " in entry.closure_id or entry.closure_id != entry.closure_id.lower():
|
|
228
|
+
errs.append(f"{eid}: closure_id must be a lowercase slug (no spaces)")
|
|
229
|
+
|
|
230
|
+
# Controlled vocab: family
|
|
231
|
+
if entry.closure_family not in CLOSURE_FAMILIES:
|
|
232
|
+
errs.append(
|
|
233
|
+
f"{eid}: closure_family '{entry.closure_family}' not in CLOSURE_FAMILIES "
|
|
234
|
+
f"({sorted(CLOSURE_FAMILIES)})"
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
# Shape: closure_name present
|
|
238
|
+
if not entry.closure_name:
|
|
239
|
+
errs.append(f"{eid}: closure_name missing")
|
|
240
|
+
|
|
241
|
+
# Shape: physics_coordinates and validated_range length match
|
|
242
|
+
if len(entry.physics_coordinates) != len(entry.validated_range):
|
|
243
|
+
errs.append(
|
|
244
|
+
f"{eid}: physics_coordinates ({len(entry.physics_coordinates)}) and "
|
|
245
|
+
f"validated_range ({len(entry.validated_range)}) length mismatch"
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
# Shape + Gate 6: coord names in validated_range match physics_coordinates list,
|
|
249
|
+
# and no geometry descriptors appear (geometry lives only in regime_context).
|
|
250
|
+
for coord_name in entry.physics_coordinates:
|
|
251
|
+
if coord_name.lower() in _FORBIDDEN_COORD_NAMES:
|
|
252
|
+
errs.append(
|
|
253
|
+
f"{eid}: physics_coordinates contains forbidden geometry descriptor "
|
|
254
|
+
f"'{coord_name}' (gate 6 — geometry lives only in regime_context)"
|
|
255
|
+
)
|
|
256
|
+
coord_names_in_bounds = {b.coord for b in entry.validated_range}
|
|
257
|
+
coord_names_declared = set(entry.physics_coordinates)
|
|
258
|
+
if coord_names_in_bounds != coord_names_declared:
|
|
259
|
+
only_in_bounds = coord_names_in_bounds - coord_names_declared
|
|
260
|
+
only_in_decl = coord_names_declared - coord_names_in_bounds
|
|
261
|
+
errs.append(
|
|
262
|
+
f"{eid}: physics_coordinates vs validated_range.coord mismatch "
|
|
263
|
+
f"(only in bounds: {sorted(only_in_bounds)}; only in declaration: "
|
|
264
|
+
f"{sorted(only_in_decl)})"
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Per-bound gates 1, 2, 3, 7
|
|
268
|
+
for b in entry.validated_range:
|
|
269
|
+
# Gate 1: every bound carries a provenance
|
|
270
|
+
if not isinstance(b.provenance, dict) or not b.provenance:
|
|
271
|
+
errs.append(
|
|
272
|
+
f"{eid}: coord '{b.coord}' missing provenance "
|
|
273
|
+
f"(gate 1 — every bound carries a citation)"
|
|
274
|
+
)
|
|
275
|
+
else:
|
|
276
|
+
# Gate 7: confirmed (and confirmed-contested) bounds require
|
|
277
|
+
# primary_source + a non-handbook citation. confirmed-contested
|
|
278
|
+
# is still a confirmation — source(s) disagree, but the primary-
|
|
279
|
+
# source provenance discipline applies the same way.
|
|
280
|
+
if b.bound_status in ("confirmed", "confirmed-contested"):
|
|
281
|
+
if not b.provenance.get(_PROVENANCE_PRIMARY_KEY):
|
|
282
|
+
errs.append(
|
|
283
|
+
f"{eid}: coord '{b.coord}' is '{b.bound_status}' but provenance "
|
|
284
|
+
f"missing primary_source=true "
|
|
285
|
+
f"(gate 7 — handbook restatement ≠ primary confirmation)"
|
|
286
|
+
)
|
|
287
|
+
has_citation = any(
|
|
288
|
+
b.provenance.get(k) for k in _PROVENANCE_CITATION_KEYS
|
|
289
|
+
)
|
|
290
|
+
if not has_citation:
|
|
291
|
+
errs.append(
|
|
292
|
+
f"{eid}: coord '{b.coord}' is '{b.bound_status}' but provenance "
|
|
293
|
+
f"carries no citation (one of {_PROVENANCE_CITATION_KEYS})"
|
|
294
|
+
)
|
|
295
|
+
|
|
296
|
+
# Gate 2: bound_status ∈ BOUND_STATUSES (claimed default until upgraded);
|
|
297
|
+
# seed_tier additionally allows the collapsed "seed" status.
|
|
298
|
+
if b.bound_status not in allowed_statuses:
|
|
299
|
+
errs.append(
|
|
300
|
+
f"{eid}: coord '{b.coord}' bound_status '{b.bound_status}' not in "
|
|
301
|
+
f"{sorted(allowed_statuses)} (gate 2)"
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
# Gate 3: a contested_note on a bound means contested=True at entry level
|
|
305
|
+
if b.contested_note and not entry.contested:
|
|
306
|
+
errs.append(
|
|
307
|
+
f"{eid}: coord '{b.coord}' has contested_note but entry.contested=False "
|
|
308
|
+
f"(gate 3 — record disagreement, do NOT silently average)"
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
# Shape: unit present
|
|
312
|
+
if not b.unit:
|
|
313
|
+
errs.append(f"{eid}: coord '{b.coord}' missing unit")
|
|
314
|
+
|
|
315
|
+
# Shape: at least one of min/max set
|
|
316
|
+
if b.min is None and b.max is None:
|
|
317
|
+
errs.append(
|
|
318
|
+
f"{eid}: coord '{b.coord}' has both min and max unset "
|
|
319
|
+
f"(at least one bound must be declared)"
|
|
320
|
+
)
|
|
321
|
+
if b.min is not None and b.max is not None and b.min > b.max:
|
|
322
|
+
errs.append(f"{eid}: coord '{b.coord}' min ({b.min}) > max ({b.max})")
|
|
323
|
+
|
|
324
|
+
# Gate 4: solver_availability set; if {"none"} alone, require handbook adoption note
|
|
325
|
+
if not entry.solver_availability:
|
|
326
|
+
errs.append(f"{eid}: solver_availability empty (gate 4 — scope filter)")
|
|
327
|
+
else:
|
|
328
|
+
for s in entry.solver_availability:
|
|
329
|
+
if s not in SOLVER_NAMES:
|
|
330
|
+
errs.append(
|
|
331
|
+
f"{eid}: solver_availability includes '{s}' not in "
|
|
332
|
+
f"{sorted(SOLVER_NAMES)}"
|
|
333
|
+
)
|
|
334
|
+
if entry.solver_availability == ["none"]:
|
|
335
|
+
# Gate 4 requires an explicit handbook-ADOPTION marker, not just any mention
|
|
336
|
+
# of "handbook" — otherwise "no handbook reference here" trivially passes.
|
|
337
|
+
adoption_markers = (
|
|
338
|
+
"handbook-adopted", "handbook adoption",
|
|
339
|
+
"handbook-canonical", "canonical handbook",
|
|
340
|
+
"widely re-cited", "widely cited", "widely adopted",
|
|
341
|
+
)
|
|
342
|
+
ctx = (entry.regime_context or "").lower()
|
|
343
|
+
if not any(marker in ctx for marker in adoption_markers):
|
|
344
|
+
errs.append(
|
|
345
|
+
f"{eid}: solver_availability=['none'] requires an explicit handbook-"
|
|
346
|
+
f"adoption marker in regime_context (one of: "
|
|
347
|
+
f"{', '.join(repr(m) for m in adoption_markers)}); gate 4 — keeps "
|
|
348
|
+
f"the count at thousands not hundreds-of-thousands"
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
# Gate 5: weakener_link populated
|
|
352
|
+
if not entry.weakener_link:
|
|
353
|
+
errs.append(
|
|
354
|
+
f"{eid}: weakener_link empty (gate 5 — bridge to UofA weakener vocabulary)"
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
# Shape: last_reviewed parseable ISO date
|
|
358
|
+
if not entry.last_reviewed:
|
|
359
|
+
errs.append(f"{eid}: last_reviewed missing")
|
|
360
|
+
else:
|
|
361
|
+
try:
|
|
362
|
+
date.fromisoformat(entry.last_reviewed)
|
|
363
|
+
except ValueError as e:
|
|
364
|
+
errs.append(
|
|
365
|
+
f"{eid}: last_reviewed '{entry.last_reviewed}' not parseable ISO date "
|
|
366
|
+
f"({e})"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# Shape: reviewer present
|
|
370
|
+
if not entry.reviewer:
|
|
371
|
+
errs.append(f"{eid}: reviewer missing")
|
|
372
|
+
|
|
373
|
+
return errs
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
# ── corpus-wide invariants ────────────────────────────────────────────────────
|
|
377
|
+
|
|
378
|
+
def validate_corpus(entries: list[ClosureEntry], *, seed_tier: bool = False) -> dict[str, list[str]]:
|
|
379
|
+
"""Per-entry errors keyed by closure_id, plus '__corpus__' for cross-entry issues.
|
|
380
|
+
|
|
381
|
+
Cross-entry checks: unique closure_id. seed_tier is forwarded to validate()."""
|
|
382
|
+
out: dict[str, list[str]] = {}
|
|
383
|
+
for e in entries:
|
|
384
|
+
errs = validate(e, seed_tier=seed_tier)
|
|
385
|
+
if errs:
|
|
386
|
+
out[e.closure_id or "<no closure_id>"] = errs
|
|
387
|
+
|
|
388
|
+
# Unique closure_id across the corpus
|
|
389
|
+
seen: dict[str, int] = {}
|
|
390
|
+
for e in entries:
|
|
391
|
+
seen[e.closure_id] = seen.get(e.closure_id, 0) + 1
|
|
392
|
+
dups = [cid for cid, n in seen.items() if n > 1]
|
|
393
|
+
if dups:
|
|
394
|
+
out.setdefault("__corpus__", []).append(
|
|
395
|
+
f"duplicate closure_id values: {sorted(dups)}"
|
|
396
|
+
)
|
|
397
|
+
|
|
398
|
+
return out
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
# ── loader API (minimal; future-causal-layer-integration-ready) ───────────────
|
|
402
|
+
|
|
403
|
+
def load_corpus(path: str | Path | None = None) -> list[ClosureEntry]:
|
|
404
|
+
"""Parse the JSONL file into a list of ClosureEntry. Raises on parse errors;
|
|
405
|
+
schema validity must be checked separately via validate_corpus().
|
|
406
|
+
|
|
407
|
+
path=None resolves the active corpus via resolve_corpus_path() (env → premium
|
|
408
|
+
→ bundled seed). In a dev/editable tree that is the canonical premium file, so
|
|
409
|
+
behaviour is unchanged; in a core-only install it is the bundled seed."""
|
|
410
|
+
p = Path(path) if path is not None else resolve_corpus_path()
|
|
411
|
+
if not p.exists():
|
|
412
|
+
raise FileNotFoundError(f"corpus file not found: {p}")
|
|
413
|
+
entries: list[ClosureEntry] = []
|
|
414
|
+
with open(p, "r", encoding="utf-8") as f:
|
|
415
|
+
for lineno, line in enumerate(f, start=1):
|
|
416
|
+
stripped = line.strip()
|
|
417
|
+
if not stripped:
|
|
418
|
+
continue
|
|
419
|
+
try:
|
|
420
|
+
d = json.loads(stripped)
|
|
421
|
+
except json.JSONDecodeError as e:
|
|
422
|
+
raise ValueError(f"corpus JSONL parse error at line {lineno}: {e}") from e
|
|
423
|
+
try:
|
|
424
|
+
entries.append(entry_from_dict(d))
|
|
425
|
+
except TypeError as e:
|
|
426
|
+
raise ValueError(
|
|
427
|
+
f"corpus schema mismatch at line {lineno} "
|
|
428
|
+
f"(closure_id={d.get('closure_id', '?')!r}): {e}"
|
|
429
|
+
) from e
|
|
430
|
+
return entries
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def index_by_id(entries: list[ClosureEntry]) -> dict[str, ClosureEntry]:
|
|
434
|
+
return {e.closure_id: e for e in entries}
|
|
435
|
+
|
|
436
|
+
|
|
437
|
+
def get_validated_range(
|
|
438
|
+
entries_index: dict[str, ClosureEntry], closure_id: str, coord: str,
|
|
439
|
+
) -> CoordinateBound | None:
|
|
440
|
+
"""Return the CoordinateBound for (closure_id, coord), or None if unknown."""
|
|
441
|
+
entry = entries_index.get(closure_id)
|
|
442
|
+
if entry is None:
|
|
443
|
+
return None
|
|
444
|
+
for b in entry.validated_range:
|
|
445
|
+
if b.coord == coord:
|
|
446
|
+
return b
|
|
447
|
+
return None
|
|
448
|
+
|
|
449
|
+
|
|
450
|
+
def is_in_calibration(
|
|
451
|
+
entries_index: dict[str, ClosureEntry], closure_id: str, coord: str, value: float,
|
|
452
|
+
) -> bool | None:
|
|
453
|
+
"""Return True/False if the value is inside [min, max] for the (closure_id, coord)
|
|
454
|
+
bound, or None if the closure_id or coord is not in the corpus. None signals the
|
|
455
|
+
caller to fall back to its own bounds (Mechanism.calib_lo/hi today)."""
|
|
456
|
+
b = get_validated_range(entries_index, closure_id, coord)
|
|
457
|
+
if b is None:
|
|
458
|
+
return None
|
|
459
|
+
if b.min is not None and value < b.min:
|
|
460
|
+
return False
|
|
461
|
+
if b.max is not None and value > b.max:
|
|
462
|
+
return False
|
|
463
|
+
return True
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
# ── CLI ───────────────────────────────────────────────────────────────────────
|
|
467
|
+
|
|
468
|
+
def _print_errors(errs_by_id: dict[str, list[str]]) -> int:
|
|
469
|
+
"""Print errors grouped by closure_id; return non-zero exit code if any."""
|
|
470
|
+
if not errs_by_id:
|
|
471
|
+
return 0
|
|
472
|
+
total = sum(len(v) for v in errs_by_id.values())
|
|
473
|
+
print(f"corpus validation FAILED — {total} errors across {len(errs_by_id)} entries:")
|
|
474
|
+
for cid in sorted(errs_by_id):
|
|
475
|
+
print(f" [{cid}]")
|
|
476
|
+
for err in errs_by_id[cid]:
|
|
477
|
+
print(f" • {err}")
|
|
478
|
+
return 1
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def main(argv: list[str] | None = None) -> int:
|
|
482
|
+
parser = argparse.ArgumentParser(
|
|
483
|
+
description="PhysMAP Calibration-Boundary Corpus — validator + summary CLI",
|
|
484
|
+
)
|
|
485
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
486
|
+
|
|
487
|
+
p_val = sub.add_parser("validate", help="run the 7 QC gates + corpus invariants")
|
|
488
|
+
p_val.add_argument("--path", type=Path, default=None,
|
|
489
|
+
help="path to corpus.jsonl (default: the active corpus via "
|
|
490
|
+
"resolve_corpus_path — env → premium → bundled seed)")
|
|
491
|
+
p_val.add_argument("--tier", choices=("auto", "seed", "premium"), default="auto",
|
|
492
|
+
help="validation tier; 'auto' detects 'seed' (all-seed bounds) "
|
|
493
|
+
"and applies the seed-tier gates")
|
|
494
|
+
|
|
495
|
+
p_sum = sub.add_parser("summary", help="print a status summary of the corpus")
|
|
496
|
+
p_sum.add_argument("--path", type=Path, default=None)
|
|
497
|
+
|
|
498
|
+
args = parser.parse_args(argv)
|
|
499
|
+
|
|
500
|
+
if args.cmd == "validate":
|
|
501
|
+
path = args.path or resolve_corpus_path()
|
|
502
|
+
try:
|
|
503
|
+
entries = load_corpus(path)
|
|
504
|
+
except (FileNotFoundError, ValueError) as e:
|
|
505
|
+
print(f"FAILED to load corpus: {e}", file=sys.stderr)
|
|
506
|
+
return 2
|
|
507
|
+
seed_tier = args.tier == "seed" or (args.tier == "auto" and active_tier(path) == "seed")
|
|
508
|
+
errs_by_id = validate_corpus(entries, seed_tier=seed_tier)
|
|
509
|
+
if not errs_by_id:
|
|
510
|
+
tier = "seed" if seed_tier else "premium"
|
|
511
|
+
print(f"corpus validation PASSED — {len(entries)} entries clean "
|
|
512
|
+
f"[{tier}] ({path})")
|
|
513
|
+
# Status summary by bound_status (useful audit signal)
|
|
514
|
+
status_counts: dict[str, int] = {}
|
|
515
|
+
for e in entries:
|
|
516
|
+
for b in e.validated_range:
|
|
517
|
+
status_counts[b.bound_status] = status_counts.get(b.bound_status, 0) + 1
|
|
518
|
+
total_bounds = sum(status_counts.values())
|
|
519
|
+
print(f" total bounds: {total_bounds}")
|
|
520
|
+
display_statuses = sorted(BOUND_STATUSES | ({SEED_BOUND_STATUS} if seed_tier else set()))
|
|
521
|
+
for s in display_statuses:
|
|
522
|
+
print(f" {s:>12s} : {status_counts.get(s, 0)}")
|
|
523
|
+
print(f" contested entries: "
|
|
524
|
+
f"{sum(1 for e in entries if e.contested)}/{len(entries)}")
|
|
525
|
+
return 0
|
|
526
|
+
return _print_errors(errs_by_id)
|
|
527
|
+
|
|
528
|
+
if args.cmd == "summary":
|
|
529
|
+
path = args.path or resolve_corpus_path()
|
|
530
|
+
entries = load_corpus(path)
|
|
531
|
+
family_counts: dict[str, int] = {}
|
|
532
|
+
for e in entries:
|
|
533
|
+
family_counts[e.closure_family] = family_counts.get(e.closure_family, 0) + 1
|
|
534
|
+
print(f"corpus: {len(entries)} entries [{active_tier(path)}] ({path})")
|
|
535
|
+
for fam in sorted(family_counts):
|
|
536
|
+
print(f" {fam:>26s} : {family_counts[fam]}")
|
|
537
|
+
return 0
|
|
538
|
+
|
|
539
|
+
return 0
|
|
540
|
+
|
|
541
|
+
|
|
542
|
+
if __name__ == "__main__":
|
|
543
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Bundled open-tier corpus data (package resources).
|
|
2
|
+
|
|
3
|
+
Holds the sanitized SEED corpus (`corpus_seed.jsonl`) and the premium COVERAGE
|
|
4
|
+
map (`premium_coverage.json`, closure_ids only — no bounds). Both are generated
|
|
5
|
+
by `dev/tools/split_corpus.py` and shipped in the core wheel as package data;
|
|
6
|
+
the loader resolves them via `importlib.resources` when neither an explicit
|
|
7
|
+
`$PHYSMAP_CORPUS` override nor a premium corpus is present.
|
|
8
|
+
|
|
9
|
+
This package carries NO moat content: the seed is firewalled (every bound
|
|
10
|
+
collapsed to status "seed", all confirmation/disposition stripped) and the
|
|
11
|
+
coverage map is ids-only.
|
|
12
|
+
"""
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{"closure_id": "aung-worku-mixed-convection-1986", "closure_family": "single-phase-convection", "closure_name": "Aung-Worku mixed-convection Nusselt correlation (Eq13 family)", "physics_coordinates": ["richardson_number"], "validated_range": [{"coord": "richardson_number", "min": 0.1, "max": 10.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Aung, W. & Worku, G. (1986). 'Theory of fully developed combined convection including flow reversal.' J. Heat Transfer 108:485-488", "doi": "10.1115/1.3246958", "primary_source": false, "note": "mixed-convection regime is typically reported as Ri \u2208 [0.1, 10]; classical 'mixed' regime boundaries"}}], "fluid_context": ["air", "water"], "regime_context": "vertical channel, fully developed mixed forced-natural convection; Eq13 family of correlations used in conv_step3_final validation", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-03", "reviewer": "seed"}
|
|
2
|
+
{"closure_id": "blasius-pohlhausen-flat-plate-forced-1921", "closure_family": "single-phase-convection", "closure_name": "Blasius/Pohlhausen flat-plate laminar forced convection", "physics_coordinates": ["reynolds_number", "prandtl_number", "richardson_number"], "validated_range": [{"coord": "reynolds_number", "min": 0.0, "max": 500000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Pohlhausen, E. (1921). 'Der Waermeaustausch zwischen festen Koerpern und Fluessigkeiten mit kleiner Reibung und kleiner Waermeleitung.' Zeitschrift fuer Angewandte Mathematik und Mechanik 1(2):115-121. Velocity-field foundation: Blasius, H. (1908). 'Grenzschichten in Fluessigkeiten mit kleiner Reibung.' Z. Math. Phys. 56:1-37. Restated in: Bergman, T. L. et al. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 7.2; Schlichting, H. & Gersten, K. (2017). Boundary-Layer Theory (9th ed.), Ch. 9.", "primary_source": true, "year": 1921, "note": "Analytical correlation. Pohlhausen's thermal extension of Blasius's velocity boundary layer. Local Nu_x = 0.332*Re_x^(1/2)*Pr^(1/3); length-averaged Nu_L = 0.664*Re_L^(1/2)*Pr^(1/3). Re upper bound 5e5 is the standard laminar-to-turbulent boundary-layer transition on a smooth isothermal flat plate; above this is transitional/turbulent regime where Blasius/Pohlhausen do not apply."}}, {"coord": "prandtl_number", "min": 0.6, "max": 50.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Bergman, T. L. et al. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 7.2, Eqs. 7.30 and 7.31. Lower bound 0.6 derives from Pohlhausen's asymptotic analysis (assumes Pr = O(1) or larger; for liquid metals with Pr << 1, a separate Pohlhausen low-Pr correlation applies). Upper bound 50 is pragmatic textbook usage.", "primary_source": true, "year": 2011, "page": "Section 7.2", "note": "Lance & Smith vehicle uses air (Pr ~ 0.71), well inside the validated range."}}, {"coord": "richardson_number", "min": null, "max": 0.225, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Sparrow, E.M. & Gregg, J.L. (1959). 'Buoyancy Effects in Forced-Convection Flow and Heat Transfer.' J. Appl. Mech. 26: 133-134", "year": 1959, "discovery_path": {"handbook": "Rohsenow, W.M., Hartnett, J.P. & Cho, Y.I. (eds.) (1998). Handbook of Heat Transfer, 3rd ed. McGraw-Hill. ISBN 0-07-053555-8", "handbook_table": "\u00a74.9 Mixed Convection \u2014 External Flow / Vertical Plates, Eq. 4.158", "handbook_page": "4.75 (PDF p.275)"}, "note": "Buoyancy-validity envelope of the forced-convection flat-plate result: the pure forced-convection Nusselt number stays within 5% of the combined value only while the Richardson number Gr/Re^2 remains at or below 0.225; beyond that, assisting or opposing buoyancy must be accounted for and a mixed-convection correlation used. Per Sparrow & Gregg's perturbation analysis, valid for Prandtl 0.01 to 10. Gr/Re^2 is the Richardson number for mixed convection."}}], "fluid_context": ["air", "water", "fluid-Pr-ge-0.6"], "regime_context": "external flow over an isothermal flat plate, laminar boundary layer (Re_x <= 5e5); handbook-canonical analytical correlation widely cited across all major heat-transfer textbooks (Bergman, Incropera, Schlichting, Kays & Crawford)", "contested": false, "solver_availability": ["none"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-05", "reviewer": "seed"}
|
|
3
|
+
{"closure_id": "dittus-boelter-1930", "closure_family": "single-phase-convection", "closure_name": "Dittus-Boelter forced-convection Nusselt", "physics_coordinates": ["reynolds_number", "prandtl_number"], "validated_range": [{"coord": "reynolds_number", "min": 10000.0, "max": 1200000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Dittus, F.W. & Boelter, L.M.K. (1930). 'Heat transfer in automobile radiators of tubular type.' Univ. California Publ. Engr. 2(13):443-461", "primary_source": false, "note": "Nu = 0.023 Re^0.8 Pr^n; the canonical single-phase forced-convection correlation, ranges per handbook restatement"}}, {"coord": "prandtl_number", "min": 0.7, "max": 160.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Dittus-Boelter 1930", "primary_source": false, "note": "Pr range per handbook restatement; primary-source verification pending"}}], "fluid_context": ["water", "air", "oils"], "regime_context": "fully developed turbulent flow in smooth tubes; small temperature differences (no property-variation correction)", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-03", "reviewer": "seed"}
|
|
4
|
+
{"closure_id": "dittus-boelter-buoyancy-sco2", "closure_family": "single-phase-convection", "closure_name": "Constant-property Dittus-Boelter (supercritical CO2, buoyancy-bounded)", "physics_coordinates": ["reynolds_number", "prandtl_number", "liu_buoyancy_parameter"], "validated_range": [{"coord": "reynolds_number", "min": 10000.0, "max": 1200000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Dittus-Boelter (McAdams form); Re turbulent window per dittus-boelter-1930.", "primary_source": false, "note": "Same Re window as dittus-boelter-1930; broad sCO2 turbulent envelope, NOT the discriminator (Jin Re_b ~3.5e4-9.3e4 is interior)."}}, {"coord": "prandtl_number", "min": 0.5, "max": 2000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Broad sCO2 turbulent envelope (Pr spikes near the pseudo-critical point).", "primary_source": false, "note": "Broad on purpose so the buoyancy coordinate is this regime-specific entry's discriminator (mirrors gnielinski-constprop-sco2); Jin Pr_b ~2.5-16 is interior."}}, {"coord": "liu_buoyancy_parameter", "min": 0.0, "max": 1.3e-05, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Liu et al. 2017 buoyancy parameter Bu; Jin et al. 2023 (Annals of Nuclear Energy 188:109825) Eq. 21 + buoyancy criteria (deterioration onset near Bu = 1.3e-5). Banked evidence Claim jin-2023-correct-dittus-boelter-buoyancy-bound. Set A PRIORI from the paper's stated HTD-onset threshold, NOT tuned to data.", "primary_source": false, "note": "Constant-property Dittus-Boelter validity bound for BUOYANCY. Fires (signal>0) when Bu exceeds 1.3e-5 (buoyancy-significant / heat-transfer-deterioration regime). Bu (Liu, wall-aware) is a DISTINCT physical group from richardson_number (Gr/Re^2) \u2014 the same-name trap is avoided by this dedicated coordinate."}}], "fluid_context": ["supercritical CO2"], "regime_context": "Constant-property turbulent pipe Nu (Dittus-Boelter) applied to supercritical CO2 in a VERTICAL heated tube; valid when buoyancy is negligible (Liu Bu < 1.3e-5), INVALID once buoyancy is significant (upward flow -> heat-transfer deterioration; downward -> enhancement) which the direction-blind bare correlation cannot predict. Jin et al. 2023.", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-10", "reviewer": "seed"}
|
|
5
|
+
{"closure_id": "gnielinski-1976", "closure_family": "single-phase-convection", "closure_name": "Gnielinski forced-convection Nusselt", "physics_coordinates": ["reynolds_number", "prandtl_number", "x_over_D", "prandtl_ratio_bulk_wall", "temperature_ratio_bulk_wall"], "validated_range": [{"coord": "reynolds_number", "min": 3000.0, "max": 5000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Gnielinski, V. (1976). 'New equations for heat and mass transfer in turbulent pipe and channel flow.' Int. Chem. Engr. 16:359-368", "primary_source": true, "note": "Gnielinski's modified Petukhov correlation; transition + turbulent; ranges per handbook restatement"}}, {"coord": "prandtl_number", "min": 0.5, "max": 2000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Gnielinski 1976 ICE", "primary_source": true, "note": "wider Pr range than Dittus-Boelter; primary-source verification pending"}}, {"coord": "x_over_D", "min": 10.0, "max": 1000000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Bergman, T. L., Lavine, A. S., Incropera, F. P., DeWitt, D. P. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 8.5, p. 519. Cites the L/D > 10 criterion for fully-developed turbulent flow in circular tubes, applicable to Gnielinski-style correlations. The criterion is textbook-stable: Kays & Crawford 'Convective Heat and Mass Transfer' 4th ed. Ch. 14 gives the same L/D > 10 for thermal entry length in turbulent gas flow.", "primary_source": false, "year": 2011, "note": "L/D > 10 is a handbook usage guideline for the fully-developed turbulent flow assumption underlying Gnielinski 1976. Status 'claimed' (not 'confirmed') because the cutoff is a handbook judgment about when the Hausen entrance-correction factor [1 + (D/L)^(2/3)] becomes negligible (< 5% effect at L/D > 10 for gases). Hausen 1943 gave the correction; the L/D > 10 threshold is the handbook's interpretation of 'negligibly small entrance effect.' For high-Pr liquids the thermal entry length is longer (handbook quote L/D > 60 for water); this bound uses 10 as the lower-end conservative cutoff appropriate for gas-flow vehicles like NACA TN-1451 (air, Pr~0.71)."}}, {"coord": "prandtl_ratio_bulk_wall", "min": 0.1, "max": 10.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Hufschmidt, W. & Burck, E. (1968). 'Einfluss temperaturabhaengiger Stoffwerte auf den Waermeuebergang bei turbulenter Stroemung von Fluessigkeiten in Rohren bei hohen Waermestromdichten und Prandtlzahlen.' Int. J. Heat Mass Transfer 11:1041-1048 -- origin of the (Pr/Pr_w)^0.11 factor, applied here to the turbulent Gnielinski 1976 correlation; validity window stated in VDI G1 sec.4.4.", "year": 1968, "discovery_path": {"handbook": "VDI Heat Atlas (2nd English ed., Springer-Verlag Berlin Heidelberg, 2010; ed. VDI-GVC)", "handbook_table": "G1 'Heat Transfer in Pipe Flow' (V. Gnielinski), sec.4.4 'Effect of Temperature Dependent Property Variations', Eq.(40)", "handbook_page": "697 (PDF p720)"}}}, {"coord": "temperature_ratio_bulk_wall", "min": 0.5, "max": 1.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Gnielinski, V. (1976). 'New equations for heat and mass transfer in turbulent pipe and channel flow.' Int. Chem. Engr. 16:359-368 -- fitted the gas correction exponent n=0.45 over the heating range 0.5<T/Tw<1; restated in VDI G1 sec.4.4.", "year": 1976, "discovery_path": {"handbook": "VDI Heat Atlas (2nd English ed., Springer-Verlag Berlin Heidelberg, 2010; ed. VDI-GVC)", "handbook_table": "G1 'Heat Transfer in Pipe Flow' (V. Gnielinski), sec.4.4 'Effect of Temperature Dependent Property Variations', Eq.(41)", "handbook_page": "697 (PDF p720)"}}}], "fluid_context": ["water", "air", "oils", "molten salts"], "regime_context": "transition + fully turbulent pipe/channel flow; smooth walls; fully-developed thermal profile (x/D > 10 lower bound). For x/D < 10 (entrance region) the Hausen correction factor [1 + (D/L)^(2/3)] is required; without it the correlation under-predicts Nu by factor 1.2-2.0 in the developing region.", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-05", "reviewer": "seed"}
|
|
6
|
+
{"closure_id": "gnielinski-constprop-sco2", "closure_family": "single-phase-convection", "closure_name": "Constant-property Gnielinski (supercritical CO2, property-variation-bounded)", "physics_coordinates": ["reynolds_number", "prandtl_number", "viscosity_ratio_wall_bulk"], "validated_range": [{"coord": "reynolds_number", "min": 3000.0, "max": 5000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Gnielinski 1976 ICE; Re range per Bergman/Incropera sec 8.5", "primary_source": false, "note": "Same Re validity window as gnielinski-1976 (constant-property turbulent pipe); claimed here for this regime-specific entry."}}, {"coord": "prandtl_number", "min": 0.5, "max": 2000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Gnielinski 1976 ICE", "primary_source": false, "note": "Same Pr validity window as gnielinski-1976."}}, {"coord": "viscosity_ratio_wall_bulk", "min": 0.9, "max": 1.1, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Kays, W.M. & Crawford, M.E., 'Convective Heat and Mass Transfer' (property-ratio method). Constant-property correlations (Gnielinski/Dittus-Boelter) omit the (mu_b/mu_w)^n property correction and are stated for SMALL property variation. Constant-property validity is conventionally taken for viscosity-ratio departures within ~+/-10% of unity (mu_w/mu_b in [0.9, 1.1]); beyond that (e.g. near the supercritical pseudo-critical point) the property-ratio/reference-temperature correction is required and the bare correlation is out of validity. Status 'claimed' (handbook judgment, like the gnielinski-1976 x/D>10 bound), set A PRIORI from the property-ratio-method threshold, NOT tuned to the Velazquez data.", "primary_source": false, "note": "Property-variation validity bound for the constant-property assumption. Fires (signal>0) when mu_w/mu_b leaves [0.9,1.1], i.e. strong property variation near pseudo-critical."}}], "fluid_context": ["supercritical CO2"], "regime_context": "Constant-property turbulent pipe Nu applied to supercritical CO2; valid for small property variation (viscosity ratio near unity), INVALID near the pseudo-critical point where rho/mu/cp/lambda vary sharply and the constant-property assumption breaks (heat-transfer deterioration/enhancement the bare correlation cannot predict).", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-07", "reviewer": "seed"}
|
|
7
|
+
{"closure_id": "laminar-fully-developed-tube-isothermal-nu366", "closure_family": "single-phase-convection", "closure_name": "Laminar fully-developed circular-tube Nusselt, uniform surface temperature (Nu=3.66, analytical)", "physics_coordinates": ["reynolds_number"], "validated_range": [{"coord": "reynolds_number", "min": null, "max": 2300.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Shah, R.K. & London, A.L. (1978). Laminar Flow Forced Convection in Ducts (Supplement 1 to Advances in Heat Transfer). Academic Press, New York", "year": 1978, "page": "circular-tube fully-developed Nusselt numbers (Nu_T=3.657 const-T; Nu_H1=4.364 const-q)", "discovery_path": {"handbook": "Incropera, F.P., DeWitt, D.P., Bergman, T.L. & Lavine, A.S. (2007). Fundamentals of Heat and Mass Transfer, 6th ed. Wiley", "handbook_table": "Eq. 8.55 (Nu_D=3.66) in Table 8.4 Summary of Convection Correlations for Flow in a Circular Tube; laminar limit Re_D,c~2300 from Eq. 8.2", "handbook_page": "p.532 (Table 8.4); p.487 (Eq. 8.2 transition Reynolds)"}, "note": "Laminar-regime validity limit for the analytical fully-developed constant-Nusselt result Nu_D=3.66 (uniform/constant surface temperature) in a circular tube: it applies while the tube Reynolds number stays laminar, up to the transition-onset value near 2300. The 3.66 is the fully-developed analytical asymptote."}}], "fluid_context": ["any single-phase fluid"], "regime_context": "laminar, hydrodynamically and thermally fully-developed flow in a circular tube held at a uniform (constant) surface temperature; the analytical fully-developed Nusselt number is the constant 3.66; valid below the laminar-to-turbulent transition (critical Reynolds ~2300)", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure", "closure-set-mismatch"], "last_reviewed": "2026-06-08", "reviewer": "seed"}
|
|
8
|
+
{"closure_id": "laminar-fully-developed-tube-uniform-flux-nu436", "closure_family": "single-phase-convection", "closure_name": "Laminar fully-developed circular-tube Nusselt, uniform surface heat flux (Nu=4.36, analytical)", "physics_coordinates": ["reynolds_number"], "validated_range": [{"coord": "reynolds_number", "min": null, "max": 2300.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Shah, R.K. & London, A.L. (1978). Laminar Flow Forced Convection in Ducts (Supplement 1 to Advances in Heat Transfer). Academic Press, New York", "year": 1978, "page": "circular-tube fully-developed Nusselt numbers (Nu_T=3.657 const-T; Nu_H1=4.364 const-q)", "discovery_path": {"handbook": "Incropera, F.P., DeWitt, D.P., Bergman, T.L. & Lavine, A.S. (2007). Fundamentals of Heat and Mass Transfer, 6th ed. Wiley", "handbook_table": "Eq. 8.53 (Nu_D=4.36) in Table 8.4 Summary of Convection Correlations for Flow in a Circular Tube; laminar limit Re_D,c~2300 from Eq. 8.2", "handbook_page": "p.532 (Table 8.4); p.487 (Eq. 8.2 transition Reynolds)"}, "note": "Laminar-regime validity limit for the analytical fully-developed constant-Nusselt result Nu_D=4.36 (uniform/constant surface heat flux) in a circular tube: it applies while the tube Reynolds number stays laminar, up to the transition-onset value near 2300. The 4.36 is the fully-developed analytical asymptote."}}], "fluid_context": ["any single-phase fluid"], "regime_context": "laminar, hydrodynamically and thermally fully-developed flow in a circular tube with a uniform (constant) surface heat flux; the analytical fully-developed Nusselt number is the constant 4.36; valid below the laminar-to-turbulent transition (critical Reynolds ~2300)", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure", "closure-set-mismatch"], "last_reviewed": "2026-06-08", "reviewer": "seed"}
|
|
9
|
+
{"closure_id": "marineau-entropy-layer-shock-interaction-2014", "closure_family": "boundary-layer-transition", "closure_name": "Marineau entropy-layer / shock-interaction hypersonic transition validity boundary", "physics_coordinates": ["entropy_layer_shock_ratio"], "validated_range": [{"coord": "entropy_layer_shock_ratio", "min": 0.1, "max": 1000000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Marineau, E.C. et al. (2014), 'Investigation of Mach 10 Boundary-Layer Transition ...', SAND2014-4326C / AIAA; Table 3 'Transition Parameters at 0-deg AoA' (S_T/X_SW).", "primary_source": true, "note": "S_T/X_SW >= 0.1 delineates the e^N / 2nd-mode regime (benign); large-bluntness deploy runs fall to S_T/X_SW < 0.1 (entropy-layer dominated, 2nd mode absent) -> OUTSIDE (below). max=1e9 keeps the benign large-S_T/X_SW rows (up to ~113) inside."}}], "fluid_context": ["air"], "regime_context": "hypersonic boundary-layer transition on a blunt cone (Mach~10); entropy-layer/shock-interaction (bluntness)-driven; the e^N/2nd-mode framework is widely cited; transcribed directly from the primary source (Marineau Table 3)", "contested": false, "solver_availability": ["none"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-07", "reviewer": "seed"}
|
|
10
|
+
{"closure_id": "mcadams-vertical-plate-natural-1954", "closure_family": "single-phase-convection", "closure_name": "McAdams vertical-plate laminar natural convection", "physics_coordinates": ["rayleigh_number", "prandtl_number"], "validated_range": [{"coord": "rayleigh_number", "min": 10000.0, "max": 1000000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "McAdams, W. H. (1954). 'Heat Transmission' (3rd ed.). McGraw-Hill, New York, Ch. 7. Restated in: Bergman, T. L. et al. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 9.6, Eq. 9.27; Churchill, S. W. and Chu, H. H. S. (1975). 'Correlating equations for laminar and turbulent free convection from a vertical plate.' Int. J. Heat Mass Transfer 18(11):1323-1329 (the refined Churchill-Chu form reduces to 0.59*Ra^(1/4) in the laminar limit).", "primary_source": true, "year": 1954, "page": "Ch. 7", "note": "Simple form Nu_L = 0.59 * Ra_L^(1/4) for laminar natural convection from an isothermal vertical heated plate. Ra upper bound 1e9 is the standard laminar-to-turbulent transition for vertical plate natural convection; above this the turbulent form (Nu ~ Ra^(1/3)) applies."}}, {"coord": "prandtl_number", "min": 0.6, "max": 7.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Bergman, T. L. et al. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 9.6. The simple McAdams form 0.59*Ra^(1/4) is calibrated for moderate-Pr fluids (air ~0.71, water ~7); Churchill-Chu 1975 provides an explicit Pr correction (1 + (0.492/Pr)^(9/16))^(-4/9) that extends to liquid metals (Pr<<1) and other fluids. The simple form is conservatively stated for Pr in roughly [0.6, 7] in textbook restatements; for wider Pr, the Churchill-Chu corrected form is required.", "primary_source": true, "year": 2011, "page": "Section 9.6", "note": "Air at Pr=0.71 (Lance & Smith's fluid) sits well inside the validated range. The Pr range stated here is for the simple McAdams form, not the more general Churchill-Chu corrected form."}}], "fluid_context": ["air", "water", "fluid-Pr-ge-0.6-le-7"], "regime_context": "external natural convection from an isothermal vertical heated plate, laminar regime (Ra_L <= 1e9); handbook-canonical correlation widely cited across all major heat-transfer textbooks (McAdams, Bergman, Incropera, Churchill-Chu)", "contested": false, "solver_availability": ["none"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-05", "reviewer": "seed"}
|
|
11
|
+
{"closure_id": "modified-sparrow-cur-asym-narrow-rect-channel-2014", "closure_family": "single-phase-convection", "closure_name": "Modified Sparrow-Cur asymmetrically-heated narrow rectangular channel Nusselt", "physics_coordinates": ["reynolds_number", "prandtl_number"], "validated_range": [{"coord": "reynolds_number", "min": 10000.0, "max": 70000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Original: Sparrow, E. M. & Cur, N. (1982). 'Turbulent Heat Transfer in a Symmetrically or Asymmetrically Heated Flat Rectangular Duct With Flow Separation at Inlet.' Journal of Heat Transfer 104:82-89. Modified form: Forrest, E. C., Hu, L.-W., Buongiorno, J., & McKrell, T. J. (2014). 'Convective Heat Transfer in a High Aspect Ratio Mini-Channel Heated on One Side.' Journal of Heat Transfer 138(2):021704, Eq. (7). DOI: 10.1115/1.4031646. SAND2014-18834J / OSTI 1295764.", "primary_source": true, "year": 2014, "note": "Modified form: Nu = 0.036 Re^0.76 Pr^(1/3). Original Sparrow-Cur (1982): Nu = 0.0464 Re^0.76 at fixed Pr=2.5, isothermal wall, alpha*=0.0556, Re 10000-45000. Forrest 2014 modification: (a) Chilton-Colburn analogy applied to extend Pr to 2.2-5.4 via explicit Pr^(1/3) term; (b) coefficient adjusted from 0.0464 to 0.036 consistent with Pr^(1/3)|_{Pr=2.5}; (c) applied to uniform-heat-flux wall BC (vs original isothermal); (d) Re range extended to 70,000 on Forrest's data. GEOMETRY-MATCHED to one-sided-heated narrow rectangular mini-channels with high aspect ratio (alpha* < 0.1); the only standard corpus closure with explicit one-sided-heating geometry dependence. Forrest 2014 Table 4 reports MAE 6.1% for this Modified form over Re 10000-70000 at Pr 2.2-5.4 -- LOWER than every other circular-pipe closure evaluated on the same one-sided narrow-rect geometry: Dittus-Boelter 6.4%, Gnielinski 8.4%, Petukhov 11.4%, Sieder-Tate 15.2%. The modified form is the most accurate published closure for this geometry."}}, {"coord": "prandtl_number", "min": 2.2, "max": 5.4, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Forrest et al. 2014 J. Heat Transfer 138(2):021704 Table 4 and Eq. (7) (Pr 2.2-5.4 tested range, water at 30-80 degC). Modification base: Sparrow & Cur 1982 J. Heat Transfer 104:82-89 (original Pr=2.5 single value, mass-transfer experiment converted to heat transfer via Chilton-Colburn analogy).", "primary_source": true, "year": 2014, "note": "Pr 2.2-5.4 is Forrest 2014's tested range, corresponding to water at bulk temperatures 30-80 degC. Original Sparrow-Cur was at Pr=2.5 only (single value). The Pr^(1/3) term in the Modified form is a Forrest-2014 Chilton-Colburn-analogy modification, not a directly validated Pr dependence in the original Sparrow-Cur experiments."}}], "fluid_context": ["water", "Pr-2-to-6"], "regime_context": "Fully developed turbulent flow in one-sided heated narrow rectangular mini-channels (high aspect ratio, alpha* < 0.1), uniform heat flux on one wall and insulated/adiabatic other walls; characteristic length = hydraulic diameter Dhyd. Modified form by Forrest 2014 extends original Sparrow & Cur 1982 (fixed Pr=2.5 isothermal mass-transfer with Chilton-Colburn) to variable Pr uniform-heat-flux via explicit Pr^(1/3) factor. Geometry-matched closure for the Forrest mini-channel substrate. Widely re-cited in narrow-channel heat-transfer validation literature for asymmetric one-sided heating (e.g., Forrest et al. 2016 J. Nucl. Eng. & Rad. Sci. on surface-oxidation effects; Tano & Marshall 2022 INL RELAP5-3D MITR/NBSR safety analyses) -- the standard closure choice for one-sided heated narrow rectangular channels in MTR fuel-channel safety analyses.", "contested": false, "solver_availability": ["none"], "weakener_link": ["extrapolated-closure", "geometry-mismatch-risk"], "last_reviewed": "2026-06-05", "reviewer": "seed"}
|
|
12
|
+
{"closure_id": "pate-stainback-freestream-noise-hypersonic-1980", "closure_family": "boundary-layer-transition", "closure_name": "Pate-Stainback freestream-noise hypersonic transition validity boundary", "physics_coordinates": ["freestream_noise_rms_pitot_pct"], "validated_range": [{"coord": "freestream_noise_rms_pitot_pct", "min": 0.5, "max": 10.0, "unit": "percent", "bound_status": "seed", "provenance": {"citation": "Pate, S.R. & Stainback, P.C. \u2014 freestream-disturbance effects on boundary-layer transition in hypersonic wind tunnels (conventional-tunnel transition correlations validated for RMS-Pitot >= ~0.5%; flight/quiet ~0.05-0.1%). Casper et al. quiet-vs-noisy BAM6QT/HWT data.", "primary_source": false, "note": "0.5% RMS-Pitot is the conventionally-cited lower edge of the noisy-tunnel regime the transition correlation was built on; the flight-like quiet regime (~0.05%) is OUTSIDE it (below). max=10% keeps the noisy training (1.0-3.7%) inside -> do-no-harm."}}], "fluid_context": ["air"], "regime_context": "hypersonic boundary-layer transition on a sharp cone; disturbance(freestream-noise)-driven; Pate-Stainback freestream-noise correlation is widely cited in the hypersonic-transition literature", "contested": false, "solver_availability": ["none"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-07", "reviewer": "seed"}
|
|
13
|
+
{"closure_id": "petukhov-1970", "closure_family": "single-phase-convection", "closure_name": "Petukhov forced-convection Nusselt", "physics_coordinates": ["reynolds_number", "prandtl_number"], "validated_range": [{"coord": "reynolds_number", "min": 10000.0, "max": 5000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Petukhov, B.S. (1970). 'Heat Transfer and Friction in Turbulent Pipe Flow with Variable Physical Properties.' Advances in Heat Transfer 6:503-564. Re range 10000-5e6 per Bergman, T. L., Lavine, A. S., Incropera, F. P., DeWitt, D. P. (2011). Fundamentals of Heat and Mass Transfer (7th ed.), Section 8.5 restatement of Petukhov's stated validity range.", "primary_source": false, "year": 1970, "note": "High-accuracy turbulent-pipe correlation; superseded for transition coverage by Gnielinski 1976. Status 'claimed' (not 'confirmed') because the primary paper (Petukhov 1970 in Advances in Heat Transfer 6:503-564) has not been directly verified; bounds are cited via the Bergman/Incropera 2011 restatement chain."}}, {"coord": "prandtl_number", "min": 0.5, "max": 2000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Petukhov, B.S. (1970). Advances in Heat Transfer 6:503-564. Pr range 0.5-2000 per Bergman/Incropera 2011 sec. 8.5 restatement.", "primary_source": false, "year": 1970, "note": "Same Pr range as Gnielinski 1976 extended-coverage form."}}], "fluid_context": ["water", "air", "oils"], "regime_context": "fully developed turbulent flow in smooth tubes; high-accuracy correlation form using the friction factor as a free parameter; superseded for transition (Re 2300-10000) coverage by Gnielinski 1976.", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-05", "reviewer": "seed"}
|
|
14
|
+
{"closure_id": "sieder-tate-1936", "closure_family": "single-phase-convection", "closure_name": "Sieder-Tate forced-convection with property-variation correction", "physics_coordinates": ["reynolds_number", "prandtl_number"], "validated_range": [{"coord": "reynolds_number", "min": 10000.0, "max": 1000000.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Sieder, E.N. & Tate, G.E. (1936). 'Heat transfer and pressure drop of liquids in tubes.' Ind. Eng. Chem. 28(12):1429-1435", "doi": "10.1021/ie50324a027", "primary_source": false, "note": "Sieder-Tate adds (\u03bc/\u03bc_w)^0.14 viscosity-ratio correction; ranges per handbook restatement"}}, {"coord": "prandtl_number", "min": 0.7, "max": 16700.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"citation": "Sieder-Tate 1936 IEC", "doi": "10.1021/ie50324a027", "primary_source": false, "note": "much wider Pr range than Dittus-Boelter due to viscosity correction; primary-source verification pending"}}], "fluid_context": ["water", "oils", "high-viscosity liquids"], "regime_context": "turbulent flow in tubes with significant property variation (large wall-bulk temperature difference)", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure"], "last_reviewed": "2026-06-03", "reviewer": "seed"}
|
|
15
|
+
{"closure_id": "sieder-tate-laminar-combined-entry-1936", "closure_family": "single-phase-convection", "closure_name": "Sieder-Tate laminar combined-entry-length Nusselt (property-variation correction)", "physics_coordinates": ["prandtl_number", "viscosity_ratio_wall_bulk"], "validated_range": [{"coord": "prandtl_number", "min": 0.6, "max": 5.0, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Sieder, E.N. & Tate, G.E. (1936). 'Heat transfer and pressure drop of liquids in tubes.' Ind. Eng. Chem. 28(12):1429-1435", "doi": "10.1021/ie50324a027", "year": 1936, "discovery_path": {"handbook": "Incropera, F.P., DeWitt, D.P., Bergman, T.L. & Lavine, A.S. (2007). Fundamentals of Heat and Mass Transfer, 6th ed. Wiley", "handbook_table": "Eq. 8.57, \u00a78.4.2 The Entry Region (laminar combined-entry-length correlation)", "handbook_page": 513}, "note": "Prandtl applicability of the laminar combined-entry Sieder-Tate correlation as restated by Incropera."}}, {"coord": "viscosity_ratio_wall_bulk", "min": 0.0044, "max": 9.75, "unit": "dimensionless", "bound_status": "seed", "provenance": {"primary_source": false, "citation": "Sieder, E.N. & Tate, G.E. (1936). 'Heat transfer and pressure drop of liquids in tubes.' Ind. Eng. Chem. 28(12):1429-1435", "doi": "10.1021/ie50324a027", "year": 1936, "discovery_path": {"handbook": "Incropera, F.P., DeWitt, D.P., Bergman, T.L. & Lavine, A.S. (2007). Fundamentals of Heat and Mass Transfer, 6th ed. Wiley", "handbook_table": "Eq. 8.57, \u00a78.4.2 The Entry Region (laminar combined-entry-length Sieder-Tate)", "handbook_page": 513}, "note": "Validity envelope of the Sieder-Tate viscosity-ratio correction for the laminar combined-entry tube correlation: wall-to-bulk viscosity ratio spanning 0.0044 up to 9.75, with Prandtl between 0.60 and 5; wall viscosity is taken at the surface temperature and the remaining properties at the mean."}}], "fluid_context": ["oils", "water", "liquids with large property variation"], "regime_context": "laminar combined thermal+velocity entry length in a circular tube; large wall-to-bulk property variation handled by the Sieder-Tate viscosity-ratio correction", "contested": false, "solver_availability": ["openfoam", "fluent", "starccm+", "cfx"], "weakener_link": ["extrapolated-closure", "closure-set-mismatch"], "last_reviewed": "2026-06-07", "reviewer": "seed"}
|