32 """The co-location term charged (|D_pitch_O - D_pitch_F| / bore)^2, driving the ring gap --
33 and the impingement standoff dr / (tan th_O + tan th_F) -- to zero."""
35 kw = dict(D_chamber_inner_m=bore, L_chamber_m=0.15)
36 apart = L1._impinging_geometry_fit_squared({
"D_pitch_O": 0.060,
"D_pitch_F": 0.090}, **kw)
37 together = L1._impinging_geometry_fit_squared({
"D_pitch_O": 0.075,
"D_pitch_F": 0.075}, **kw)
38 assert apart == together == 0.0
40 out = L1._impinging_geometry_fit_squared({
"D_pitch_O": 0.060,
"D_pitch_F": 0.1397}, **kw)
41 assert out == pytest.approx((0.1397 / bore - 1.0) ** 2, rel=1e-12)
59 return dict(mdot_O=1.80, mdot_F=1.13, rho_O=1141.0, rho_F=789.0, n_elements=24,
60 d_jet_O_m=0.00163, d_jet_F_m=0.00147, angle_O_deg=40.0, angle_F_deg=40.0)
64 """LOX outboard (s_O > s_F) travels INWARD to the collision, so a LOX-heavy pair leans
65 inward -- whatever layer1_ring_order_fuel_outboard requested."""
67 lox_in = L1._impinging_resultant_tilt_deg(**kw, lox_inboard=
True)
69 lox_out_geom = L1._impinging_resultant_tilt_deg(
70 **kw, lox_inboard=
True, spacing_O_m=0.0118, spacing_F_m=0.0091)
71 assert lox_out_geom == pytest.approx(-lox_in, rel=1e-12)
72 lox_in_geom = L1._impinging_resultant_tilt_deg(
73 **kw, lox_inboard=
False, spacing_O_m=0.0091, spacing_F_m=0.0118)
74 assert lox_in_geom == pytest.approx(lox_in, rel=1e-12)
78 """Break-even takes theta of the inner ring (smaller D_pitch); the tilt's sign must use the
79 same ring. Swapping the spacings swaps both."""
81 for sO, sF
in ((0.0091, 0.0118), (0.0118, 0.0091)):
82 inner_is_O = n * sO / math.pi <= n * sF / math.pi
83 assert L1._lox_ring_inboard(sO, sF)
is inner_is_O
84 be_O = L1._resultant_tilt_breakeven_deg(
85 n_elements=n, spacing_O_m=sO, spacing_F_m=sF, angle_O_deg=30.0, angle_F_deg=50.0,
86 D_chamber_inner_m=0.127, L_chamber_m=0.15)
87 dr = 0.5 * abs(n * (sO - sF) / math.pi)
88 L_imp = dr / (math.tan(math.radians(30.0)) + math.tan(math.radians(50.0)))
89 th_in = 30.0
if inner_is_O
else 50.0
90 r_imp = 0.5 * n *
min(sO, sF) / math.pi + L_imp * math.tan(math.radians(th_in))
91 assert be_O == pytest.approx(math.degrees(math.atan2(0.0635 - r_imp, 0.15)), rel=1e-12)
92 t = L1._impinging_resultant_tilt_deg(**
_tilt_kw(), spacing_O_m=sO, spacing_F_m=sF)
93 assert (t > 0)
is inner_is_O
101 """Roark Table 11.2, uniform load: simply supported k = 3(3+nu)/8 (centre), fixed k = 3/4
102 (edge), sigma_max = k p a^2 / t^2."""
103 assert L1._closure_plate_stress_coeff(
"simply_supported", 0.3) == pytest.approx(1.2375, rel=1e-12)
104 assert L1._closure_plate_stress_coeff(
"simply_supported", 0.25) == pytest.approx(3 * 3.25 / 8)
105 assert L1._closure_plate_stress_coeff(
"clamped") == 0.75
106 assert L1._closure_plate_stress_coeff(
"fixed") == 0.75
107 with pytest.raises(ValueError):
108 L1._closure_plate_stress_coeff(
"bolted")
112 a_bore, a_plug, p, sig, rho, wall = 0.0635, 0.0762, 2.2e6, 205e6, 3400.0, 0.0381
113 A_c = math.pi * a_bore ** 2
114 barrel = L1._layer1_chamber_mass_kg(A_c, 0.1, wall, rho, Pc_pa=0.0)
115 m = L1._layer1_chamber_mass_kg(A_c, 0.1, wall, rho, Pc_pa=p,
116 plate_support=
"simply_supported", r_plate_m=a_plug)
117 t = a_plug * math.sqrt(3.0 * 3.3 / 8.0 * p / sig)
118 assert m - barrel == pytest.approx(math.pi * a_plug ** 2 * t * rho, rel=1e-12)
120 m0 = L1._layer1_chamber_mass_kg(A_c, 0.1, wall, rho, Pc_pa=p)
121 t0 = a_bore * math.sqrt(0.75 * p / sig)
122 assert m0 - barrel == pytest.approx(math.pi * a_bore ** 2 * t0 * rho, rel=1e-12)
127 bare = SimpleNamespace(injector=SimpleNamespace(plate=SimpleNamespace(
128 model_dump=
lambda: {
"back":
"channels"})), discharge={})
129 assert L1._layer1_injector_plate_constants(bare, req)[
"layer1_injector_plate_support"] ==
"simply_supported"
130 declared = SimpleNamespace(injector=SimpleNamespace(plate=SimpleNamespace(
131 support=
"clamped", model_dump=
lambda: {
"back":
"channels",
"support":
"clamped"})), discharge={})
132 assert L1._layer1_injector_plate_constants(declared, req)[
"layer1_injector_plate_support"] ==
"clamped"
139@pytest.fixture(scope="module")
170 """1/2 NPT: L2 = 0.5337 in = 13.56 mm (ASME B1.20.1) against a 0.500 in (12.70 mm) plate."""
171 cfg, req =
_load(tmp_path)
172 assert req[
"layer1_injector_plate_thickness_m"] == pytest.approx(0.5 * IN)
173 ok, msg = L1._layer1_declared_limit_gates(cfg, req, {})[
"igniter_engagement"]
174 assert not ok
and "13.56 mm" in msg
and "12.70 mm" in msg
177 d[
"injector"][
"igniter"][
"hub_thickness"] = 0.014
178 cfg, req =
_load(tmp_path, hub)
179 ok, _ = L1._layer1_declared_limit_gates(cfg, req, {})[
"igniter_engagement"]
190 cfg, req =
_load(tmp_path)
191 gates = L1._layer1_declared_limit_gates(cfg, req, {}, performance=solved)
192 ok_O, msg_O = gates[
"manifold_q_O"]
193 assert not ok_O
and "1 feed port" in msg_O
194 dg = solved[
"diagnostics"]
195 flows = {
"mdot_O": solved[
"mdot_O"],
"mdot_F": solved[
"mdot_F"],
196 "dp_O": dg[
"delta_p_injector_O"],
"dp_F": dg[
"delta_p_injector_F"]}
197 lay = layout_from_config(yaml.safe_load(Y6500.read_text()), drawings=
False, flows=flows)
198 A_O = lay[
"passages"][
"O"][
"channel"][
"flow_area"]
199 assert A_O * 1e6 == pytest.approx(19.3, abs=0.5)
200 assert f
"channel {A_O * 1e6:.1f} mm^2" in msg_O
201 rho_O = cfg.fluids[
"oxidizer"].density
202 q =
_q_hand(A_O, solved[
"mdot_O"], rho_O, dg[
"delta_p_injector_O"], 1)
203 assert q > MANIFOLD_Q_FRAC
204 assert lay[
"passages"][
"O"][
"channel"][
"q_over_dp"] == pytest.approx(q, rel=1e-9)
207 d[
"injector"][
"plate"][
"channel_inlets"] = 4
208 cfg4, req4 =
_load(tmp_path, four)
209 gates4 = L1._layer1_declared_limit_gates(cfg4, req4, {}, performance=solved)
210 assert gates4[
"manifold_q_O"][0]
and gates4[
"manifold_q_F"][0]
211 assert _q_hand(A_O, solved[
"mdot_O"], rho_O, dg[
"delta_p_injector_O"], 4) == pytest.approx(q / 16)
215 cfg, req =
_load(tmp_path)
216 c = L1._layer1_injector_plate_constants(cfg, req)
217 g = cfg.injector.geometry
218 dg = solved[
"diagnostics"]
219 kw = dict(n_elements=g.oxidizer.n_elements, spacing_O_m=g.oxidizer.spacing,
220 spacing_F_m=g.fuel.spacing, d_jet_O_m=g.oxidizer.d_jet, d_jet_F_m=g.fuel.d_jet,
221 angle_O_deg=g.oxidizer.impingement_angle, angle_F_deg=g.fuel.impingement_angle,
222 plate_thickness_m=c[
"layer1_injector_plate_thickness_m"],
223 face_contoured=c[
"layer1_injector_face_contoured"],
224 exit_land=c[
"layer1_injector_exit_land"],
225 passage_ld_O=c[
"layer1_injector_land_ld_O"], passage_ld_F=c[
"layer1_injector_land_ld_F"],
226 channel_width_m=c[
"layer1_injector_channel_width_m"],
227 channel_floor=c[
"layer1_injector_channel_floor"],
228 mdot_O=solved[
"mdot_O"], mdot_F=solved[
"mdot_F"],
229 dp_O=dg[
"delta_p_injector_O"], dp_F=dg[
"delta_p_injector_F"],
230 rho_O=cfg.fluids[
"oxidizer"].density, rho_F=cfg.fluids[
"fuel"].density)
231 q1 = L1._impinging_manifold_q(**kw, channel_inlets=1)
232 v1 = L1._impinging_manifold_violation(q1)
233 hand = sum(
max(0.0, h[
"area_needed"] / h[
"flow_area"] - 1.0) ** 2
for h
in q1.values())
234 assert v1 == pytest.approx(hand, rel=1e-12)
and v1 > 0.0
235 assert L1._impinging_manifold_violation(L1._impinging_manifold_q(**kw, channel_inlets=4)) == 0.0
237 assert L1._impinging_manifold_q(**dict(kw, mdot_O=
None, mdot_F=
None), channel_inlets=1) == {}