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
physmap/cli.py ADDED
@@ -0,0 +1,301 @@
1
+ """The `physmap` console script.
2
+
3
+ The set of subcommands is fixed by `physmap.release.CURRENT_RELEASE_STATE`. It is not
4
+ computed from the filesystem: a command never appears because a data directory happens
5
+ to be present. In particular `reproduce` does not exist in a preview build at all.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import argparse
11
+ import sys
12
+
13
+ from physmap.release import CURRENT_RELEASE_STATE, has_reproduce_command
14
+
15
+
16
+ def build_parser() -> argparse.ArgumentParser:
17
+ p = argparse.ArgumentParser(
18
+ prog="physmap",
19
+ description=(
20
+ "Physics-aware credibility checks for AI surrogates. "
21
+ f"Release state: {CURRENT_RELEASE_STATE.value}."
22
+ ),
23
+ )
24
+ p.add_argument("--version", action="store_true", help="print version and release state")
25
+ sub = p.add_subparsers(dest="command")
26
+
27
+ screen = sub.add_parser("screen", help="run the cheap applicability screen for a case")
28
+ screen.add_argument("case", nargs="?", help="case id; omit with --list")
29
+ screen.add_argument("--qoi", help="quantity of interest (checked against the case)")
30
+ screen.add_argument("--list", action="store_true", help="list the available cases")
31
+
32
+ explain = sub.add_parser(
33
+ "explain", help="deterministically explain one benchmark cell or screening case")
34
+ explain.add_argument("subject", nargs="?", help="a benchmark vehicle id, or a screening case id")
35
+ explain.add_argument("--list", action="store_true", help="list what can be explained")
36
+
37
+ bench = sub.add_parser(
38
+ "benchmark",
39
+ help="report the seven-vehicle closure-observability benchmark, and rerun the "
40
+ "subset whose source data is redistributable",
41
+ )
42
+ bench_sub = bench.add_subparsers(dest="action", required=True)
43
+ bench_run = bench_sub.add_parser(
44
+ "run", help="recompute the rerunnable vehicles (a SUBSET of the seven)")
45
+ bench_run.add_argument("--report", action="store_true",
46
+ help="print the full seven-vehicle report afterwards")
47
+ bench_sub.add_parser(
48
+ "report", help="print all seven outcomes, marking which are recomputed here")
49
+ bench_sub.add_parser(
50
+ "coverage", help="print what the rerunnable subset does and does not cover")
51
+
52
+ # A stress test is a controlled demonstration of the CAUSAL path. It is deliberately not a
53
+ # `benchmark` action: that command measures closure validity and observability, and a
54
+ # materiality result must never appear under its name.
55
+ stress = sub.add_parser(
56
+ "stress-test",
57
+ help="run a controlled stress test of the causal path -- a development demonstration, "
58
+ "not a benchmark and not a performance claim")
59
+ stress.add_argument("test_id", nargs="?", help="stress test id; omit with --list")
60
+ stress.add_argument("--list", action="store_true", help="list the available stress tests")
61
+ stress.add_argument("--json", metavar="PATH",
62
+ help="also write the full fresh record to PATH (never the committed bank)")
63
+
64
+ # `reproduce` is added only in a release whose benchmark has cleared the readiness
65
+ # gate. In a preview build the command does not exist -- `physmap reproduce` is an
66
+ # unrecognised command, not a runtime "data missing" failure.
67
+ if has_reproduce_command():
68
+ rep = sub.add_parser("reproduce", help="reproduce a complete published benchmark")
69
+ rep.add_argument("benchmark_id")
70
+ rep.add_argument("--out", default="./artifacts")
71
+
72
+ return p
73
+
74
+
75
+ def main(argv: list[str] | None = None) -> int:
76
+ parser = build_parser()
77
+ args = parser.parse_args(argv)
78
+ if args.version:
79
+ from physmap import __version__
80
+ print(f"physmap {__version__} (release state: {CURRENT_RELEASE_STATE.value})")
81
+ return 0
82
+ if not args.command:
83
+ parser.print_help()
84
+ return 0
85
+ from physmap._paths import CheckoutRequired
86
+ try:
87
+ if args.command == "screen":
88
+ return _cmd_screen(args)
89
+ if args.command == "benchmark":
90
+ return _cmd_benchmark(args)
91
+ if args.command == "explain":
92
+ return _cmd_explain(args)
93
+ if args.command == "stress-test":
94
+ return _cmd_stress_test(args)
95
+ except CheckoutRequired as e:
96
+ # A pip-installed wheel has no checkout. One sentence saying what is missing and how
97
+ # to get it, not a traceback.
98
+ print(f"physmap {args.command}: {e}", file=sys.stderr)
99
+ return 1
100
+ print(f"'{args.command}' is not implemented yet in this build.", file=sys.stderr)
101
+ return 2
102
+
103
+
104
+ def _cmd_explain(args) -> int:
105
+ from physmap.applicability.fixtures import FIXTURE_IDS, get_fixture
106
+ from physmap.benchmarks.registry import VEHICLES
107
+ from physmap.benchmarks.report import load_banked_matrix
108
+ from physmap.explain.benchmark import render_cell
109
+ from physmap.explain.causal import render_screen
110
+
111
+ if args.list:
112
+ print("benchmark vehicles (closure validity + observability):")
113
+ for v in VEHICLES:
114
+ print(f" {v.vehicle_id}")
115
+ print("screening cases (causal-materiality applicability, declarative):")
116
+ for c in FIXTURE_IDS:
117
+ print(f" {c}")
118
+ return 0
119
+
120
+ if not args.subject:
121
+ print("a subject is required (or use --list)", file=sys.stderr)
122
+ return 2
123
+
124
+ if args.subject in FIXTURE_IDS:
125
+ print(render_screen(get_fixture(args.subject)))
126
+ return 0
127
+
128
+ cells = {c["vehicle_id"]: c for c in load_banked_matrix()["cells"]}
129
+ if args.subject in cells:
130
+ print(render_cell(cells[args.subject]))
131
+ return 0
132
+
133
+ print(f"unknown subject {args.subject!r}; use --list", file=sys.stderr)
134
+ return 2
135
+
136
+
137
+ def _cmd_screen(args) -> int:
138
+ from physmap.applicability.fixtures import FIXTURE_IDS, get_fixture
139
+ from physmap.explain.causal import render_screen
140
+
141
+ if args.list:
142
+ for case_id in FIXTURE_IDS:
143
+ print(case_id)
144
+ return 0
145
+
146
+ if not args.case:
147
+ print("a case id is required (or use --list)", file=sys.stderr)
148
+ return 2
149
+
150
+ try:
151
+ result = get_fixture(args.case)
152
+ except KeyError as e:
153
+ print(str(e).strip("'"), file=sys.stderr)
154
+ return 2
155
+
156
+ if args.qoi and args.qoi != result.qoi:
157
+ print(
158
+ f"case {result.case_id!r} is screened for qoi {result.qoi!r}, not "
159
+ f"{args.qoi!r}",
160
+ file=sys.stderr,
161
+ )
162
+ return 2
163
+
164
+ print(render_screen(result))
165
+ return 0
166
+
167
+
168
+
169
+
170
+ def _cmd_benchmark(args) -> int:
171
+ from physmap.benchmarks.registry import (
172
+ banked_only_ids,
173
+ licensed_ids,
174
+ rerunnable_ids,
175
+ unlicensed_shipped_ids,
176
+ )
177
+ from physmap.benchmarks.report import coverage_note, render_report
178
+
179
+ if args.action == "report":
180
+ print(render_report())
181
+ return 0
182
+
183
+ if args.action == "coverage":
184
+ print(coverage_note())
185
+ return 0
186
+
187
+ # `run`. Recompute every vehicle from the checkout, then compare against the
188
+ # banked matrix. The comparison is the point: a benchmark that runs but is never
189
+ # checked against its own bank will drift silently.
190
+ from physmap._paths import CheckoutRequired, have_checkout
191
+ if not have_checkout():
192
+ raise CheckoutRequired("the benchmark's vehicle data")
193
+ from physmap.benchmarks.benchmark_v0_4 import run_matrix
194
+ from physmap.benchmarks.compare import compare_matrices
195
+ from physmap.benchmarks.report import load_banked_matrix
196
+
197
+ rerun, banked = rerunnable_ids(), banked_only_ids()
198
+ print(f"Source data ships for {len(rerun)} of {len(rerun) + len(banked)} vehicles.")
199
+ if banked:
200
+ print(f"Banked only ({len(banked)}): {', '.join(banked)}")
201
+ print(f"Of those shipped, {len(licensed_ids())} carry a licence and "
202
+ f"{len(unlicensed_shipped_ids())} do not. Shipping is not licensing -- see NOTICE.")
203
+ print()
204
+ print("Recomputing from this checkout ...")
205
+
206
+ # write=False on purpose: `run` must never overwrite the committed bank it is
207
+ # being compared against.
208
+ fresh = run_matrix(write=False)
209
+ cells = {c["vehicle_id"]: c for c in fresh["cells"]}
210
+ cmp = compare_matrices(fresh, load_banked_matrix())
211
+
212
+ print(f"Recomputed {len(cells)} vehicles.")
213
+ if not cmp.matches:
214
+ print(f"DRIFT against the banked matrix in: {', '.join(cmp.drifting_vehicles())}")
215
+ for d in cmp.drift[:10]:
216
+ print(f" {d}")
217
+ elif cmp.bit_identical:
218
+ print("Every cell matches the banked matrix exactly.")
219
+ else:
220
+ # Floats differing in their last bits across numpy/BLAS builds. Reported
221
+ # rather than hidden, because "matches within tolerance" and "identical"
222
+ # are different claims.
223
+ print(f"Every cell matches the banked matrix: {cmp.summary()}.")
224
+ for d in cmp.within_tolerance[:5]:
225
+ print(f" {d}")
226
+
227
+ if args.report:
228
+ print()
229
+ print(render_report(rerun_results=cells))
230
+ return 0 if cmp.matches else 1
231
+
232
+
233
+ _STRESS_TESTS = {
234
+ "lewis-reuse": "controlled model-reuse stress test on Lewis (1992) Test 35A: a "
235
+ "forced-convection surrogate reused where buoyancy is material",
236
+ }
237
+
238
+
239
+ def _cmd_stress_test(args) -> int:
240
+ if args.list or not args.test_id:
241
+ for tid, what in _STRESS_TESTS.items():
242
+ print(f"{tid:14s} {what}")
243
+ return 0
244
+ if args.test_id not in _STRESS_TESTS:
245
+ print(f"unknown stress test {args.test_id!r}; try --list", file=sys.stderr)
246
+ return 2
247
+
248
+ import json
249
+ from pathlib import Path
250
+
251
+ from physmap._paths import CheckoutRequired, have_checkout
252
+ from physmap.stress_tests import lewis_reuse as st
253
+
254
+ if not have_checkout():
255
+ raise CheckoutRequired("the stress test's banked CFD-derived inputs")
256
+ print("Recomputing from this checkout (a few minutes) ...")
257
+ print()
258
+ try:
259
+ record = st.run()
260
+ except FileNotFoundError as e:
261
+ # The banked CFD profiles live in the checkout, like the benchmark's substrate data;
262
+ # a wheel install says so plainly instead of failing with a traceback.
263
+ print(f"Cannot run the stress test here: {e}", file=sys.stderr)
264
+ return 1
265
+ print(st.render(record))
266
+ print()
267
+
268
+ failures = st.check(record)
269
+ if failures:
270
+ print("ASSERTIONS FAILED:")
271
+ for f in failures:
272
+ print(f" {f}")
273
+ else:
274
+ print("Assertions hold: every visible deployment input is a training input (design M); "
275
+ "OOD scores are unchanged between gravity off and gravity on; materiality is zero "
276
+ "with gravity off; the surrogate matches the accurate control.")
277
+
278
+ if args.json:
279
+ Path(args.json).write_text(json.dumps(record, indent=1) + "\n")
280
+ print(f"Wrote the fresh record to {args.json}.")
281
+
282
+ # Same contract as `benchmark run`: a result that is recomputed but never checked against
283
+ # its own bank drifts silently.
284
+ try:
285
+ cmp = st.compare_with_bank(record)
286
+ except FileNotFoundError as e:
287
+ print(f"No banked record to compare against: {e}")
288
+ return 1
289
+ if not cmp.matches:
290
+ print(f"DRIFT against the banked record in {len(cmp.drift)} field(s):")
291
+ for d in cmp.drift[:10]:
292
+ print(f" {d}")
293
+ elif cmp.bit_identical:
294
+ print("The record matches the banked record exactly.")
295
+ else:
296
+ print(f"The record matches the banked record: {cmp.summary()}.")
297
+ return 0 if (not failures and cmp.matches) else 1
298
+
299
+
300
+ if __name__ == "__main__":
301
+ raise SystemExit(main())
@@ -0,0 +1,48 @@
1
+ """Closure registry — the executable bridge between corpus entries and Nu formulas.
2
+
3
+ Per the v0.2 architecture refactor (Part 1): closure formulas live in `formulas.py`;
4
+ the keyed registry of `{closure_id -> ClosureEntry}` lives in `registry.py`; the
5
+ controlled geometry-class vocabulary lives in `geometry_classes.py`.
6
+
7
+ The registry is queried by `substrate_engine.build_substrate(...)` to look up both
8
+ the executable formula AND the geometry_class used in the geometry-match invariant.
9
+ Ranges and status mirror the calibration table (`results/calibration_corpus/corpus.jsonl`);
10
+ the registry caches them for fast invariant checks, but corpus.jsonl remains the
11
+ source of truth.
12
+ """
13
+
14
+ from physmap.closures.geometry_classes import (
15
+ GeometryClass,
16
+ NARROW_RECT_CHANNEL_ONE_SIDED,
17
+ CIRCULAR_PIPE,
18
+ CIRCULAR_MICRO_TUBE_SMOOTH,
19
+ CIRCULAR_PIPE_ROUGH,
20
+ FLAT_PLATE_EXTERNAL_FORCED,
21
+ VERTICAL_PLATE_EXTERNAL_NATURAL,
22
+ FLAT_PLATE_EXTERNAL_MIXED,
23
+ CIRCULAR_PIPE_ENTRANCE_REGION,
24
+ ALL_GEOMETRY_CLASSES,
25
+ )
26
+ from physmap.closures.registry import (
27
+ ClosureEntry,
28
+ REGISTRY,
29
+ get_closure,
30
+ closure_ids_for_geometry,
31
+ )
32
+
33
+ __all__ = [
34
+ "ClosureEntry",
35
+ "REGISTRY",
36
+ "get_closure",
37
+ "closure_ids_for_geometry",
38
+ "GeometryClass",
39
+ "NARROW_RECT_CHANNEL_ONE_SIDED",
40
+ "CIRCULAR_PIPE",
41
+ "CIRCULAR_MICRO_TUBE_SMOOTH",
42
+ "CIRCULAR_PIPE_ROUGH",
43
+ "FLAT_PLATE_EXTERNAL_FORCED",
44
+ "VERTICAL_PLATE_EXTERNAL_NATURAL",
45
+ "FLAT_PLATE_EXTERNAL_MIXED",
46
+ "CIRCULAR_PIPE_ENTRANCE_REGION",
47
+ "ALL_GEOMETRY_CLASSES",
48
+ ]
@@ -0,0 +1,7 @@
1
+ """Bundled open closure-index data (package resource).
2
+
3
+ Holds `closure_index.json` — public coverage metadata for every known closure
4
+ (id, name, family, geometry, coordinates, citation, aliases). NO ranges, NO
5
+ provenance, NO disposition content. Generated by
6
+ `physmap.closures.index.build_index_json()` and shipped in the core wheel.
7
+ """