80 ap = argparse.ArgumentParser(description=__doc__)
81 ap.add_argument(
"--config", type=str, default=str(ROOT /
"configs/impinging_lox_ch4_8000N.yaml"))
82 ap.add_argument(
"--a-throat-center-mm2", type=float, default=2443.263433372886)
83 ap.add_argument(
"--throat-span-fraction", type=float, default=0.125)
84 ap.add_argument(
"--layer1-max-iterations", type=int, default=150)
85 ap.add_argument(
"--layer1-cma-restarts", type=int, default=1)
86 ap.add_argument(
"--output-csv", type=str, default=str(ROOT /
"output/layer1_sweep_of_fuel_pressure.csv"))
90 default=
"3.5,3.7,4.0",
91 help=
"Comma-separated optimal_of_ratio targets (default: 3.5,3.7,4.0)",
96 default=
"900,800,700",
97 help=
"Comma-separated max fuel tank pressure caps [psi] (default: 900,800,700)",
99 args = ap.parse_args()
101 cfg_path = Path(args.config)
102 center = float(args.a_throat_center_mm2)
103 frac = float(args.throat_span_fraction)
104 dp_band = (0.15, 0.40)
105 r_lo, r_hi = 0.85, 1.15
107 of_targets = [float(x.strip())
for x
in args.of_targets.split(
",")
if x.strip()]
108 fuel_caps_psi = [float(x.strip())
for x
in args.fuel_caps.split(
",")
if x.strip()]
110 rows: List[Dict[str, Any]] = []
112 out_path = Path(args.output_csv)
113 out_path.parent.mkdir(parents=
True, exist_ok=
True)
115 for of_tgt
in of_targets:
116 for pf_cap
in fuel_caps_psi:
117 cfg = load_config(str(cfg_path))
118 if cfg.chamber_geometry
is not None:
119 cfg.chamber_geometry.design_MR = float(of_tgt)
121 req = cfg.design_requirements.model_dump()
124 req[
"target_thrust"] = 8000.0
125 req[
"optimal_of_ratio"] = float(of_tgt)
126 req[
"layer1_A_throat_mm2_min"] = center * (1.0 - frac)
127 req[
"layer1_A_throat_mm2_max"] = center * (1.0 + frac)
128 req[
"injector_dp_ratio_O_min"] = dp_band[0]
129 req[
"injector_dp_ratio_O_max"] = dp_band[1]
130 req[
"injector_dp_ratio_F_min"] = dp_band[0]
131 req[
"injector_dp_ratio_F_max"] = dp_band[1]
133 req[
"W_geom_ao_af_momentum"] = 2800.0
134 req[
"impinging_momentum_R_min"] = float(r_lo)
135 req[
"impinging_momentum_R_max"] = float(r_hi)
137 req[
"max_fuel_tank_pressure_psi"] = float(pf_cap)
140 "mode":
"optimizer_controlled",
141 "max_lox_pressure_psi": float(req[
"max_lox_tank_pressure_psi"]),
142 "max_fuel_pressure_psi": float(pf_cap),
145 print(f
"\n>>> OF_target={of_tgt} max_fuel_psi={pf_cap:.0f}", flush=
True)
147 opt_cfg, results = run_layer1_optimization(
151 target_burn_time=float(req.get(
"target_burn_time", 6.0)),
152 tolerances={
"thrust": 0.10,
"apogee": 0.15},
153 pressure_config=pcfg,
155 layer1_max_iterations=int(args.layer1_max_iterations),
156 layer1_cma_restarts=int(args.layer1_cma_restarts),
159 perf = results.get(
"performance")
or {}
161 cdo, cdf =
_cds(perf)
162 R =
_sf(perf.get(
"momentum_ratio_R"))
164 ci = results.get(
"convergence_info")
or {}
165 ls = results.get(
"layer_status")
or {}
166 diag = perf.get(
"diagnostics")
if isinstance(perf.get(
"diagnostics"), dict)
else {}
168 math.isfinite(
_sf(perf.get(
"F")))
169 and math.isfinite(
_sf(perf.get(
"MR")))
170 and math.isfinite(
_sf(perf.get(
"Pc")))
172 inj_succ = diag.get(
"constraints_satisfied")
173 mdot_ratio = diag.get(
"mdot_ratio")
177 "max_fuel_psi_cap": pf_cap,
178 "F_N": round(
_sf(perf.get(
"F")), 2),
179 "MR_actual": round(
_sf(perf.get(
"MR")), 5),
181 "dP_O_over_Pc": round(ro, 5),
182 "dP_F_over_Pc": round(rf, 5),
183 "P_O_psi": round(
_sf(perf.get(
"P_O_start_psi")), 4),
184 "P_F_psi": round(
_sf(perf.get(
"P_F_start_psi")), 4),
185 "Cd_O": round(cdo, 5),
186 "Cd_F": round(cdf, 5),
188 "cma_converged": bool(ci.get(
"converged")),
189 "pressure_candidate_valid": bool(ls.get(
"layer_1_pressure_candidate")),
190 "finite_F_MR_Pc": bool(finite_core),
191 "injector_constraints_ok": inj_succ
if isinstance(inj_succ, bool)
else "",
192 "mdot_ratio_diag": mdot_ratio
if mdot_ratio
is not None else "",
193 "thrust_ok_5pct": abs(
_sf(perf.get(
"F")) - 8000.0) / 8000.0 <= 0.05,
194 "dP_F_ok": dp_band[0] <= rf <= dp_band[1]
if math.isfinite(rf)
else False,
195 "R_ok": r_lo <= R <= r_hi
if math.isfinite(R)
else False,
199 fieldnames = list(rows[0].keys())
if rows
else []
201 with open(out_path,
"w", newline=
"", encoding=
"utf-8")
as f:
202 w = csv.DictWriter(f, fieldnames=fieldnames)
206 print(
"\n" +
"=" * 100)
207 print(
"SUMMARY (target thrust 8000 N, R∈[0.85,1.15], ΔP/Pc∈[0.15,0.40])")
210 f
"{'OF':>5} {'P_F_cap':>8} {'F[N]':>8} {'MR':>8} {'R':>8} "
211 f
"{'dP_O':>8} {'dP_F':>8} {'P_O':>8} {'P_F':>8} {'Cd_O':>7} {'Cd_F':>7} "
212 f
"{'cma':>4} {'pCV':>4}"
215 print(
"-" * len(hdr))
218 f
"{r['OF_target']:5.1f} {r['max_fuel_psi_cap']:8.0f} {r['F_N']:8.1f} "
219 f
"{r['MR_actual']:8.4f} {r['R']:8.4f} {r['dP_O_over_Pc']:8.4f} {r['dP_F_over_Pc']:8.4f} "
220 f
"{r['P_O_psi']:8.1f} {r['P_F_psi']:8.1f} {r['Cd_O']:7.4f} {r['Cd_F']:7.4f} "
221 f
"{int(r['cma_converged']):4d} {int(r['pressure_candidate_valid']):4d}"
227 if r[
"thrust_ok_5pct"]
and r[
"dP_F_ok"]
and r[
"R_ok"]
and dp_band[0] <= r[
"dP_O_over_Pc"] <= dp_band[1]
229 print(
"\nJoint feasibility (|F−8000|/8000≤5%, both ΔP/Pc in band, R in band): ", end=
"")
231 print(f
"YES — {len(hits)} row(s)")
233 print(f
" OF={h['OF_target']} P_F_cap={h['max_fuel_psi_cap']:.0f} → dP_F={h['dP_F_over_Pc']:.4f}")
238 print(f
"\nCSV written: {out_path}")