STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
run_layer1_8000n_dp_report.py
Go to the documentation of this file.
1#!/usr/bin/env python3
2"""One-shot Layer 1 on impinging_lox_ch4_8000N.yaml (for multiprocessing-safe __main__)."""
3
4from __future__ import annotations
5
6import argparse
7import copy
8import math
9import sys
10from pathlib import Path
11
12import numpy as np
13
14ROOT = Path(__file__).resolve().parents[1]
15sys.path.insert(0, str(ROOT))
16
17from engine.core.runner import PintleEngineRunner
19 _expected_geom_ao_af_for_unit_momentum_ratio,
20 run_layer1_optimization,
21)
22from engine.pipeline.io import load_config
23
24
26 perf: dict,
27 *,
28 rho_o: float,
29 rho_f: float,
30 optimal_mr: float,
31) -> None:
32 """Print mdot/ρ/A/v and ρv² terms consistent with impinging bulk momentum_ratio_R."""
33 diag = perf.get("diagnostics") or {}
34 mdot_o = float(perf.get("mdot_O", diag.get("mdot_O") or float("nan")))
35 mdot_f = float(perf.get("mdot_F", diag.get("mdot_F") or float("nan")))
36 a_go = perf.get("A_geom_O", diag.get("A_geom_O"))
37 a_gf = perf.get("A_geom_F", diag.get("A_geom_F"))
38 a_go = float(a_go) if a_go is not None else float("nan")
39 a_gf = float(a_gf) if a_gf is not None else float("nan")
40 a_eo = perf.get("A_eff_O", diag.get("A_eff_O"))
41 a_ef = perf.get("A_eff_F", diag.get("A_eff_F"))
42 a_eo = float(a_eo) if a_eo is not None else float("nan")
43 a_ef = float(a_ef) if a_ef is not None else float("nan")
44
45 v_o = mdot_o / (rho_o * a_go) if np.isfinite(a_go) and a_go > 0 and rho_o > 0 else float("nan")
46 v_f = mdot_f / (rho_f * a_gf) if np.isfinite(a_gf) and a_gf > 0 and rho_f > 0 else float("nan")
47 flux_o = rho_o * v_o * v_o if np.isfinite(v_o) else float("nan")
48 flux_f = rho_f * v_f * v_f if np.isfinite(v_f) else float("nan")
49 r_diag = diag.get("momentum_ratio_R")
50 r_diag = float(r_diag) if r_diag is not None and np.isfinite(r_diag) else float("nan")
51 r_recon = math.sqrt(flux_o / flux_f) if flux_f > 0 and flux_o >= 0 else float("nan")
52
53 cd_o = perf.get("Cd_O", diag.get("Cd_O"))
54 cd_f = perf.get("Cd_F", diag.get("Cd_F"))
55 cd_o = float(cd_o) if cd_o is not None else float("nan")
56 cd_f = float(cd_f) if cd_f is not None else float("nan")
57
58 exp_af = _expected_geom_ao_af_for_unit_momentum_ratio(optimal_mr, rho_o, rho_f)
59 ao_af_geom = a_go / a_gf if np.isfinite(a_go) and np.isfinite(a_gf) and a_gf > 0 else float("nan")
60 ao_af_eff = a_eo / a_ef if np.isfinite(a_eo) and np.isfinite(a_ef) and a_ef > 0 else float("nan")
61
62 u_o_lin = diag.get("u_O")
63 u_f_lin = diag.get("u_F")
64 u_o_lin = float(u_o_lin) if u_o_lin is not None else float("nan")
65 u_f_lin = float(u_f_lin) if u_f_lin is not None else float("nan")
66
67 print("\n--- Impinging momentum decomposition (bulk v = mdot/(ρ A_geom_tot)) ---")
68 print(f"mdot_O [kg/s]: {mdot_o:.6g}")
69 print(f"mdot_F [kg/s]: {mdot_f:.6g}")
70 print(f"rho_O [kg/m³]: {rho_o:.6g}")
71 print(f"rho_F [kg/m³]: {rho_f:.6g}")
72 print(f"A_geom_O [m²]: {a_go:.6g}")
73 print(f"A_geom_F [m²]: {a_gf:.6g}")
74 print(f"A_eff_O [m²]: {a_eo:.6g}")
75 print(f"A_eff_F [m²]: {a_ef:.6g}")
76 print(f"v_O_bulk [m/s]: {v_o:.6g} (runner linear u_O [m/s]: {u_o_lin:.6g})")
77 print(f"v_F_bulk [m/s]: {v_f:.6g} (runner linear u_F [m/s]: {u_f_lin:.6g})")
78 print(f"rho_O*v_O² [Pa]: {flux_o:.6g}")
79 print(f"rho_F*v_F² [Pa]: {flux_f:.6g}")
80 print(f"momentum_ratio_R: {r_diag:.6g} (reconstructed √(flux_O/flux_F): {r_recon:.6g})")
81 print(f"Cd_O / Cd_F: {cd_o:.6g} / {cd_f:.6g}")
82 print("\n--- Geometry momentum hint (optimizer steering; MR from requirements) ---")
83 print(f"A_geom_O/A_geom_F: {ao_af_geom:.6g}")
84 print(f"A_eff_O/A_eff_F: {ao_af_eff:.6g}")
85 print(f"expected_AO_AF_for_R≈1: {exp_af:.6g} (= MR/√(ρ_O/ρ_F), MR={optimal_mr:g})")
86 pg = perf.get("geom_ao_af")
87 pe = perf.get("expected_ao_af_for_R1")
88 if pg is not None or pe is not None:
89 print(f"(promoted perf geom_ao_af / expected): {pg} / {pe}")
90
91
92def main() -> None:
93 ap = argparse.ArgumentParser(description=__doc__)
94 ap.add_argument(
95 "--config",
96 type=str,
97 default=str(ROOT / "configs/impinging_lox_ch4_8000N.yaml"),
98 help="YAML config path",
99 )
100 ap.add_argument("--layer1-max-iterations", type=int, default=22)
101 args = ap.parse_args()
102
103 cfg = load_config(args.config)
104 req = cfg.design_requirements.model_dump()
105 runner = PintleEngineRunner(copy.deepcopy(cfg))
106 pcfg = {
107 "mode": "optimizer_controlled",
108 "max_lox_pressure_psi": float(req["max_lox_tank_pressure_psi"]),
109 "max_fuel_pressure_psi": float(req["max_fuel_tank_pressure_psi"]),
110 }
111
112 opt_cfg, results = run_layer1_optimization(
113 copy.deepcopy(cfg),
114 runner,
115 req,
116 target_burn_time=float(req.get("target_burn_time", 6.0)),
117 tolerances={"thrust": 0.10, "apogee": 0.15},
118 pressure_config=pcfg,
119 layer1_smoke=True,
120 layer1_max_iterations=int(args.layer1_max_iterations),
121 layer1_cma_restarts=1,
122 )
123
124 perf = results.get("performance") or {}
125 rho_o = float(opt_cfg.fluids["oxidizer"].density)
126 rho_f = float(opt_cfg.fluids["fuel"].density)
127 optimal_mr = float(req.get("optimal_of_ratio", 3.5))
128 geom = getattr(getattr(opt_cfg, "injector", None), "geometry", None)
129 ox = getattr(geom, "oxidizer", None)
130 fu = getattr(geom, "fuel", None)
131
132 print("\n=== Layer-1 summary (8000 N impinging, asymmetric ΔP/Pc bands) ===")
133 print(
134 f"Preferred bands: O [{req.get('injector_dp_ratio_O_min')},{req.get('injector_dp_ratio_O_max')}], "
135 f"F [{req.get('injector_dp_ratio_F_min')},{req.get('injector_dp_ratio_F_max')}]"
136 )
137 print(f"Thrust F [N]: {perf.get('F')}")
138 print(f"O/F (MR): {perf.get('MR')}")
139 print(f"momentum_ratio_R: {perf.get('momentum_ratio_R')}")
140 print(f"ΔP_inj_O / Pc: {perf.get('injector_dp_ratio_O')}")
141 print(f"ΔP_inj_F / Pc: {perf.get('injector_dp_ratio_F')}")
142 print(f"P_O_start_psi: {perf.get('P_O_start_psi')}")
143 print(f"P_F_start_psi: {perf.get('P_F_start_psi')}")
144 print(f"injector_dp_out_of_range: {perf.get('injector_dp_out_of_range')}")
145 print(
146 f"geom AO/AF diagnostics: AoAf={perf.get('geom_ao_af')} "
147 f"expected_R1={perf.get('expected_ao_af_for_R1')} "
148 f"rel_err={perf.get('geom_ao_af_rel_error')}"
149 )
151 perf, rho_o=rho_o, rho_f=rho_f, optimal_mr=optimal_mr
152 )
153 if ox and fu:
154 print(f"n_elements_O / d_jet_O [mm]: {ox.n_elements} / {ox.d_jet * 1000:.4f}")
155 print(f"n_elements_F / d_jet_F [mm]: {fu.n_elements} / {fu.d_jet * 1000:.4f}")
156 print(f"spacing_O/spacing_F [mm]: {ox.spacing * 1000:.4f} / {fu.spacing * 1000:.4f}")
157 print(f"impingement_angle_O/F [deg]: {ox.impingement_angle:.2f} / {fu.impingement_angle:.2f}")
158
159
160if __name__ == "__main__":
161 main()
None print_impinging_momentum_decomposition(dict perf, *, float rho_o, float rho_f, float optimal_mr)