STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
thrust_model_comparison.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""Read-only thrust-model comparison harness (Step 1 of the thrust-bug investigation).
3
4For a set of configs, operating points, expansion ratios, and ambient pressures, compare
5three thrust numbers WITHOUT touching any production code:
6
7 1. MODEL — engine/core/nozzle.py momentum + shifting reconstruction (runner.evaluate).
8 2. CF_VAC — F(Pa) = Cf_vac * Pc * At - Pa * Ae, with Cf_vac from CEA at the model's real Pc.
9 Efficiency rides in through Pc (the chamber solve already reduced it by eta_c*).
10 3. CEA ref — CEA's ideal equilibrium ambient Isp (the ceiling) and eta_c**ideal (physical target).
11
12The claim under test: delivered Isp must be <= eta_c* * Isp_ideal. The MODEL instead sits at the
13ideal ceiling; the CF_VAC method should land at eta_c**ideal.
14
15Run: ED_ACCEL=off python scripts/thrust_model_comparison.py
16"""
17import os
18import sys
19from pathlib import Path
20
21os.environ["ED_ACCEL"] = "off" # pure-Python authoritative path is the subject under test
22sys.path.insert(0, str(Path(__file__).resolve().parents[1]))
23
24import numpy as np # noqa: E402
25from engine.pipeline.io import load_config # noqa: E402
26from engine.core.runner import PintleEngineRunner # noqa: E402
27from engine.pipeline.cea_cache import _get_CEA_Obj # noqa: E402
28
29G0 = 9.80665
30PSI = 6894.76
31CONFIGS = [
32 ("impinging/CH4", "configs/canonical/impinging.yaml"),
33 ("pintle/Ethanol", "configs/canonical/pintle.yaml"),
34]
35
36
37def cea_obj(cfg):
38 return _get_CEA_Obj()(oxName=cfg.combustion.cea.ox_name, fuelName=cfg.combustion.cea.fuel_name)
39
40
41def cea_refs(ch, Pc_pa, MR, eps, Pa_pa):
42 """CEA ideal numbers at the model's real operating point."""
43 P = Pc_pa / PSI
44 isp_vac = ch.get_Isp(Pc=P, MR=MR, eps=eps) # s, vacuum, equilibrium
45 cstar = ch.get_Cstar(Pc=P, MR=MR) * 0.3048 # m/s
46 cf_vac = isp_vac * G0 / cstar # vacuum thrust coeff
47 try:
48 isp_amb = ch.estimate_Ambient_Isp(Pc=P, MR=MR, eps=eps, Pamb=Pa_pa / PSI)[0]
49 except Exception:
50 isp_amb = float("nan")
51 return cf_vac, cstar, isp_vac, isp_amb
52
53
54def one_case(runner, ch, P_O_pa, P_F_pa, Pa_pa, eps_override=None):
55 cfg = runner.config
56 cg = cfg.chamber_geometry
57 if eps_override is not None:
58 cg.expansion_ratio = eps_override
59 cg.A_exit = eps_override * cg.A_throat
60 res = runner.evaluate(P_O_pa, P_F_pa, P_ambient=Pa_pa)
61 Pc, MR, mdot = res["Pc"], res["MR"], res["mdot_total"]
62 At, Ae, eps = cg.A_throat, cg.A_exit, cg.expansion_ratio
63 eta = (res.get("diagnostics") or {}).get("eta_cstar", float("nan"))
64
65 model_F = res["F"]
66 model_Isp = model_F / (mdot * G0)
67
68 cf_vac, cstar, isp_vac, isp_amb = cea_refs(ch, Pc, MR, eps, Pa_pa)
69 cfvac_F = cf_vac * Pc * At - Pa_pa * Ae # delivered thrust, perfect nozzle, real Pc
70 cfvac_Isp = cfvac_F / (mdot * G0)
71 expected_Isp = eta * isp_amb # physically correct delivered (eta_cf=1)
72
73 return dict(Pc=Pc, MR=MR, eps=eps, eta=eta, mdot=mdot,
74 model_F=model_F, model_Isp=model_Isp,
75 cfvac_F=cfvac_F, cfvac_Isp=cfvac_Isp,
76 ceiling=isp_amb, expected=expected_Isp,
77 model_err=model_Isp / isp_amb - 1.0, # vs ideal ceiling
78 model_vs_expected=model_Isp / expected_Isp - 1.0,
79 cfvac_vs_expected=cfvac_Isp / expected_Isp - 1.0)
80
81
82def fmt_row(label, r):
83 flag = " <<OVER CEILING>>" if r["model_Isp"] > r["ceiling"] else ""
84 return (f" {label:22s} Pc={r['Pc']/1e6:5.2f}MPa MR={r['MR']:.2f} eps={r['eps']:.2f} "
85 f"eta={r['eta']:.3f} | MODEL Isp={r['model_Isp']:6.1f} CF_VAC={r['cfvac_Isp']:6.1f} "
86 f"ceiling={r['ceiling']:6.1f} expected={r['expected']:6.1f} | "
87 f"model+{r['model_vs_expected']*100:5.1f}% cfvac{r['cfvac_vs_expected']*100:+4.1f}%{flag}")
88
89
90def main():
91 for name, path in CONFIGS:
92 cfg = load_config(path)
93 runner = PintleEngineRunner(cfg)
94 ch = cea_obj(cfg)
95 print(f"\n{'='*120}\n{name} ({path})\n{'='*120}")
96
97 print("\n[A] Operating-point sweep (sea level, design eps):")
98 for po, pf in [(1305, 974), (1200, 900), (1100, 820), (1000, 750), (900, 680)]:
99 try:
100 print(fmt_row(f"P=({po},{pf})", one_case(runner, ch, po*PSI, pf*PSI, 101325.0)))
101 except Exception as e:
102 print(f" P=({po},{pf}) skipped: {type(e).__name__}: {e}")
103
104 print("\n[B] Expansion-ratio sweep (P=(1305,974), sea level):")
105 base_eps = cfg.chamber_geometry.expansion_ratio
106 for eps in [3.0, 4.0, 5.0, 6.5, 8.0]:
107 try:
108 print(fmt_row(f"eps={eps}", one_case(runner, ch, 1305*PSI, 974*PSI, 101325.0, eps_override=eps)))
109 except Exception as e:
110 print(f" eps={eps} skipped: {type(e).__name__}: {e}")
111 cfg.chamber_geometry.expansion_ratio = base_eps
112 cfg.chamber_geometry.A_exit = base_eps * cfg.chamber_geometry.A_throat
113
114 print("\n[C] Altitude sweep (P=(1305,974), design eps):")
115 for alt_pa in [101325.0, 70000.0, 40000.0, 10000.0, 1.0]:
116 try:
117 print(fmt_row(f"Pa={alt_pa/1000:.0f}kPa", one_case(runner, ch, 1305*PSI, 974*PSI, alt_pa)))
118 except Exception as e:
119 print(f" Pa={alt_pa} skipped: {type(e).__name__}: {e}")
120
121
122if __name__ == "__main__":
123 main()
one_case(runner, ch, P_O_pa, P_F_pa, Pa_pa, eps_override=None)
cea_refs(ch, Pc_pa, MR, eps, Pa_pa)