1"""A blank doublet design flows its holes as the drilled passages the layout draws.
3The layout drills every doublet hole as an L/d-4 passage (SP-8089 3.1.2.2: "an orifice L/d of
4at least 4"), so a design started from configs/default.yaml (what a new session loads) or
5configs/canonical/impinging.yaml (what an injector switch loads) must use that passage's Cd. A
6sharp-entry drilled hole reattaches inside the bore: Lichtarowicz, Duggins & Markland (1965),
7Cd_u = 0.827 - 0.0085 L/d for 2 <= L/d <= 10, i.e. 0.793 at L/d 4. Both blank configs used to
8fall through to the thin-plate diameter model, 0.60 x (d / 2 mm)^0.2 = 0.566 at 1.5 mm: holes
9sized on it come out ~1.4x too large in area and run at half the design injector dp.
11That small-hole exponent also had the wrong sign. ISO 5167 and Sutton & Biblarz Table 8-2 both
12have small sharp holes flowing slightly more, not less; the thin-plate path is now the flat
130.60 asymptote below d_ref.
15Expected values are the published fit and an energy balance built on ``fluids``, not the module.
17from __future__
import annotations
20from pathlib
import Path
27ROOT = Path(__file__).resolve().parents[1]
28BLANKS = [
"configs/default.yaml",
"configs/canonical/impinging.yaml"]
31@pytest.fixture(autouse=True, scope="module")
33 """The authoritative Python path; engine/accel mirrors it and test_numba_ab_parity holds the
35 from engine
import accel
36 real = accel.enabled, accel.require
37 accel.enabled =
lambda:
False
38 accel.require =
lambda:
False
42 accel.enabled, accel.require = real
46 return 0.827 - 0.0085 * ld
50 """Energy balance through a short tube, Cd = (1 + K_entrance + f L/d)^-1/2, with the sharp
51 entrance K from Rennels (0.57) and Idelchik (0.5) and a smooth-wall Colebrook f."""
52 from fluids.fittings
import entrance_sharp
53 from fluids.friction
import friction_factor
54 f = friction_factor(Re=Re, eD=0.0)
55 cds = [1.0 / math.sqrt(1.0 + entrance_sharp(method=m) + f * ld)
for m
in (
"Rennels",
"Idelchik")]
59@pytest.mark.parametrize("path", BLANKS)
60@pytest.mark.parametrize("side", ["oxidizer", "fuel"])
64 cfg = switch_config(load_config(str(ROOT / path)).model_dump(mode=
"json"), propellant_preset=
"ethalox")
66 cd = cd_inf_from_orifice_diameter(1.5e-3, dc)
68 assert lo <= cd <= hi, f
"{path} {side}: Cd {cd:.4f} outside the short-tube energy balance {lo:.4f}-{hi:.4f}"
70 f
"{path} {side}: Cd {cd:.4f}; a sharp L/d-4 hole is {_lichtarowicz(4.0):.4f} (Lichtarowicz 1965)")
74 """One number drives the drawing and the Cd: the layout's passage L/d is the discharge L/d."""
78 dis = yaml.safe_load((ROOT / path).read_text())[
"discharge"]
79 for side
in (
"oxidizer",
"fuel"):
80 assert dis[side][
"inlet_geometry"] ==
"sharp"
81 assert dis[side][
"orifice_l_over_d"] == ORIFICE_LD_MIN
84@pytest.mark.parametrize("ld", [2.0, 4.0, 5.0, 8.0, 10.0])
86 """No plateau over L/d 2-5: the default is Lichtarowicz, not the piecewise curve."""
87 dc =
DischargeConfig(Cd_inf=0.6, a_Re=0.18, inlet_geometry=
"sharp", orifice_l_over_d=ld)
88 assert cd_inf_from_inlet_geometry(dc) == pytest.approx(
_lichtarowicz(ld), abs=1e-9)
93 dc =
DischargeConfig(Cd_inf=0.6, a_Re=0.18, inlet_geometry=
"sharp", orifice_l_over_d=13.0)
94 with assumptions.scope()
as used:
95 cd_inf_from_inlet_geometry(dc)
96 assert any(
"orifice_l_over_d" in k
for k
in used), used
99@pytest.mark.parametrize("d_mm", [0.8, 1.0, 1.5, 1.9])
101 """Below d_ref the thin-plate path is the 0.60 sharp-orifice asymptote (ISO 5167 C ~ 0.60-0.62),
102 not 0.60 x (d/2 mm)^0.2."""
104 assert 0.60 - 1e-12 <= cd_inf_from_orifice_diameter(d_mm * 1e-3, dc) <= 0.62
108 """An impinging config that still declares no inlet is told its Cd is a thin plate."""
113 base = load_config(str(ROOT /
"configs/ethalox_6500N.yaml")).model_dump()
114 base[
"discharge"] = {s: {**base[
"discharge"][s],
"inlet_geometry":
None,
"orifice_l_over_d":
None}
115 for s
in (
"oxidizer",
"fuel")}
116 cfg = PintleEngineConfig.model_validate(base)
118 with assumptions.scope()
as used:
119 ImpingingInjector(cfg).solve(584.27 * PSI, 584.27 * PSI, 430.0 * PSI)
120 assert "discharge.oxidizer.inlet_geometry" in used
and "discharge.fuel.inlet_geometry" in used, used
test_default_length_model_is_the_published_fit(ld)
_short_tube_band(float ld, float Re=3.0e5)
test_past_the_fit_is_recorded_not_silent()
test_blank_hole_ld_is_the_one_the_layout_draws()
float _lichtarowicz(float ld)
test_blank_doublet_gets_the_drilled_hole_cd(path, side)
test_impinging_solve_on_the_thin_plate_path_says_so()
test_thin_plate_path_has_no_small_hole_penalty(d_mm)