1"""Orifice L/d from the plate as built, and the Cd it gives (docs/injector_face_and_manifold_plan.md, phase 6).
3The discharge block used to take L/d as a typed number while the plate thickness, hole angle
4and counterbore that decide it lived elsewhere. On SHIP the declared 4.0 is right only
5because a 4 mm counterbore is drilled; skip it and the small drill runs L/d 9.7 (LOX) and
613.2 (fuel), Cd falls to 0.745 / 0.715, and the engine makes 3.1 % less thrust at O/F 1.70.
8from __future__
import annotations
12from contextlib
import contextmanager
13from pathlib
import Path
18 LICHTAROWICZ_A, LICHTAROWICZ_B, cd_inf_from_inlet_geometry, cd_length_factor, cd_with_approach,
22ROOT = Path(__file__).resolve().parents[1]
24SHIP = ROOT /
"configs/ethalox_8kN_SHIP.yaml"
27def _ship(src=None, model=None, counterbore="keep"):
30 c = load_config(str(SHIP))
33 c.injector.plate = InjectorPlateConfig(face=
"flat", back=
"plenum")
34 for side
in (
"oxidizer",
"fuel"):
36 c.discharge[side].l_over_d_source = src
38 c.discharge[side].length_model = model
39 if counterbore !=
"keep":
40 c.design_requirements.layer1_injector_counterbore_dia_m = counterbore
48 assert effective_discharge(c,
"oxidizer")
is c.discharge[
"oxidizer"]
53 assert cd_length_factor(4.0,
"piecewise") == 1.0
54 assert cd_length_factor(10.0,
"piecewise") == pytest.approx(0.94)
55 assert cd_length_factor(0.0) == 1.0
56 assert cd_with_approach(0.8,
None) == 0.8
61@pytest.mark.parametrize("ld", [2.0, 4.0, 7.0, 10.0])
63 """Sharp inlet (table 0.80) x factor must equal Cd_u = 0.827 - 0.0085 L/d exactly."""
64 assert 0.80 * cd_length_factor(ld,
"lichtarowicz") == pytest.approx(0.827 - 0.0085 * ld, rel=1e-12)
68 assert 0.80 * cd_length_factor(1e-9,
"lichtarowicz") == pytest.approx(0.61, abs=1e-6)
70 assert cd_length_factor(2.0 - 1e-9,
"lichtarowicz") == pytest.approx(
71 cd_length_factor(2.0,
"lichtarowicz"), abs=1e-8)
77 """Plenum -> counterbore (entrance K) -> orifice; solve the drops for a mass flow, compare."""
78 cd, beta, K = 0.80, 0.45, 0.5
79 rho, A, dp_total = 1000.0, 1e-6, 1e6
83 v = math.sqrt(2 * dp_total / (rho * ((1 - beta ** 4) / cd ** 2 + beta ** 4 * (1 + K))))
85 cd_eff = mdot / (A * math.sqrt(2 * rho * dp_total))
86 assert cd_with_approach(cd, beta, K) == pytest.approx(cd_eff, rel=1e-12)
92 rl = realized_land(d=0.0017094, theta_deg=40.0, plate_thickness=0.0127,
93 counterbore_dia=0.0, declared_land_ld=4.0)
94 assert rl[
"land_ld"] == pytest.approx(0.0127 / math.cos(math.radians(40)) / 0.0017094)
95 assert rl[
"beta"]
is None
99 rl = realized_land(d=0.0017094, theta_deg=40.0, plate_thickness=0.0127,
100 counterbore_dia=0.004, declared_land_ld=4.0)
101 assert rl[
"land_ld"] == pytest.approx(4.0)
102 assert rl[
"beta"] == pytest.approx(0.0017094 / 0.004)
106 rl = realized_land(d=0.001, theta_deg=0.0, plate_thickness=0.003,
107 counterbore_dia=0.004, declared_land_ld=10.0)
108 assert rl[
"land_ld"] == pytest.approx(3.0)
and rl[
"beta"]
is None
112 c =
_ship(src=
"plate", model=
"lichtarowicz", counterbore=
None)
113 eO, eF = effective_discharge(c,
"oxidizer"), effective_discharge(c,
"fuel")
114 assert eO.orifice_l_over_d == pytest.approx(9.70, abs=0.01)
115 assert eF.orifice_l_over_d == pytest.approx(13.15, abs=0.01)
116 assert cd_inf_from_inlet_geometry(eF) < cd_inf_from_inlet_geometry(eO) < 0.80
123 from engine
import accel
124 real_enabled, real_require = accel.enabled, accel.require
125 accel.enabled =
lambda:
False
126 accel.require =
lambda:
False
130 accel.enabled, accel.require = real_enabled, real_require
133@pytest.mark.parametrize("counterbore", ["keep", None], ids=["counterbored", "bare"])
135 from engine
import accel
137 if not accel.available():
138 pytest.skip(
"numba unavailable")
139 c =
_ship(src=
"plate", model=
"lichtarowicz", counterbore=counterbore)
140 runner = PintleEngineRunner(c)
141 P = (c.lox_tank.initial_pressure_psi * PSI, c.fuel_tank.initial_pressure_psi * PSI)
144 ref = runner.evaluate(*P, P_ambient=101325.0, silent=
True)
147 full = accel.evaluate(c, runner.cea_cache, *P, 101325.0)
148 assert full
is not None
149 assert full[
"F"] == pytest.approx(ref[
"F"], rel=1e-6)
150 got = accel.solve(c, *P, ref[
"Pc"])
151 assert got
is not None
152 mO, mF, _ = get_injector_model(c).solve(*P, ref[
"Pc"])
153 assert got[0] == pytest.approx(mO, rel=1e-9)
154 assert got[1] == pytest.approx(mF, rel=1e-9)
155 assert mO == pytest.approx(ref[
"mdot_O"], rel=1e-6)
159 base = PintleEngineRunner(
_ship()).evaluate(*P, P_ambient=101325.0, silent=
True)
160 if counterbore
is None:
161 assert abs(ref[
"F"] - base[
"F"]) / base[
"F"] > 1e-3
163 assert ref[
"F"] == pytest.approx(base[
"F"], rel=1e-3)
172 assert contraction_coefficient(0.0) == pytest.approx(0.62)
173 assert contraction_coefficient(0.1) == pytest.approx((1 / 0.62 ** 2 - 1.14) ** -0.5)
174 assert contraction_coefficient(1.0) == pytest.approx(0.98)
178 m = cavitation_margin(P_in=4.0e6, Pc=3.0e6, Pv=1.0e5, Cd=0.80)
179 assert m[
"K"] == pytest.approx((4.0e6 - 1.0e5) / 1.0e6)
180 assert m[
"K_crit"] == pytest.approx((0.80 / 0.62) ** 2)
181 assert m[
"margin"] == pytest.approx(m[
"K"] / m[
"K_crit"])
187 P = (c.lox_tank.initial_pressure_psi * PSI, c.fuel_tank.initial_pressure_psi * PSI)
188 cav = PintleEngineRunner(c).evaluate(*P, silent=
True)[
"injector_cavitation"]
189 assert cav[
"O"][
"margin"] > 2.0
and cav[
"F"][
"margin"] > 2.0
192 c.fluids[
"oxidizer"].vapor_pressure = 500 * PSI
193 cav = PintleEngineRunner(c).evaluate(*P, silent=
True)[
"injector_cavitation"]
195 assert cav[
"O"][
"margin"] == pytest.approx(1.0, abs=1e-6)
199 """Cavitation used to be reported only; a vena contracta at vapour pressure now caps Cd at
200 Cc sqrt(K) (Nurick 1976), so saturated LOX flows less and the engine makes less thrust."""
203 P = (c.lox_tank.initial_pressure_psi * PSI, c.fuel_tank.initial_pressure_psi * PSI)
204 base = PintleEngineRunner(c).evaluate(*P, silent=
True)
205 c.fluids[
"oxidizer"].vapor_pressure = 500 * PSI
206 warm = PintleEngineRunner(c).evaluate(*P, silent=
True)
207 assert warm[
"mdot_O"] < 0.9 * base[
"mdot_O"]
208 assert warm[
"F"] < base[
"F"]
test_lichtarowicz_reproduces_the_published_fit_for_a_sharp_inlet(ld)
test_ship_without_its_counterbore()
test_piecewise_default_is_unchanged()
test_a_counterbore_leaves_the_declared_land()
test_margin_is_k_over_cd_over_cc_squared()
test_a_cavitating_feed_loses_flow()
test_python_and_accelerator_agree_with_plate_ld(counterbore)
test_approach_by_independent_energy_balance()
test_contraction_coefficient()
test_no_counterbore_means_the_small_drill_runs_the_whole_passage()
test_declared_returns_the_block_itself()
_ship(src=None, model=None, counterbore="keep")
test_ship_is_clear_and_warm_lox_is_not()
test_lichtarowicz_keeps_the_thin_plate_anchor()
test_a_land_longer_than_the_passage_is_capped()