1"""Cd comes from the orifice INLET and its L/d, and it is a per-side design knob.
3Two things this pins down.
51. THE ANCHORS. The widely-quoted "sharp-edged orifice, Cd = 0.61" is the THIN-PLATE value
6 (L/d -> 0), where the jet leaves at the vena contracta and never recovers. The same sharp
7 entry on a normally drilled hole (L/d 2-5) reattaches inside the bore and recovers to
8 ~0.80. Conflating them under-predicts a real injector's flow by ~24%, which is the same
9 class of error as the Cd-reduction loop this codebase already had.
11 Sources: Huzel & Huang, "Modern Engineering for Design of Liquid-Propellant Rocket
12 Engines" (injector orifice Cd table); Lichtarowicz, Duggins & Markland (1965),
13 "Discharge Coefficients for Incompressible Non-Cavitating Flow through Long Orifices"
14 (Cd peaks near L/d ~ 2, Re-independent above ~1e4); Nurick (1976) on inlet condition and
172. IT IS A KNOB. `discharge.oxidizer` and `discharge.fuel` are separate config blocks, so
18 filleting only ONE side's inlet raises that propellant's Cd and moves the momentum ratio
19 / O/F without touching hole size, element count or angles.
21from __future__
import annotations
27 cd_from_inlet_radius_ratio,
29 cd_inf_from_inlet_geometry,
34_BASE = dict(Cd_inf=0.60, a_Re=0.18, Cd_min=0.35, use_geometry_cd=
True, d_ref_m=0.002,
35 cd_small_hole_exponent=0.20, cd_large_hole_log_gain=0.015,
36 cd_inf_max=0.62, cd_inf_min_geom=0.48)
39@pytest.mark.parametrize(
"r_over_d,l_over_d,expected,label", [
40 (0.00, 0.001, 0.61,
"thin plate, sharp"),
41 (0.00, 4.0, 0.80,
"drilled hole, sharp"),
42 (0.10, 4.0, 0.88,
"short tube, rounded entrance"),
45 cd = cd_from_inlet_radius_ratio(r_over_d) * cd_length_factor(l_over_d)
46 assert cd == pytest.approx(expected, abs=0.01), f
"{label}: got {cd:.4f}, published {expected}"
50 """The 24% gap is the whole point -- if these collapse, the model has lost the physics."""
51 thin = cd_from_inlet_radius_ratio(0.0) * cd_length_factor(0.001)
52 drilled = cd_from_inlet_radius_ratio(0.0) * cd_length_factor(4.0)
53 assert drilled / thin > 1.2,
"a drilled hole must flow materially more than a thin plate"
57 """Cd rises to a maximum near L/d ~ 2 and falls slowly after; it does not rise forever."""
58 assert cd_length_factor(2.0,
"piecewise") == pytest.approx(1.0)
59 assert cd_length_factor(4.0,
"piecewise") == pytest.approx(1.0)
60 assert cd_length_factor(2.0) > cd_length_factor(4.0)
61 assert cd_length_factor(0.5) < cd_length_factor(2.0)
62 assert cd_length_factor(12.0) < cd_length_factor(4.0)
66 """Past r/d ~ 0.2 rounding buys almost nothing -- why bellmouths are specified there."""
67 gain_early = cd_from_inlet_radius_ratio(0.10) - cd_from_inlet_radius_ratio(0.0)
68 gain_late = cd_from_inlet_radius_ratio(0.30) - cd_from_inlet_radius_ratio(0.20)
71 assert gain_early > 4.0 * gain_late
74@pytest.mark.parametrize("name", sorted(INLET_GEOMETRY_CD))
76 cfg =
DischargeConfig(**_BASE, inlet_geometry=name, orifice_l_over_d=4.0)
77 cd = cd_from_re(3.5e5, cfg, d_hyd_m=2.1e-3)
78 assert 0.55 < cd < 0.98
79 assert cd == pytest.approx(INLET_GEOMETRY_CD[name], abs=0.02)
83 """Opt-in: a config that declares no inlet geometry must behave exactly as before."""
85 assert cd_inf_from_inlet_geometry(cfg)
is None
86 assert cd_from_re(3.5e5, cfg, d_hyd_m=2.1e-3) == pytest.approx(0.60, abs=0.02)
90 """The knob: same hole, same dP, one side filleted -> that side flows measurably more."""
91 sharp = cd_from_re(3.5e5,
DischargeConfig(**_BASE, inlet_geometry=
"sharp",
92 orifice_l_over_d=4.0), d_hyd_m=2.1e-3)
93 filleted = cd_from_re(3.5e5,
DischargeConfig(**_BASE, inlet_radius_ratio=0.10,
94 orifice_l_over_d=4.0), d_hyd_m=2.1e-3)
95 gain = filleted / sharp - 1.0
96 assert 0.08 < gain < 0.14, f
"r/d 0.10 should buy ~10% flow, got {gain*100:.1f}%"
100 with pytest.raises(ValueError, match=
"Unknown inlet_geometry"):
101 cd_inf_from_inlet_geometry(
DischargeConfig(**_BASE, inlet_geometry=
"polished"))
test_length_factor_peaks_in_the_lichtarowicz_band()
test_published_anchors(r_over_d, l_over_d, expected, label)
test_unknown_geometry_is_loud()
test_thin_plate_and_drilled_hole_are_not_the_same_number()
test_every_named_geometry_is_reachable_and_ordered(name)
test_unset_leaves_the_old_path_untouched()
test_filleting_one_side_is_a_real_trim()
test_rounding_saturates()