STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_manifold_velocity_head.py
Go to the documentation of this file.
1"""The back-face channels are the manifold, and their velocity head is checked against the
2injector drop.
3
4Holes fed from a cross-flowing channel see the channel's STATIC pressure; with the approach
5flow normal to the hole axis none of the channel's velocity head is recovered (Rohde, Richards
6& Metger, NASA TN D-5467, 1969), and Elverum & Morey (JPL Memo 30-5) ask for as uniform a
7manifold flow as possible. Sizing rule: the channel's velocity head at the inlet, q_ch, is a
8small fraction f of the injector dp, i.e. A_ch >= mdot_branch / sqrt(2 rho f dp); one inlet
9per channel splits two ways, mdot_branch = mdot / 2.
10
11On the 6500N the channels are 22.8 / 24.0 mm^2 against 45 / 38 mm^2 of orifices, so q_ch is
12~63 % / 40 % of dp (config audit H-1: at least 81 / 68 mm^2 for f = 5 %). The layout used to
13report the area ratio at 'info' and nothing read it.
14
15Expected values are hand arithmetic on the layout's channel area and the solved flows.
16"""
17from __future__ import annotations
18
19import math
20from pathlib import Path
21
22import pytest
23import yaml
24
25ROOT = Path(__file__).resolve().parents[1]
26C6500 = ROOT / "configs/ethalox_6500N.yaml"
27PSI = 6894.757
28
29
30@pytest.fixture(autouse=True, scope="module")
32 """The authoritative Python path; engine/accel mirrors it and test_numba_ab_parity holds the
33 two together."""
34 from engine import accel
35 real = accel.enabled, accel.require
36 accel.enabled = lambda: False
37 accel.require = lambda: False
38 try:
39 yield
40 finally:
41 accel.enabled, accel.require = real
42
43
44def _hand(area, mdot, rho, dp, inlets=1, f=0.05):
45 branch = mdot / (2.0 * inlets)
46 v = branch / (rho * area)
47 q = 0.5 * rho * v * v
48 return q / dp, branch / math.sqrt(2.0 * rho * f * dp)
49
50
52 from engine.core.injectors.layout import channel_velocity_head
53 out = channel_velocity_head(flow_area=22.75e-6, mdot=1.6792, rho=1140.0, dp=947.9e3)
54 q_dp, A_need = _hand(22.75e-6, 1.6792, 1140.0, 947.9e3)
55 assert out["q_over_dp"] == pytest.approx(q_dp, rel=1e-12)
56 assert out["area_needed"] == pytest.approx(A_need, rel=1e-12)
57 assert out["q_over_dp"] == pytest.approx(0.63, abs=0.01) # audit H-1
58 assert out["area_needed"] * 1e6 == pytest.approx(80.8, abs=0.3) # audit H-1
59 two = channel_velocity_head(flow_area=22.75e-6, mdot=1.6792, rho=1140.0, dp=947.9e3, inlets=2)
60 assert two["q_over_dp"] == pytest.approx(out["q_over_dp"] / 4.0, rel=1e-12)
61
62
63@pytest.fixture(scope="module")
64def solved():
65 from engine.pipeline.io import load_config
66 from engine.core.runner import PintleEngineRunner
67 cfg = load_config(str(C6500))
68 P = 584.2669657943025 * PSI
69 r = PintleEngineRunner(cfg).evaluate(P_tank_O=P, P_tank_F=P)
70 dg = r["diagnostics"]
71 return {"mdot_O": r["mdot_O"], "mdot_F": r["mdot_F"],
72 "dp_O": dg["delta_p_injector_O"], "dp_F": dg["delta_p_injector_F"],
73 "rho_O": cfg.fluids["oxidizer"].density, "rho_F": cfg.fluids["fuel"].density}
74
75
76def _codes(out):
77 return {w["code"]: w for w in out["warnings"]}
78
79
81 from engine.core.injectors.layout import layout_from_config
82 out = layout_from_config(yaml.safe_load(C6500.read_text()), drawings=False, flows=solved)
83 for k, side in (("O", "O"), ("F", "F")):
84 ch = out["passages"][k]["channel"]
85 q_dp, A_need = _hand(ch["flow_area"], solved[f"mdot_{side}"], solved[f"rho_{side}"], solved[f"dp_{side}"])
86 assert ch["q_over_dp"] == pytest.approx(q_dp, rel=1e-9)
87 assert ch["area_needed"] == pytest.approx(A_need, rel=1e-9)
88 w = _codes(out).get(f"manifold_q_{k}")
89 assert w is not None and w["level"] == "bad", out["warnings"]
90 assert f"{A_need * 1e6:.0f} mm²" in w["text"]
91 # With the ring-manifold solve the starved channel carries less flow than the plenum model
92 # assumed, so q/dp is lower than the old 0.5+; it is still well past MANIFOLD_Q_FRAC.
93 from engine.core.injectors.layout import MANIFOLD_Q_FRAC
94 assert out["passages"]["O"]["channel"]["q_over_dp"] > 5 * MANIFOLD_Q_FRAC
95
96
98 from engine.core.injectors.layout import layout_from_config
99 cfg = yaml.safe_load(C6500.read_text())
100 cfg["injector"]["plate"]["channel_inlets"] = 4 # q falls 16x
101 out = layout_from_config(cfg, drawings=False, flows=solved)
102 for k in ("O", "F"):
103 assert out["passages"][k]["channel"]["q_over_dp"] < 0.05
104 assert f"manifold_q_{k}" not in _codes(out)
105
106
108 from engine.core.injectors.layout import layout_from_config
109 out = layout_from_config(yaml.safe_load(C6500.read_text()), drawings=False)
110 assert "channel_area_O" in _codes(out)
111 assert out["passages"]["O"]["channel"].get("q_over_dp") is None
_hand(area, mdot, rho, dp, inlets=1, f=0.05)