STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
design_robustness.py
Go to the documentation of this file.
1"""Does a design survive the things the model is NOT sure about?
2
3 python3 scripts/design_robustness.py configs/<design>.yaml
4
5A converged run tells you the design is self-consistent. It tells you nothing about how
6much of the answer rests on a number nobody measured. This sweeps the three that carry
7real uncertainty and re-solves the design AS WRITTEN at each point.
8
9Read the "Cd got" column. ``inlet_geometry`` resolves Cd inside
10``cd_inf_from_orifice_diameter`` and overrides ``Cd_inf``, so a sweep that only sets
11``Cd_inf`` silently does nothing -- measured, 0.72 / 0.76 / 0.80 all returned exactly the
12same thrust until this script cleared ``inlet_geometry`` too. Any sweep of this model must
13report the value it actually achieved, not the one it asked for.
14
15Three knobs carry real uncertainty, and none of them is settled by a converged run:
16 eta_c* -- 0.95 here vs a 0.87 published comparable for this propellant/class
17 Cd -- 0.80 is an inlet-geometry correlation, not a flow test
18 Pc -- the throat grows as the graphite recesses
19Each is swept INDEPENDENTLY through its plausible range and the design re-solved as written.
20"""
21import sys, copy, math, json
22sys.path.insert(0, str(__import__('pathlib').Path(__file__).resolve().parents[1]))
23import yaml
24from engine.pipeline.io import load_config
25from engine.core.runner import PintleEngineRunner
26
27def solve(cfg):
28 run = PintleEngineRunner(copy.deepcopy(cfg))
29 res = run.evaluate(cfg.lox_tank.initial_pressure_psi*6894.757,
30 cfg.fuel_tank.initial_pressure_psi*6894.757, silent=True)
31 return float(res.get('Pc', float('nan'))), res
32
33def main():
34 path = sys.argv[1]
35 base = load_config(path)
36 pc0, r0 = solve(base)
37 F0 = r0.get('F') or 0.0
38 print(f"BASE Pc {pc0/6894.757:7.2f} psia F {F0:8.1f} N O/F {r0['MR']:.4f} "
39 f"eta_c* {r0['eta_cstar']:.4f} Isp {r0.get('Isp',float('nan')):.2f}")
40 print()
41 print("--- Cd swept (flow test not yet done; 0.80 is a correlation) ---")
42 print(f"{'Cd set':>7s}{'Cd got':>8s}{'Pc psia':>10s}{'thrust N':>11s}{'d(F)':>9s}{'O/F':>9s}{'dP/Pc O':>9s}{'dP/Pc F':>9s}")
43 for cd in (0.72, 0.76, 0.80, 0.84, 0.88):
44 c = copy.deepcopy(base)
45 PO = c.lox_tank.initial_pressure_psi*6894.757
46 PF = c.fuel_tank.initial_pressure_psi*6894.757
47 for side in ('fuel','oxidizer'):
48 d = c.discharge[side]
49 # inlet_geometry resolves Cd inside cd_inf_from_orifice_diameter and WINS over
50 # Cd_inf, so it has to be cleared or the sweep silently does nothing. (Measured:
51 # 0.72/0.76/0.80 all returned the same thrust until this line existed.)
52 d.inlet_geometry = None
53 d.inlet_radius_ratio = None
54 d.use_geometry_cd = False
55 d.Cd_inf = cd; d.Cd_min = cd; d.a_Re = 0.0; d.cd_inf_max = cd; d.cd_inf_min_geom = cd
56 try:
57 pc, r = solve(c)
58 dpo = (PO - pc)/pc if pc else float('nan'); dpf = (PF - pc)/pc if pc else float('nan')
59 got = r.get('Cd_O', float('nan'))
60 flag = "" if abs(got-cd) < 5e-3 else " <-- CLAMPED, sweep not honoured"
61 print(f"{cd:7.2f}{got:8.3f}{pc/6894.757:10.2f}{r.get('F',0):11.1f}"
62 f"{100*((r.get('F',0)-F0)/F0):+8.1f}%{r['MR']:9.4f}{dpo:9.3f}{dpf:9.3f}{flag}")
63 except Exception as e:
64 print(f"{cd:6.2f} FAILED: {type(e).__name__}: {str(e)[:60]}")
65 print()
66 print("--- eta_c* haircut (0.95 modelled vs 0.87 published comparable) ---")
67 print(f"{'scale':>7s}{'eta_c*':>9s}{'Pc psia':>10s}{'thrust N':>11s}{'d(F)':>9s}{'Isp':>9s}")
68 for k in (1.00, 0.97, 0.94, 0.92):
69 c = copy.deepcopy(base)
70 e = c.combustion.efficiency
71 e.Em_peak = float(e.Em_peak) * k
72 try:
73 pc, r = solve(c)
74 print(f"{k:7.2f}{r['eta_cstar']:9.4f}{pc/6894.757:10.2f}{r.get('F',0):11.1f}"
75 f"{100*((r.get('F',0)-F0)/F0):+8.1f}%{r.get('Isp',float('nan')):9.2f}")
76 except Exception as ex:
77 print(f"{k:7.2f} FAILED: {type(ex).__name__}: {str(ex)[:60]}")
78 print()
79 print("--- throat growth from graphite recession (mid-burn) ---")
80 print(f"{'dA/A':>7s}{'D_t mm':>9s}{'Pc psia':>10s}{'thrust N':>11s}{'d(F)':>9s}")
81 A0 = base.chamber_geometry.A_throat
82 for g in (0.0, 0.02, 0.05, 0.09):
83 c = copy.deepcopy(base)
84 # The throat erodes; the exit plane does not move. eps therefore FALLS.
85 c.chamber_geometry.A_throat = A0*(1+g)
86 c.chamber_geometry.expansion_ratio = c.chamber_geometry.A_exit/(A0*(1+g))
87 c.combustion.cea.expansion_ratio = c.chamber_geometry.expansion_ratio
88 try:
89 pc, r = solve(c)
90 print(f"{g:6.0%}{math.sqrt(4*A0*(1+g)/math.pi)*1000:9.2f}{pc/6894.757:10.2f}"
91 f"{r.get('F',0):11.1f}{100*((r.get('F',0)-F0)/F0):+8.1f}%")
92 except Exception as ex:
93 print(f"{g:6.0%} FAILED: {type(ex).__name__}: {str(ex)[:60]}")
94
95if __name__ == '__main__':
96 main()