STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
config_provenance_audit.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Which numbers in this config did anyone actually choose?
3
4EngineDesign configs carry ~900 fields. Most arrive by inheriting configs/default.yaml,
5which is a GENERIC template -- its feed system is 3/8" NPT at K0 2.0 with 0.305 m runs,
6which is nobody's vehicle in particular. Those values are perfectly valid config, so
7nothing in the loader, the optimizer or the UI ever flags them, and a design point can
8miss by 20%+ on O/F with every gate reporting a clean reason that points somewhere else.
9
10This prints, for a given config, every physically consequential field that is
11BIT-IDENTICAL to default.yaml -- i.e. inherited, not chosen.
12
13 python3 scripts/config_provenance_audit.py configs/mine.yaml [--all]
14
15Note the obvious limit: identical-to-default is not the same as wrong. Prandtl 0.7 is
16fine for anyone. The point is that nothing distinguishes the fields where that is true
17from the fields where it is not, so the list is where to start looking, not a defect list.
18"""
19from __future__ import annotations
20import argparse, json, sys
21from pathlib import Path
22
23import yaml
24
25ROOT = Path(__file__).resolve().parent.parent
26
27# Sections whose values change the answer rather than the presentation.
28CONSEQUENTIAL = (
29 "feed_system", "injector", "combustion.efficiency", "spray", "stability",
30 "ablative_cooling", "film_cooling", "regen_cooling", "chamber_geometry",
31)
32# Fields that have bitten a real design point. Printed first, loudly.
33KNOWN_TRAPS = {
34 "feed_system.oxidizer.line_size": "sets LOX bore; dP ~ 1/A^2, so a name is worth ~2x",
35 "feed_system.fuel.line_size": "sets fuel bore; dP ~ 1/A^2",
36 "feed_system.oxidizer.K0": "lumped loss coeff; NOT length-scaled -- must cover the whole run",
37 "feed_system.fuel.K0": "lumped loss coeff; NOT length-scaled",
38 "feed_system.oxidizer.length": "chug inertance ONLY -- does not touch dP",
39 "feed_system.fuel.length": "chug inertance ONLY -- does not touch dP",
40 "spray.evaporation.x_star_limit": "caps vaporisation length; gates L* feasibility",
41 "ablative_cooling.initial_thickness": "sets chamber bore for a given OD",
42}
43
44
45def flatten(d, pre=""):
46 out = {}
47 if isinstance(d, dict):
48 for k, v in d.items():
49 out.update(flatten(v, f"{pre}.{k}" if pre else k))
50 elif isinstance(d, list):
51 out[pre] = json.dumps(d)[:80]
52 else:
53 out[pre] = d
54 return out
55
56
57def main() -> int:
58 ap = argparse.ArgumentParser()
59 ap.add_argument("config", help="config YAML to audit")
60 ap.add_argument("--baseline", default=str(ROOT / "configs/default.yaml"))
61 ap.add_argument("--all", action="store_true", help="every inherited field, not just consequential ones")
62 a = ap.parse_args()
63
64 cfg = flatten(yaml.safe_load(open(a.config)))
65 base = flatten(yaml.safe_load(open(a.baseline)))
66
67 inherited = [k for k in sorted(cfg)
68 if k in base and str(cfg[k]) == str(base[k]) and cfg[k] is not None]
69 if not a.all:
70 inherited = [k for k in inherited if any(k.startswith(s) for s in CONSEQUENTIAL)]
71
72 traps = [k for k in inherited if k in KNOWN_TRAPS]
73 rest = [k for k in inherited if k not in KNOWN_TRAPS]
74
75 print(f"config : {a.config}")
76 print(f"baseline : {a.baseline}")
77 print(f"\n{len(inherited)} consequential fields are bit-identical to the baseline "
78 f"(inherited, not chosen).\n")
79
80 if traps:
81 print(" These have each cost a design point before:")
82 for k in traps:
83 print(f" {k:44s} = {str(cfg[k]):<14s} {KNOWN_TRAPS[k]}")
84 print()
85 if rest:
86 print(" Also inherited:")
87 for k in rest:
88 print(f" {k:44s} = {cfg[k]}")
89 if not inherited:
90 print(" (none -- every consequential field differs from the baseline)")
91 return 0
92
93
94if __name__ == "__main__":
95 sys.exit(main())