STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_injector_plate_checks.py
Go to the documentation of this file.
1"""The layout's checks that need a solve or a load: channel velocity head from the production
2callers, orifice cavitation in place of the invented short-wall rule, and plate bending.
3
4Design: the 6.5 kN doublet (24 doublets, 35/48 deg, contoured face,
5flat-floored channels with one feed port each, 12.7 mm plate, 1/2 NPT igniter).
6
7Expected values are hand arithmetic: Timoshenko & Woinowsky-Krieger §19 closed forms for the
8plate (written out here, independent of the layout's own solve), Nurick (1976) for cavitation.
9"""
10from __future__ import annotations
11
12import copy
13import importlib.util
14import math
15from pathlib import Path
16
17import pytest
18import yaml
19
21 ATM, PLATE_YIELD_FACTOR, PSI, flows_from_result, layout_from_config, plate_bending_moments,
22)
23
24ROOT = Path(__file__).resolve().parents[1]
25DESIGN = ROOT / "tests/fixtures/doublet_6500N_autochannel.yaml"
26
27
28def _yaml():
29 return yaml.safe_load(DESIGN.read_text())
30
31
32def _codes(out):
33 return {w["code"]: w for w in out["warnings"]}
34
35
36@pytest.fixture(scope="module")
37def flows():
38 from engine.core.runner import PintleEngineRunner
39 from engine.pipeline.io import load_config
40 c = load_config(str(DESIGN))
41 r = PintleEngineRunner(copy.deepcopy(c)).evaluate(
42 c.lox_tank.initial_pressure_psi * PSI, c.fuel_tank.initial_pressure_psi * PSI, silent=True)
43 return flows_from_result(r, c)
44
45
46# =============================================================================================
47# Manifold: the production callers pass the solve
48# =============================================================================================
49
51 spec = importlib.util.spec_from_file_location("design_audit", ROOT / "scripts/design_audit.py")
52 mod = importlib.util.module_from_spec(spec)
53 spec.loader.exec_module(mod)
54 ok = mod.audit(str(DESIGN))
55 printed = capsys.readouterr().out
56 bad = [ln for ln in printed.splitlines() if "drawing bad" in ln]
57 assert any("LOX channel velocity head" in ln for ln in bad), printed
58 assert "manifold_q_O" in printed # named in the failed "layout: nothing impossible"
59 assert ok is False
60
61
63 out = layout_from_config(_yaml(), drawings=False, flows=flows)
64 for k in ("O", "F"):
65 ch = out["passages"][k]["channel"]
66 m, rho, dp = flows[f"mdot_{k}"], flows[f"rho_{k}"], flows[f"dp_{k}"]
67 v = (m / 2.0) / (rho * ch["flow_area"])
68 assert ch["q_over_dp"] == pytest.approx(0.5 * rho * v * v / dp, rel=1e-9)
69 for n in (1, 2, 4):
70 A = (m / (2.0 * n)) / math.sqrt(2.0 * rho * 0.05 * dp)
71 assert ch["area_needed_by_ports"][str(n)] == pytest.approx(A, rel=1e-9)
72 w = _codes(out)[f"manifold_q_{k}"]
73 assert w["level"] == "bad"
74 assert "% of design flow" not in w["text"]
75 for n in (1, 2, 4):
76 assert f"{ch['area_needed_by_ports'][str(n)] * 1e6:.0f} mm² ({n} port" in w["text"]
77 assert out["passages"]["O"]["channel"]["q_over_dp"] > 0.05
78
79
80# =============================================================================================
81# Cavitation number replaces the short-wall L/d
82# =============================================================================================
83
85 out = layout_from_config(_yaml(), drawings=False, flows=flows)
86 codes = _codes(out)
87 assert not [c for c in codes if c.startswith("short_wall")]
88 for k, th in (("O", 35.0), ("F", 48.0)):
89 ch = out["passages"][k]["channel"]
90 assert "short_wall_l_over_d" not in ch
91 assert ch["entry_lip_deg"] == pytest.approx(90.0 - th)
92 assert codes[f"oblique_inlet_{k}"]["level"] == "info"
93 assert f"{90 - th:.0f}°" in codes[f"oblique_inlet_{k}"]["text"]
94 P, Pc, Pv, Cd = flows[f"P_inj_{k}"], flows["Pc"], flows[f"Pv_{k}"], flows[f"Cd_{k}"]
95 cav = out["passages"][k]["cavitation"]
96 assert cav["K"] == pytest.approx((P - Pv) / (P - Pc), rel=1e-12)
97 assert cav["K_crit"] == pytest.approx((Cd / 0.62) ** 2, rel=1e-12) # sharp inlet, Cc 0.62
98 assert codes[f"cavitation_{k}"]["level"] == ("bad" if cav["K"] < cav["K_crit"] else "info")
99
100
102 f = dict(flows)
103 f["Pv_O"] = f["P_inj_O"] - 0.5 * (f["P_inj_O"] - f["Pc"]) # K = 0.5 < K_crit
104 out = layout_from_config(_yaml(), drawings=False, flows=f)
105 assert out["passages"]["O"]["cavitation"]["K"] == pytest.approx(0.5)
106 assert _codes(out)["cavitation_O"]["level"] == "bad"
107
108
110 cfg = _yaml()
111 cfg["injector"]["plate"]["channel_floor"] = "coned"
112 out = layout_from_config(cfg, drawings=False)
113 assert out["passages"]["O"]["channel"]["entry_lip_deg"] == 90.0
114 assert "oblique_inlet_O" not in _codes(out)
115
116
117# =============================================================================================
118# Plate bending
119# =============================================================================================
120
121def _timoshenko(r, a, b, p, nu, support):
122 """Solid plate, uniform p over r <= b (Timoshenko & Woinowsky-Krieger §19)."""
123 k = p * b * b / 4.0 # P / (4 pi), P = pi b^2 p
124 if r <= b:
125 Mr = k * ((1 + nu) * math.log(a / b) + 1 - (1 - nu) * b * b / (4 * a * a) - (3 + nu) * r * r / (4 * b * b))
126 Mt = k * ((1 + nu) * math.log(a / b) + 1 - (1 - nu) * b * b / (4 * a * a) - (1 + 3 * nu) * r * r / (4 * b * b))
127 else:
128 Mr = k * ((1 + nu) * math.log(a / r) + (1 - nu) * b * b / (4 * r * r) * (1 - r * r / (a * a)))
129 Mt = k * ((1 + nu) * math.log(a / r) + (1 - nu) - (1 - nu) * b * b / (4 * r * r) - (1 - nu) * b * b / (4 * a * a))
130 if support == "clamped":
131 Mr += k * (-1 + b * b / (2 * a * a))
132 Mt += k * (-1 + b * b / (2 * a * a))
133 return Mr, Mt
134
135
136@pytest.mark.parametrize("support", ["simply_supported", "clamped"])
137@pytest.mark.parametrize("r", [1e-4, 0.02, 0.0495, 0.0635, 0.07, 0.0762])
139 a, b, p, nu = 0.0762, 0.0635, 2.4e6, 0.3
140 got = plate_bending_moments(r, a=a, b=b, p=p, nu=nu, support=support)
141 assert got == pytest.approx(_timoshenko(r, a, b, p, nu, support), rel=1e-7, abs=1e-6)
142
143
145 a, p, nu, t = 0.05, 1e6, 0.3, 0.01
146 Mr0, _ = plate_bending_moments(1e-6, a=a, b=a, p=p, nu=nu, support="simply_supported")
147 assert 6 * Mr0 / t ** 2 == pytest.approx(3 * (3 + nu) * p * a * a / (8 * t * t), rel=1e-6)
148 Mra, _ = plate_bending_moments(a, a=a, b=a, p=p, nu=nu, support="clamped")
149 assert 6 * abs(Mra) / t ** 2 == pytest.approx(3 * p * a * a / (4 * t * t), rel=1e-9)
150
151
153 Mr, Mt = plate_bending_moments(0.0107, a=0.0762, b=0.0635, p=2e6, nu=0.3, r_hole=0.0107)
154 assert abs(Mr) < 1e-6 and Mt > _timoshenko(0.0107, 0.0762, 0.0635, 2e6, 0.3, "simply_supported")[1]
155
156
157@pytest.mark.parametrize("support", ["simply_supported", "clamped"])
159 cfg = _yaml()
160 cfg["injector"]["igniter"] = None # the solid plate the closed form is for
161 cfg["injector"]["plate"]["support"] = support
162 out = layout_from_config(cfg, drawings=False)
163 pb = out["plate_bending"]
164 t, a, b, nu = 0.0127, 0.5 * 0.1651 - 0.00635, 0.5 * 0.127, 0.3
165 p = 375.0 * PSI - ATM # target_chamber_pressure_psi, no solve
166 assert (pb["r_plate"], pb["r_loaded"], pb["p_gauge"]) == pytest.approx((a, b, p), rel=1e-12)
167 for k in ("O", "F"):
168 ch = out["passages"][k]["channel"]
169 worst = pb["channels"][k]
170 hand = []
171 for r in (ch["r_lo"], ch["r_center"], ch["r_hi"]):
172 t_lig = t - ch["depth"] # flat floor, face datum above
173 Mr, Mt = _timoshenko(r, a, b, p, nu, support)
174 sr = 6 * Mr / t_lig ** 2
175 st = nu * sr + 6 * (Mt - nu * Mr) * t_lig / t ** 3
176 hand.append(max(abs(sr), abs(st), abs(sr - st)))
177 assert worst["t_net"] == pytest.approx(t - ch["depth"], rel=1e-9)
178 assert worst["sigma"] == pytest.approx(max(hand), rel=1e-3)
179 assert _codes(out)["plate_bending"]["level"] == "info"
180 assert "material undeclared" in _codes(out)["plate_bending"]["text"]
181
182
184 cfg = _yaml()
185 base = layout_from_config(cfg, drawings=False)["plate_bending"]["max"]["sigma"]
186 cfg["injector"]["plate"]["yield_strength"] = 0.9 * PLATE_YIELD_FACTOR * base
187 assert _codes(layout_from_config(cfg, drawings=False))["plate_bending"]["level"] == "bad"
188 cfg["injector"]["plate"]["yield_strength"] = 1.1 * PLATE_YIELD_FACTOR * base
189 assert _codes(layout_from_config(cfg, drawings=False))["plate_bending"]["level"] == "info"
#define max(a, b)
Definition Arduino.h:43
test_design_audit_runs_the_manifold_check_and_flags_the_lox_channel(capsys)