1"""Injector discharge coefficient model Cd(Re, orifice geometry).
3A declared orifice inlet (``inlet_geometry`` / ``inlet_radius_ratio``) and L/d set Cd_inf: the
4short-tube table with the Lichtarowicz (1965) length fit, which is what a drilled doublet hole
5is (the shipped configs declare sharp, L/d 4). Without one, Cd_inf is the thin-plate
6sharp-orifice value (ASME / ISO 5167: Cd ≈ 0.60–0.61 at high Re), keyed to d_jet.
9from __future__
import annotations
12from typing
import Dict, Optional
19 d_hyd_m: Optional[float],
20 config: DischargeConfig,
22 """Asymptotic Cd at high Re for a circular injector hole.
24 A declared inlet (``inlet_geometry`` / ``inlet_radius_ratio``) wins: the short-tube value at
25 the hole's L/d (cd_inf_from_inlet_geometry). Otherwise this is a THIN-PLATE model:
26 - **d = d_ref** (default 2 mm): ``Cd_inf`` equals config baseline (default **0.60**),
27 matching thin-plate sharp-edged orifice data (Cd ≈ 0.595–0.602, Re > 10⁴).
28 - **d < d_ref**: ``(d/d_ref)^cd_small_hole_exponent``, default 0 (no change). ISO 5167 and
29 Sutton & Biblarz Table 8-2 have small sharp holes flowing slightly MORE; the old 0.2
30 penalty had the wrong sign.
31 - **d > d_ref**: small logarithmic rise toward ``cd_inf_max`` (≤ ~0.62).
32 - Result clamped to ``[cd_inf_min_geom, cd_inf_max]``.
33 A drilled hole of L/d 2-10 is not a thin plate (0.79 at L/d 4, Lichtarowicz 1965); this
34 path under-predicts its flow by ~24%.
36 When ``use_geometry_cd`` is False or diameter is missing, returns ``config.Cd_inf``.
43 if _cd_inlet
is not None:
44 return float(_cd_inlet)
45 if not getattr(config,
"use_geometry_cd",
False):
46 return float(config.Cd_inf)
47 if d_hyd_m
is None or not np.isfinite(float(d_hyd_m))
or float(d_hyd_m) <= 0.0:
48 return float(config.Cd_inf)
50 d_min = float(getattr(config,
"d_min_m", 4.0e-4))
51 d_ref = float(getattr(config,
"d_ref_m", 2.0e-3))
52 cd_base = float(config.Cd_inf)
53 exp_small = float(getattr(config,
"cd_small_hole_exponent", 0.20))
54 log_gain = float(getattr(config,
"cd_large_hole_log_gain", 0.015))
55 cd_max = float(getattr(config,
"cd_inf_max", 0.62))
56 cd_floor = float(getattr(config,
"cd_inf_min_geom", 0.48))
58 d = float(
max(d_min, float(d_hyd_m)))
60 return float(np.clip(cd_base, cd_floor, cd_max))
64 cd_geom = cd_base * (ratio **
max(0.0, exp_small))
66 cd_geom = cd_base + log_gain * math.log(ratio)
68 return float(np.clip(cd_geom, cd_floor, cd_max))
74 discharge_O: DischargeConfig,
75 discharge_F: DischargeConfig,
77 """High-Re Cd_inf,O / Cd_inf,F from orifice diameters (≈1 when jets share the same hole style)."""
82 return float(cd_o / cd_f)
87 config: DischargeConfig,
88 P_inlet: float =
None,
89 T_inlet: float =
None,
90 d_hyd_m: Optional[float] =
None,
93 Calculate discharge coefficient as function of Reynolds number, optional orifice
94 diameter, pressure, and temperature.
96 ``Re`` is on the bulk hole velocity mdot/(rho A) and the hole diameter.
98 Re law, when the block declares an inlet, an ``orifice_l_over_d`` and
99 ``length_model: lichtarowicz`` (the default) -- Lichtarowicz, Duggins & Markland (1965):
100 1/Cd = 1/Cd_u + 20 (1 + 2.25 L/d)/Re - 0.0015 (L/d) / (1 + 7.5 [log10(0.00015 Re)]^2)
101 with Cd_u the orifice's own high-Re Cd as this module anchors it (inlet table x
102 cd_length_factor, cd_u_from_inlet_geometry), then the counterbore approach
103 (approach_beta) and the 0.98 cap exactly as cd_inf_from_inlet_geometry applies them.
105 Otherwise (``length_model: piecewise``, or no declared inlet / L/d): the LEGACY, unsourced
106 form Cd(Re) = Cd_inf,eff - a_Re / √Re. ``a_Re`` is a tuned coefficient with no reference
107 and is ignored whenever the Lichtarowicz law applies.
109 ``Cd_inf,eff`` is ``cd_inf_from_orifice_diameter(d_hyd_m, config)`` when geometry
110 mode is enabled; otherwise ``config.Cd_inf``.
112 Corrections (unsourced; off by default):
113 - Pressure correction: Cd(P) = Cd(Re) × [1 + a_P × (P/P_ref - 1)]
114 - Temperature correction: Cd(T) = Cd(Re) × [1 + a_T × (T/T_ref - 1)]
116 Clamped to [Cd_min, Cd_inf,eff]
124 return float(config.Cd_min)
128 cd_u, lod, beta = lich
131 Cd = cd_inf_eff - config.a_Re / np.sqrt(
max(Re, 1e-6))
133 if config.use_pressure_correction
and P_inlet
is not None and config.P_ref > 0:
134 P_correction = 1.0 + config.a_P * (P_inlet / config.P_ref - 1.0)
137 if config.use_temperature_correction
and T_inlet
is not None and config.T_ref > 0:
138 T_correction = 1.0 + config.a_T * (T_inlet / config.T_ref - 1.0)
141 Cd = np.clip(Cd, config.Cd_min, cd_inf_eff)
152 Calculate Reynolds number.
154 Re = (ρ × u × d_hyd) / μ
159 Re = (rho * u * d_hyd) / mu
204INLET_GEOMETRY_CD: Dict[str, float] = {
208 "rounded_light": 0.85,
214INLET_GEOMETRY_RD: Dict[str, float] = {
215 "sharp": 0.0,
"chamfered": 0.03,
"conical": 0.04,
216 "rounded_light": 0.05,
"rounded": 0.125,
"bellmouth": 0.20,
221 """Cd at Re > 1e4 and L/d ~ 2-5 as a continuous function of inlet rounding r/d.
223 Saturating fit through the practitioner anchors for a SHORT TUBE: 0.80 sharp entry
224 (r/d = 0), ~0.88 at r/d = 0.1-0.125, 0.95 at r/d >= 0.2. Rounding past r/d ~ 0.2 buys
225 almost nothing, which is why bellmouth entries are specified at that ratio, not deeper.
226 For the thin-plate limit multiply by cd_length_factor(L/d), which recovers 0.61 at L/d 0.
228 x =
max(0.0, float(r_over_d))
231 cd_sharp, cd_max, k = 0.80, 0.96, 7.622
232 return float(cd_sharp + (cd_max - cd_sharp) * (1.0 - np.exp(-k * x)))
237LICHTAROWICZ_A = 0.827
238LICHTAROWICZ_B = 0.0085
241LICHTAROWICZ_REF_LD = (LICHTAROWICZ_A - 0.80) / LICHTAROWICZ_B
243LICHTAROWICZ_LD_MAX = 10.0
246COUNTERBORE_ENTRANCE_K = 0.5
250 """Multiplier on Cd for orifice length.
252 ``lichtarowicz`` (default): the published sharp-inlet fit Cd_u = 0.827 - 0.0085 L/d over
253 2 <= L/d <= 10, divided by its value at LICHTAROWICZ_REF_LD so the inlet table stays the
254 anchor. Past 10 (outside the data) the same slope continues, floored at 0.85, and the
255 extrapolation is recorded. Below 2 it blends linearly to the thin-plate anchor at L/d 0.
257 ``piecewise`` (legacy, unsourced): 1.0 over L/d = 2-5, a steep rise from the thin-plate end
258 below 2 and a 1.2 %/L/d decline above 5.
260 Lichtarowicz et al. (1965): steep rise from L/d = 0, maximum near L/d ~ 2 where the
261 expansion downstream of the vena contracta recovers dynamic pressure, then a slow decline
262 from wall friction. Below L/d ~ 1 the orifice behaves as a thin plate and loses that
263 recovery; above ~10 friction dominates.
266 if not np.isfinite(x)
or x <= 0.0:
268 if model ==
"lichtarowicz":
269 ref = LICHTAROWICZ_A - LICHTAROWICZ_B * LICHTAROWICZ_REF_LD
270 at2 = (LICHTAROWICZ_A - LICHTAROWICZ_B * 2.0) / ref
272 return float(0.7625 + (at2 - 0.7625) * x / 2.0)
273 if x > LICHTAROWICZ_LD_MAX:
275 assume(
"discharge.orifice_l_over_d_past_fit", x,
276 reason=
"Lichtarowicz (1965) fit covers 2 <= L/d <= 10; extrapolated at its slope")
277 return float(
max(0.85, (LICHTAROWICZ_A - LICHTAROWICZ_B * x) / ref))
280 return float(0.7625 + 0.11875 * x)
283 return float(
max(0.85, 1.0 - 0.012 * (x - 5.0)))
286def cd_with_approach(cd: float, beta: Optional[float], k_entrance: float = COUNTERBORE_ENTRANCE_K) -> float:
287 """Cd of an orifice fed through a counterbore of diameter d / beta, referred to the
288 plenum-to-chamber drop.
290 Energy from the plenum: the counterbore entrance costs (1 + K) of the counterbore's
291 velocity head, and the orifice, with its static tap in the counterbore, discharges
292 Cd A sqrt(2 rho dp / (1 - beta^4)). Adding the two drops:
294 dp_total = rho v^2 / 2 * [ (1 - beta^4) / Cd^2 + beta^4 (1 + K) ]
296 Wall friction in the counterbore (f L/D ~ 0.05) is neglected: it adds under 0.2 % of the
297 already-small beta^4 term.
299 if beta
is None or not np.isfinite(beta)
or beta <= 0.0
or cd <= 0.0:
301 b4 = float(beta) ** 4
302 return float(1.0 / math.sqrt((1.0 - b4) / cd ** 2 + b4 * (1.0 + k_entrance)))
306 """Asymptotic Cd from the orifice's INLET treatment and L/d, or None if not configured.
308 Returns None when neither ``inlet_geometry`` nor ``inlet_radius_ratio`` is set, so the
309 caller keeps its existing diameter-based behaviour and nothing changes for old configs.
310 This is the orifice's own ultimate Cd (cd_u_from_inlet_geometry) seen through the
311 counterbore approach, capped at 0.98.
317 return float(
min(cd, 0.98))
321 """The ORIFICE's high-Re Cd, inlet table x length factor, before any counterbore approach.
323 This is Cd_u of Lichtarowicz et al. (1965) as anchored here (sharp 0.80 at
324 LICHTAROWICZ_REF_LD). None when the config declares no inlet.
326 name = getattr(config,
"inlet_geometry",
None)
327 rd = getattr(config,
"inlet_radius_ratio",
None)
328 if name
is None and rd
is None:
330 if rd
is not None and np.isfinite(float(rd)):
333 key = str(name).strip().lower()
334 if key
not in INLET_GEOMETRY_CD:
336 f
"Unknown inlet_geometry {name!r}. Valid: {', '.join(sorted(INLET_GEOMETRY_CD))}, "
337 f
"or give inlet_radius_ratio (r/d) for a continuous value."
339 cd = INLET_GEOMETRY_CD[key]
340 lod = getattr(config,
"orifice_l_over_d",
None)
341 if lod
is not None and np.isfinite(float(lod)):
342 cd *=
cd_length_factor(float(lod), getattr(config,
"length_model",
"lichtarowicz"))
349LICHTAROWICZ_RE_A = 20.0
350LICHTAROWICZ_RE_B = 2.25
351LICHTAROWICZ_RE_C = 0.0015
352LICHTAROWICZ_RE_D = 7.5
353LICHTAROWICZ_RE_E = 0.00015
357 """Lichtarowicz et al. (1965) Cd at Reynolds number ``Re`` (bulk velocity mdot/(rho A), hole
358 diameter) for an orifice whose ultimate (high-Re) Cd is ``cd_u`` and length is ``l_over_d``.
360 Mirrored by ``engine.accel.kernels._cd_lichtarowicz_re``; keep the operation order identical.
363 lg = math.log10(LICHTAROWICZ_RE_E * Re)
365 + LICHTAROWICZ_RE_A * (1.0 + LICHTAROWICZ_RE_B * x) / Re
366 - LICHTAROWICZ_RE_C * x / (1.0 + LICHTAROWICZ_RE_D * lg * lg))
367 return float(1.0 / inv)
371 """``(Cd_u, L/d, approach_beta)`` when cd_from_re uses the Lichtarowicz Re law, else None.
373 It applies when the block declares an inlet (so Cd_u exists), an L/d, and
374 ``length_model: lichtarowicz`` (the default). Otherwise cd_from_re keeps the legacy a_Re form.
376 if getattr(config,
"length_model",
"lichtarowicz") !=
"lichtarowicz":
378 lod = getattr(config,
"orifice_l_over_d",
None)
379 if lod
is None or not np.isfinite(float(lod))
or float(lod) <= 0.0:
384 return float(cd_u), float(lod), getattr(config,
"approach_beta",
None)
406 """Vena-contracta contraction coefficient for inlet rounding r/d (Nurick 1976)."""
407 rd =
max(0.0, float(r_over_d))
if np.isfinite(r_over_d)
else 0.0
408 inv2 = 1.0 / NURICK_CC0 ** 2 - NURICK_RD_GAIN * rd
409 return float(
min(0.98, 1.0 / math.sqrt(inv2)))
if inv2 > 1.0 / 0.98 ** 2
else 0.98
412def cavitation_margin(*, P_in: float, Pc: float, Pv: float, Cd: float, r_over_d: float = 0.0) -> Dict[str, float]:
413 """K, K_crit = (Cd/Cc)^2 and their ratio for one orifice. Ratio < 1 means it cavitates."""
415 dp = float(P_in) - float(Pc)
416 if not (np.isfinite(dp)
and dp > 0
and np.isfinite(Cd)
and Cd > 0):
417 return {
"K": float(
"nan"),
"K_crit": float(
"nan"),
"margin": float(
"nan"),
"Cc": Cc,
"Pv": float(Pv)}
418 K = (float(P_in) - float(Pv)) / dp
419 K_crit = (float(Cd) / Cc) ** 2
420 return {
"K": K,
"K_crit": K_crit,
"margin": K / K_crit,
"Cc": Cc,
"Pv": float(Pv)}
424 """r/d a discharge block describes: inlet_radius_ratio, else the named inlet's, else 0 (sharp)."""
425 rd = getattr(config,
"inlet_radius_ratio",
None)
426 if rd
is not None and np.isfinite(float(rd)):
428 name = getattr(config,
"inlet_geometry",
None)
429 return float(INLET_GEOMETRY_RD.get(str(name).strip().lower(), 0.0))
if name
else 0.0
float cd_from_inlet_radius_ratio(float r_over_d)
Optional[float] cd_u_from_inlet_geometry(config)
float contraction_coefficient(float r_over_d)
Optional[tuple] lichtarowicz_re_inputs(config)
float inlet_radius_ratio_of(config)
float cd_with_approach(float cd, Optional[float] beta, float k_entrance=COUNTERBORE_ENTRANCE_K)
float cd_length_factor(float l_over_d, str model="lichtarowicz")
float cd_from_re(float Re, DischargeConfig config, float P_inlet=None, float T_inlet=None, Optional[float] d_hyd_m=None)
float cd_inf_from_orifice_diameter(Optional[float] d_hyd_m, DischargeConfig config)
float calculate_reynolds_number(float rho, float u, float d_hyd, float mu)
float cd_lichtarowicz_re(float cd_u, float Re, float l_over_d)
Dict[str, float] cavitation_margin(*, float P_in, float Pc, float Pv, float Cd, float r_over_d=0.0)
float discharge_cd_inf_ratio(float d_hyd_O, float d_hyd_F, DischargeConfig discharge_O, DischargeConfig discharge_F)
Optional[float] cd_inf_from_inlet_geometry(config)