99 """Return skip tag if geometry is unrealistic before runner.evaluate(); else None.
101 Mirrors Layer-1 soft limits (area vs throat, packing, d_jet vs spacing) plus coarse
102 Bernoulli mdot proxies using tank PSI and a guessed Pc so obviously oversized A_eff
103 does not burn closure iterations.
106 cg = ensure_chamber_geometry(cfg)
107 At = float(cg.A_throat
or 0.0)
108 D_inner = float(cg.chamber_diameter
or 0.0)
111 if At <= 0
or not np.isfinite(At)
or D_inner <= 0
or not np.isfinite(D_inner):
119 if max(A_go, A_gf) / At > 0.92:
120 return "precheck:max_geometric_area_vs_throat"
121 if (A_go + A_gf) / At > 1.35:
122 return "precheck:sum_geometric_area_vs_throat"
126 cd_o_cfg = float(getattr(cfg.discharge[
"oxidizer"],
"Cd_inf", 0.4)
or 0.4)
127 cd_f_cfg = float(getattr(cfg.discharge[
"fuel"],
"Cd_inf", 0.3)
or 0.3)
129 cd_o_cfg, cd_f_cfg = 0.4, 0.3
130 a_eff_o =
max(cd_o_cfg, 0.08) * A_go
131 a_eff_f =
max(cd_f_cfg, 0.08) * A_gf
132 if max(a_eff_o, a_eff_f) / At > 0.96:
133 return "precheck:max_Aeff_vs_throat"
134 if (a_eff_o + a_eff_f) / At > 1.15:
135 return "precheck:sum_Aeff_vs_throat"
138 g = cfg.injector.geometry
139 sp_o = float(getattr(g.oxidizer,
"spacing", 0.012)
or 0.012)
140 sp_f = float(getattr(g.fuel,
"spacing", 0.012)
or 0.012)
141 face_a = np.pi * (D_inner / 2.0) ** 2
142 pack = float(n_O + n_F) *
max(sp_o, sp_f) ** 2
143 if face_a > 0
and pack / face_a > 0.68:
144 return "precheck:packing_density"
147 circ = np.pi * D_inner
148 if d_O >= sp_o * 0.998
or d_F >= sp_f * 0.998:
149 return "precheck:d_jet_ge_spacing"
150 if 2.0 * d_O > sp_o
or 2.0 * d_F > sp_f:
151 return "precheck:layer1_spacing_rule_2p0dj"
153 if n_O > 0
and n_O * d_O > 1.08 * circ:
154 return "precheck:n_O_d_O_vs_circ"
155 if n_F > 0
and n_F * d_F > 1.08 * circ:
156 return "precheck:n_F_d_F_vs_circ"
158 n_cap_o =
max(int(circ /
max(k_ring * d_O, 1e-9)), 1)
159 n_cap_f =
max(int(circ /
max(k_ring * d_F, 1e-9)), 1)
160 if n_O > n_cap_o + 2
or n_F > n_cap_f + 2:
161 return "precheck:ring_element_count"
165 rho_o = float(cfg.fluids[
"oxidizer"].density)
166 rho_f = float(cfg.fluids[
"fuel"].density)
168 rho_o, rho_f = 1140.0, 422.6
169 P_O = float(P_O_psi) * PSI_TO_PA
170 P_F = float(P_F_psi) * PSI_TO_PA
172 Pc_est =
min(P_O, P_F) * 0.38
173 Pc_est =
max(Pc_est, 8.0e4)
175 mdot_cap =
max(Pc_est * At / cstar_typ, 1e-9)
176 dP_o =
max(P_O - Pc_est, 0.07 * P_O, 4.0e4)
177 dP_f =
max(P_F - Pc_est, 0.07 * P_F, 4.0e4)
178 md_o = a_eff_o * np.sqrt(
max(2.0 * rho_o * dP_o, 0.0))
179 md_f = a_eff_f * np.sqrt(
max(2.0 * rho_f * dP_f, 0.0))
180 if md_o > 2.3 * mdot_cap
or md_f > 2.3 * mdot_cap:
181 return "precheck:mdot_proxy_stream_vs_throat"
182 if md_o + md_f > 2.85 * mdot_cap:
183 return "precheck:mdot_proxy_combined_vs_throat"
462 d_jet_upper_m: float = 0.004,
463) -> Tuple[List[int], List[int], List[float], List[float], List[Tuple[float, float]], str]:
464 """Return (n_O, n_F, d_O, d_F, tanks, description). n_max/d_jet_hi scale with chamber bore."""
467 (P_O_yaml, P_F_yaml),
468 (P_O_lo_l1, P_F_lo_l1),
469 (P_O_hi_l1, P_F_hi_l1),
470 (P_O_lo_l1, P_F_hi_l1),
471 (P_O_hi_l1, P_F_lo_l1),
472 ((P_O_lo_l1 + P_O_hi_l1) / 2, (P_F_lo_l1 + P_F_hi_l1) / 2),
475 if name ==
"widen_ox":
477 n_O = [6, 18, 30, 42]
478 n_F = list(range(n_min,
min(n_max + 1, 28), 6))
479 d_O = [0.0015, 0.0025, 0.0035, 0.0045, 0.0049]
480 d_F = [0.0012, 0.0020, 0.0028, 0.0034]
482 n_O = [6, 10, 14, 18, 22, 26, 30, 34, 38, 42]
483 n_F = list(range(n_min, n_max + 1, 4))
484 d_O = [0.0010, 0.0018, 0.0026, 0.0034, 0.0040, 0.0046, 0.0049]
485 d_F = np.linspace(0.0009, 0.0036, num=6, dtype=float).tolist()
487 "widen_ox: extend oxidizer side — n_O up to 42, d_jet_O up to 4.9 mm; "
488 "fuel n_F/d_F same class as Layer-1 (cap n_F by n_max). Tanks: Layer-1 corners + YAML."
490 return n_O, n_F, d_O, d_F, tanks_l1, desc
492 if name ==
"tighten_fuel":
495 n_O = [6, 14, 22, 30, 34]
496 n_F = [6, 10, 14, n_F_hi]
497 d_O = [0.0012, 0.0022, 0.0032]
498 d_F = [0.0009, 0.0013, 0.0017, 0.0021, 0.0025]
500 n_O = list(range(n_min, n_max + 1, 4))
501 n_F = [6, 8, 10, 12, 14, n_F_hi]
502 d_O = np.linspace(0.001, 0.0036, num=6, dtype=float).tolist()
503 d_F = [0.00085, 0.00115, 0.00145, 0.00175, 0.00205, 0.00235]
505 "tighten_fuel: cap fuel injectors — n_F ≤ 16, d_jet_F ≤ ~2.35 mm (coarse grid); "
506 "oxidizer side unchanged span vs Layer-1. Tanks: Layer-1 corners + YAML."
508 return n_O, n_F, d_O, d_F, tanks_l1, desc
510 if name ==
"expand_tanks":
512 d_hi_grid = float(
min(
max(float(d_jet_upper_m), 0.0034), 0.0075))
514 d_sym = [0.0014, 0.0022,
min(0.0030, d_hi_grid)]
515 d_sym = sorted({float(round(d, 6))
for d
in d_sym
if d <= d_hi_grid + 1e-9})
519 d_sym = np.linspace(0.001, d_hi_grid, num=5, dtype=float).tolist()
522 (P_O_yaml, P_F_yaml),
523 (max_lox_psi * 0.92, max_fuel_psi * 0.48),
524 (max_lox_psi * 0.97, max_fuel_psi * 0.45),
525 (max_lox_psi * 0.88, max_fuel_psi * 0.90),
526 (max_lox_psi * 0.72, max_fuel_psi * 0.55),
527 (max_lox_psi * 0.82, max_fuel_psi * 0.40),
528 (max_lox_psi * 0.65, max_fuel_psi * 0.68),
529 (max_lox_psi * 0.55, max_fuel_psi * 0.78),
532 "expand_tanks: symmetric injector grid; tank grid extends LOX toward ~0.97·P_max_lox "
533 "and fuel toward ~0.40–0.90·P_max_fuel (outside strict Layer-1 0.65–0.85 band)."
535 return n_sym, n_sym, d_sym, d_sym, tank_grid, desc
537 if name ==
"asymmetric":
539 n_O = [12, 22, 34, 42]
541 d_O = [0.0024, 0.0034, 0.0044]
542 d_F = [0.0010, 0.0016, 0.0022]
544 n_O = [10, 18, 26, 34, 42]
545 n_F = [6, 8, 10, 12, 14, 16]
546 d_O = [0.0018, 0.0026, 0.0034, 0.0042, 0.0049]
547 d_F = [0.00095, 0.00135, 0.00175, 0.00215, 0.00255]
549 (P_O_yaml, P_F_yaml),
550 (max_lox_psi * 0.95, max_fuel_psi * 0.55),
551 (max_lox_psi * 0.92, max_fuel_psi * 0.48),
552 (max_lox_psi * 0.88, max_fuel_psi * 0.62),
553 (max_lox_psi * 0.78, max_fuel_psi * 0.42),
554 (P_O_hi_l1, max_fuel_psi * 0.50),
557 "asymmetric: large oxidizer / small fuel grid (explicit O–F imbalance) + tank pairs "
558 "favoring high LOX / moderate–low fuel feed pressure."
560 return n_O, n_F, d_O, d_F, tank_grid, desc
562 raise ValueError(f
"unknown scenario {name!r}")
810def main(argv: Optional[Sequence[str]] =
None) ->
None:
811 parser = argparse.ArgumentParser(description=__doc__)
815 help=
"Smaller grid (~few hundred evals)",
821 help=
"How many top rows to print per category",
826 help=
"After main grid (YAML tanks), add Layer-1 tank-box corners on a sparse geometry grid (~hundreds of evals).",
831 default=
"configs/impinging_lox_ch4_8000N.yaml",
832 help=
"Path to engine YAML (relative to repo root or absolute).",
837 choices=(
"baseline",
"widen_ox",
"tighten_fuel",
"expand_tanks",
"asymmetric"),
840 "baseline: symmetric Layer-1-style grid (+ optional --tank-sweep). "
841 "Other scenarios relax/adjust injector O/F bounds or tank PSI grid only "
842 "(physics unchanged)."
845 args = parser.parse_args(argv)
847 cfg_path = Path(args.config)
848 if not cfg_path.is_absolute():
849 cfg_path = ROOT / cfg_path
850 base = load_config(cfg_path)
851 req = base.design_requirements.model_dump()
852 F_target = float(req.get(
"target_thrust", 4000.0))
853 MR_target = float(req.get(
"optimal_of_ratio", 3.2))
854 max_chamber_od = float(req.get(
"max_chamber_outer_diameter", 0.15))
855 max_lox_psi = float(req.get(
"max_lox_tank_pressure_psi")
or 700.0)
856 max_fuel_psi = float(req.get(
"max_fuel_tank_pressure_psi")
or 850.0)
858 D_inner_fb = impinging_chamber_inner_diameter_for_bounds(
860 max_chamber_outer_diameter_m=max_chamber_od,
861 wall_thickness_m=TOTAL_WALL_THICKNESS_M,
863 n_hi_int = impinging_n_elements_hi_int(D_inner_fb)
864 d_jet_hi = impinging_d_jet_upper_bound_m(D_inner_fb)
865 spacing_hi = impinging_spacing_upper_bound_m(D_inner_fb)
866 n_min, n_max = 6, int(n_hi_int)
869 P_O_yaml = float(base.lox_tank.initial_pressure_psi)
870 P_F_yaml = float(base.fuel_tank.initial_pressure_psi)
873 min_P_ratio, max_P_ratio = 0.65, 0.85
874 P_O_lo = max_lox_psi * min_P_ratio
875 P_O_hi = max_lox_psi * max_P_ratio
876 P_F_lo = max_fuel_psi * min_P_ratio
877 P_F_hi = max_fuel_psi * max_P_ratio
879 print(
"=== Impinging LOX/CH4 feasibility sweep ===")
880 print(f
"Config: {cfg_path}")
881 print(f
"Scenario: {args.scenario}")
882 print(f
"Frozen chamber/throat/nozzle: from YAML (A_throat frozen_parameters / chamber_geometry).")
884 f
"Targets: F={F_target:.0f} N, MR={MR_target:.3f}, "
885 f
"R in [{R_BAND_LO:.3f},{R_BAND_HI:.3f}], "
886 f
"ΔP_inj/Pc per stream in [{DP_BAND_LO:.2f}, {DP_BAND_HI:.2f}]"
889 f
"Full mission hit (count): |F−F_target|/F_target ≤ {MISSION_F_REL:.0%}, "
890 f
"|MR−MR_target|/MR_target ≤ {MISSION_MR_REL:.0%}, plus R band and ΔP band."
893 f
"Injector face inner Ø (bounds/packing ref): {D_inner_fb*1000:.2f} mm "
894 f
"({D_inner_fb / 0.0254:.3f} in bore)"
897 f
"Scaled injector bounds (Layer-1 class): n_elements ≤ {n_hi_int}, "
898 f
"d_jet ≤ {d_jet_hi*1000:.2f} mm, spacing ≤ {spacing_hi*1000:.2f} mm "
899 f
"(packing uses face_a = π·D_inner²/4 from runner geometry)."
901 print(f
"Layer-1 reference n_hi≈{float(n_hi_int):.1f} (oxidizer scenarios may exceed n_hi).")
902 print(f
"Tank bands (Layer-1 ref): LOX [{P_O_lo:.1f}, {P_O_hi:.1f}] psi, fuel [{P_F_lo:.1f}, {P_F_hi:.1f}] psi")
903 print(f
"YAML tanks: LOX={P_O_yaml:.1f} psi, fuel={P_F_yaml:.1f} psi")
908 if args.scenario ==
"baseline":
912 {round(x, 6)
for x
in [0.0010, 0.0015, 0.0020, 0.0025,
min(0.0030, d_jet_hi)]}
914 tank_grid = [(P_O_yaml, P_F_yaml)]
917 d_hi_use = float(
min(
max(d_jet_hi, 0.0036), 0.0075))
918 d_list = np.linspace(0.0008, d_hi_use, num=6, dtype=float).tolist()
919 tank_grid = [(P_O_yaml, P_F_yaml)]
920 total_main = len(n_list) ** 2 * len(d_list) ** 2 * len(tank_grid)
921 print(f
"Main grid evaluations (symmetric): {total_main}")
926 (P_O_yaml, P_F_yaml),
931 ((P_O_lo + P_O_hi) / 2, (P_F_lo + P_F_hi) / 2),
934 d_hi_sparse = float(
min(
max(d_jet_hi, 0.0034), 0.0075))
935 d_sparse = np.linspace(0.001, d_hi_sparse, num=4, dtype=float).tolist()
936 extra = len(n_sparse) ** 2 * len(d_sparse) ** 2 * len(tank_corners)
937 print(f
"Tank-sweep add-on evaluations: {extra}")
940 "baseline: symmetric Layer-1-class injector bounds; optional --tank-sweep adds Layer-1 tank corners."
950 max_lox_psi=max_lox_psi,
951 max_fuel_psi=max_fuel_psi,
956 d_jet_upper_m=d_jet_hi,
960 len(n_O) * len(n_F) * len(d_O) * len(d_F) * len(tank_grid)
962 print(f
"Scenario grid evaluations: {total_main}")
963 print(f
" n_O ∈ {min(n_O)}…{max(n_O)} ({len(n_O)} pts), n_F ∈ {min(n_F)}…{max(n_F)} ({len(n_F)} pts)")
965 f
" d_O ∈ [{min(d_O)*1000:.2f},{max(d_O)*1000:.2f}] mm ({len(d_O)} pts), "
966 f
"d_F ∈ [{min(d_F)*1000:.2f},{max(d_F)*1000:.2f}] mm ({len(d_F)} pts)"
968 print(f
" tank combos: {len(tank_grid)}")
971 scenario_footer = scen_desc
984 scenario_footer=scenario_footer,