44 """Every limit is the one THIS config declares (Layer 1's default where it declares none).
46 An earlier version hardcoded the SHIP numbers -- densities 1140/789, dP/Pc 0.20-0.40,
47 n <= 30, 90 deg, 40 deg -- so it audited every other design against SHIP's requirements.
48 A limit the config does not declare and Layer 1 has no default for is skipped and said so.
50 c = load_config(f); Y = yaml.safe_load(open(f))
51 g = Y[
'injector'][
'geometry']; cg = Y[
'chamber_geometry']; rq = Y.get(
'design_requirements')
or {}
53 PO = c.lox_tank.initial_pressure_psi*6894.757; PF = c.fuel_tank.initial_pressure_psi*6894.757
54 r = run.evaluate(PO, PF, silent=
True)
55 O, F = g[
'oxidizer'], g[
'fuel']; n = int(O[
'n_elements'])
56 thO, thF = float(O[
'impingement_angle']), float(F[
'impingement_angle'])
57 bore = cg[
'chamber_diameter']; Lch = cg[
'length_cylindrical']+cg[
'length_contraction']
58 rho_O = float(c.fluids[
'oxidizer'].density); rho_F = float(c.fluids[
'fuel'].density)
59 tilt = _impinging_resultant_tilt_deg(r[
'mdot_O'], r[
'mdot_F'], rho_O, rho_F, n,
60 O[
'd_jet'], F[
'd_jet'], thO, thF)
61 be = _resultant_tilt_breakeven_deg(n_elements=n, spacing_O_m=O[
'spacing'],
62 spacing_F_m=F[
'spacing'], angle_O_deg=thO,
63 angle_F_deg=thF, D_chamber_inner_m=bore, L_chamber_m=Lch)
64 allow = _resolve_tilt_allowance_deg(
65 from_reach=
_flag(rq,
'layer1_resultant_tilt_from_reach',
False),
66 constant_deg=
_opt(rq,
'layer1_resultant_tilt_max_deg', 0.0),
67 breakeven_deg=be, margin=
_opt(rq,
'layer1_resultant_tilt_reach_margin', 1.5))
68 plate = _layer1_plate(c)
69 contoured = plate.get(
'face') ==
'contoured'
71 inc_min = 0.0
if contoured
else _opt(rq,
'layer1_injector_min_face_incidence_deg', 0.0)
72 face = _impinging_face_infeasibility_terms(n_elements=float(n),
73 spacing_O_m=O[
'spacing'], spacing_F_m=F[
'spacing'], d_jet_O_m=O[
'd_jet'],
74 d_jet_F_m=F[
'd_jet'], D_chamber_inner_m=bore,
75 angle_O_deg=thO, angle_F_deg=thF,
76 center_clear_dia_m=_layer1_centre_clear_m(c, rq),
77 min_web_m=
_opt(rq,
'layer1_injector_min_web_m', 0.0),
78 wall_clearance_m=
_opt(rq,
'layer1_injector_wall_clearance_m', 0.0),
79 spray_radius_frac=
_opt(rq,
'layer1_injector_spray_radius_frac', 0.0),
80 spray_radius_tol=
_opt(rq,
'layer1_injector_spray_radius_tol', 0.08),
81 min_face_incidence_deg=inc_min, face_contoured=contoured,
82 exit_land=plate.get(
'exit_land')
if plate.get(
'exit_land')
is not None else EXIT_LAND_DEFAULT)
86 dpo, dpf = _d[
'delta_p_injector_O']/r[
'Pc'], _d[
'delta_p_injector_F']/r[
'Pc']
87 dpo_lo =
_opt(rq,
'injector_dp_ratio_O_min', _LAYER1_DEFAULT_DP_O_BAND[0])
88 dpo_hi =
_opt(rq,
'injector_dp_ratio_O_max', _LAYER1_DEFAULT_DP_O_BAND[1])
89 dpf_lo =
_opt(rq,
'injector_dp_ratio_F_min', _LAYER1_DEFAULT_DP_F_BAND[0])
90 dpf_hi =
_opt(rq,
'injector_dp_ratio_F_max', _LAYER1_DEFAULT_DP_F_BAND[1])
94 F_tgt = float(rq.get(
'target_thrust')
or 0.0)
95 OF_tgt = float(rq.get(
'optimal_of_ratio')
or 0.0)
96 band = impingement_ld_band(rq)
97 tan_sum = math.tan(math.radians(thO)) + math.tan(math.radians(thF))
98 L_imp = 0.5 * abs(n*O[
'spacing'] - n*F[
'spacing']) / math.pi / tan_sum
99 ld = L_imp / (0.5*(O[
'd_jet'] + F[
'd_jet']))
100 ld_eps = 1e-6 *
max(1.0, band.hi)
103 lay = layout_from_config(Y, flows=flows_from_result(r, c))
104 back_web_req =
_opt(rq,
'layer1_injector_min_back_web_m')
105 channels = lay[
'back'][
'mode'] ==
'channels'
106 back_web =
min(lay[
'passages'][
'O'][
'back_web'], lay[
'passages'][
'F'][
'back_web'])
109 o_f_land = lay[
'back'][
'lands'][
'between']
if channels
else lay[
'back'][
'o_f_land']
112 n_max =
_opt(rq,
'layer1_impinging_n_doublets_max')
113 inc_lo =
_opt(rq,
'layer1_impinging_angle_deg_min')
114 inc_hi =
_opt(rq,
'layer1_impinging_angle_deg_max')
115 jet_lo =
_opt(rq,
'layer1_impinging_jet_angle_min_deg')
123 gates = _layer1_declared_limit_gates(
124 c, rq, {
'layer1_contraction_half_angle_deg': rq.get(
'layer1_contraction_half_angle_deg')})
125 R = float((r.get(
'diagnostics')
or {}).get(
'momentum_ratio_R', float(
'nan')))
126 r_lo, r_hi =
_opt(rq,
'impinging_momentum_R_min'),
_opt(rq,
'impinging_momentum_R_max')
128 (f
"thrust {F_tgt:.0f} +/-2%",
129 F_tgt > 0
and abs(r[
'F']-F_tgt)/F_tgt <= 0.02,
130 f
"{r['F']:.1f} N" + (
"" if F_tgt > 0
else " (no target_thrust in config)")),
131 (f
"O/F {OF_tgt:.3f} +/-5%",
132 OF_tgt > 0
and abs(r[
'MR']-OF_tgt)/OF_tgt <= 0.05,
133 f
"{r['MR']:.4f}" + (
"" if OF_tgt > 0
else " (no optimal_of_ratio in config)")),
134 (f
"dP/Pc O in [{dpo_lo:g},{dpo_hi:g}]", dpo_lo <= dpo <= dpo_hi, f
"{dpo:.3f}"),
135 (f
"dP/Pc F in [{dpf_lo:g},{dpf_hi:g}]", dpf_lo <= dpf <= dpf_hi, f
"{dpf:.3f}"),
136 (f
"n <= {n_max:g}" if n_max
else "n <= (unset)",
137 SKIP
if n_max
is None else n <= n_max, f
"{n}"),
138 (f
"included in [{inc_lo or 0:g},{inc_hi or 180:g}]",
139 SKIP
if inc_lo
is None and inc_hi
is None
140 else (inc_lo
or 0.0) <= thO + thF <= (inc_hi
or 180.0), f
"{thO+thF:.1f} deg"),
141 (f
"jet >= {jet_lo:g} deg" if jet_lo
else "jet >= (unset)",
142 SKIP
if jet_lo
is None else min(thO, thF) >= jet_lo, f
"{thO:.1f}/{thF:.1f}"),
143 (f
"incidence >= {inc_min:g} deg" if not contoured
else "incidence (contoured face: square exits)",
144 SKIP
if not inc_min
else 90 -
max(thO, thF) >= inc_min, f
"{90-max(thO, thF):.1f} deg"),
145 (f
"standoff L/d in [{band.lo:g},{band.hi:g}]",
146 band.lo - ld_eps <= ld <= band.hi + ld_eps, f
"{ld:.4f}"),
147 (
"face limits all met", face <= 1e-12, f
"{face:.2e}"),
148 (f
"back-face web >= {back_web_req * 1000:g} mm" if back_web_req
else "back-face web >= (unset)",
149 SKIP
if not back_web_req
else back_web >= back_web_req, f
"{back_web * 1000:.2f} mm"),
150 (
"LOX/fuel entries separate", o_f_land >= 0.0, f
"{o_f_land * 1000:.2f} mm land"),
151 (
"tilt <= allowed (NO slack)", tilt <= allow, f
"{tilt:+.3f} vs {allow:.3f}"),
152 (f
"momentum R in [{r_lo:g},{r_hi:g}]" if r_lo
is not None and r_hi
is not None
153 else "momentum R band (unset)",
154 SKIP
if r_lo
is None or r_hi
is None else _layer1_momentum_gate(R, r_lo, r_hi)[0],
156 *[(f
"L1 gate: {k}", ok, msg)
for k, (ok, msg)
in gates.items()
157 if k
in (
"element_pitch",
"lcyl_over_d",
"engine_length",
"free_jet")],
158 (
"layout: nothing impossible",
159 not [w
for w
in lay[
'warnings']
if w[
'level'] ==
'bad'],
160 "; ".join(w[
'code']
for w
in lay[
'warnings']
if w[
'level'] ==
'bad')
or "none"),
162 bad = [c
for c
in checks
if c[1]
is False]
163 skipped = [c[0]
for c
in checks
if c[1]
is None]
164 print(f
"{f.split('/')[-1]:34s} n={n:2d} Isp {r['Isp']:.2f} O/F {r['MR']:.4f} "
165 f
"{'CLEAN' if not bad else 'FAILS: ' + ', '.join(c[0] for c in bad)}")
166 for name, ok, val
in checks:
167 if ok
is False: print(f
" {name:30s} {val}")
169 print(f
" not declared, not checked: {', '.join(skipped)}")
171 for w
in lay[
'warnings']:
172 print(f
" drawing {w['level']:4s}: {w['text']}")