29from typing
import Any, Dict, Mapping, Optional
34 PSI, arr, firing_mask, inp, model_block, out, scalar, series_t, unavailable,
45 forward: Optional[Mapping[str, Any]] =
None) -> Optional[Dict[str, Any]]:
46 """EngineDesign's Forward solve at the burn's lockup, both tanks, at the site's ambient: what
47 the design column of a "design assumed X -> feed delivers Y(t)" row must come from (AUDIT 4).
48 ``forward`` (a ``runner.evaluate`` result) is returned as is; else ``runner`` is evaluated."""
49 if forward
is not None:
51 if runner
is None or prep
is None:
53 derived = getattr(prep,
"derived", {})
or {}
54 lockup = derived.get(
"target_lockup_psia")
57 ambient = float(getattr(prep,
"ambient_pa",
None)
or derived.get(
"ambient_pa")
or 101325.0)
58 res = runner.evaluate(float(lockup) * PSI, float(lockup) * PSI, P_ambient=ambient, silent=
True)
60 res[
"_basis"] = {
"tank_psia": float(lockup),
"ambient_pa": ambient}
64def _replay_onto(result: Mapping[str, Any], key: str, t: np.ndarray) -> np.ndarray:
65 rep = result.get(
"replay")
or {}
66 rt = arr(rep.get(
"t"))
67 rv = arr(rep.get(key))
if rep.get(key)
is not None else np.full(rt.size, np.nan)
68 ok = np.isfinite(rt) & np.isfinite(rv)
70 return np.full(t.size, np.nan)
71 rt, rv = rt[ok], rv[ok]
72 vals = np.interp(t, rt, rv)
73 return np.where((t >= rt[0] - 1e-9) & (t <= rt[-1] + 1e-9), vals, np.nan)
76def build_injector(result: Mapping[str, Any], prep: Any =
None, config: Any =
None, *,
77 forward: Optional[Mapping[str, Any]] =
None, runner: Any =
None) -> Dict[str, Any]:
78 """``diagnostics.injector``. Never raises. Needs ``config`` (the orifices) and the series."""
81 return unavailable(
"the engine config is needed for the orifices")
85 series = result.get(
"series")
or {}
88 firing = firing_mask(result, n)
89 go, gf = orifice_geometry(config,
"ox"), orifice_geometry(config,
"fuel")
90 m_o = arr((series.get(
"ox")
or {}).get(
"mdot"), n)
91 m_f = arr((series.get(
"fuel")
or {}).get(
"mdot"), n)
92 flowing = firing & (np.nan_to_num(m_o) > 0) & (np.nan_to_num(m_f) > 0)
93 v_o = np.where(flowing, m_o / (go[
"rho"] * go[
"area"]), np.nan)
94 v_f = np.where(flowing, m_f / (gf[
"rho"] * gf[
"area"]), np.nan)
95 R = np.full(n, np.nan)
96 M = np.full(n, np.nan)
97 tilt = np.full(n, np.nan)
98 for k
in np.flatnonzero(flowing):
99 R[k] = momentum_ratio_R_from_bulk_velocities(go[
"rho"], gf[
"rho"], float(v_o[k]), float(v_f[k]))
100 M[k] = rupe_mixing_ratio(go[
"rho"], float(v_o[k]), go[
"d_jet"], gf[
"rho"], float(v_f[k]), gf[
"d_jet"])
101 tilt[k] = _impinging_resultant_tilt_deg(
102 float(m_o[k]), float(m_f[k]), go[
"rho"], gf[
"rho"], float(go[
"n"]), go[
"d_jet"], gf[
"d_jet"],
103 go[
"angle_deg"], gf[
"angle_deg"], spacing_O_m=go[
"spacing"], spacing_F_m=gf[
"spacing"])
104 cd_o = cd_effective(m_o, arr((series.get(
"ox")
or {}).get(
"dp_injector_psi"), n), go[
"area"], go[
"rho"])
105 cd_f = cd_effective(m_f, arr((series.get(
"fuel")
or {}).get(
"dp_injector_psi"), n), gf[
"area"], gf[
"rho"])
109 fwd_note =
"no Forward solve supplied (pass forward= or runner=)"
112 except Exception
as exc:
113 fwd_note = f
"Forward solve failed: {type(exc).__name__}: {exc}"
114 d = (fwd
or {}).get(
"diagnostics")
or {}
115 design_R = scalar(d.get(
"momentum_ratio_R"))
if fwd
else None
116 design_M = scalar(d.get(
"rupe_M"))
if fwd
else None
118 if fwd
and fwd.get(
"mdot_O")
and fwd.get(
"mdot_F"):
119 design_tilt = scalar(_impinging_resultant_tilt_deg(
120 float(fwd[
"mdot_O"]), float(fwd[
"mdot_F"]), go[
"rho"], gf[
"rho"], float(go[
"n"]), go[
"d_jet"],
121 gf[
"d_jet"], go[
"angle_deg"], gf[
"angle_deg"], spacing_O_m=go[
"spacing"], spacing_F_m=gf[
"spacing"]))
123 fwd_note = f
"EngineDesign Forward at {((fwd.get('_basis') or {}).get('tank_psia'))} psia both tanks"
125 def rng(a: np.ndarray) -> Optional[list]:
126 ok = flowing & np.isfinite(a)
127 return [scalar(np.nanmin(a[ok])), scalar(np.nanmax(a[ok]))]
if ok.any()
else None
129 req = getattr(config,
"design_requirements",
None)
130 band = [getattr(req,
"impinging_momentum_R_min",
None), getattr(req,
"impinging_momentum_R_max",
None)]
133 "v_ox": out(v_o),
"v_fuel": out(v_f),
134 "momentum_ratio": out(R),
"rupe_M": out(M),
135 "design_momentum_ratio": design_R,
"design_rupe_M": design_M,
136 "momentum_ratio_band": band
if any(b
is not None for b
in band)
else None,
137 "resultant_angle_deg": out(tilt),
"design_resultant_angle_deg": design_tilt,
138 "Cd_eff_ox": out(cd_o),
"Cd_eff_fuel": out(cd_f),
139 "design_Cd": ({
"ox": scalar(d.get(
"Cd_O")),
"fuel": scalar(d.get(
"Cd_F"))}
if fwd
else None),
140 "eta_cstar": out(eta),
141 "design_eta_cstar": scalar(fwd.get(
"eta_cstar")
or d.get(
"eta_cstar"))
if fwd
else None,
142 "eta_cstar_replay": {
"t": (result.get(
"replay")
or {}).get(
"t"),
143 "values": (result.get(
"replay")
or {}).get(
"eta_cstar")},
144 "range": {
"momentum_ratio": rng(R),
"resultant_angle_deg": rng(tilt),
"v_ox": rng(v_o),
146 "design_basis": fwd_note,
147 "model": model_block(
148 "injector state on the twin's flows (EngineDesign definitions)",
150 [
"v = mdot / (rho n A_jet) at the config density (the card's basis), the twin's mdot per step",
151 "R = sqrt(rho_O v_O^2 / (rho_F v_F^2)); M = R^2 d_O/d_F (Rupe)",
152 "tilt: + toward the wall; ring order from the pitch circles (D = n s / pi)",
153 "Cd_eff = mdot / (A sqrt(2 rho dp_inj)) on the twin's orifice drop",
154 "eta_c* interpolated between replay points, null outside the replay's span",
155 "design values: one EngineDesign Forward solve at the burn's lockup on both tanks"],
156 {
"rho_ox": inp(go[
"rho"],
"kg/m^3",
"config fluids.oxidizer.density"),
157 "rho_fuel": inp(gf[
"rho"],
"kg/m^3",
"config fluids.fuel.density"),
158 "d_jet_ox": inp(go[
"d_jet"] * 1e3,
"mm",
"config injector.geometry.oxidizer.d_jet"),
159 "d_jet_fuel": inp(gf[
"d_jet"] * 1e3,
"mm",
"config injector.geometry.fuel.d_jet"),
160 "n_elements": inp(go[
"n"],
"-",
"config injector.geometry.*.n_elements"),
161 "angle_ox": inp(go[
"angle_deg"],
"deg",
"config injector.geometry.oxidizer.impingement_angle"),
162 "angle_fuel": inp(gf[
"angle_deg"],
"deg",
"config injector.geometry.fuel.impingement_angle"),
163 "spacing_ox": inp(go[
"spacing"] * 1e3,
"mm",
"config injector.geometry.oxidizer.spacing"),
164 "spacing_fuel": inp(gf[
"spacing"] * 1e3,
"mm",
"config injector.geometry.fuel.spacing")}),
166 except Exception
as exc:
167 return unavailable(f
"{type(exc).__name__}: {exc}")
Dict[str, Any] build_injector(Mapping[str, Any] result, Any prep=None, Any config=None, *, Optional[Mapping[str, Any]] forward=None, Any runner=None)