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.
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).
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.
10from __future__
import annotations
15from pathlib
import Path
21 ATM, PLATE_YIELD_FACTOR, PSI, flows_from_result, layout_from_config, plate_bending_moments,
24ROOT = Path(__file__).resolve().parents[1]
25DESIGN = ROOT /
"tests/fixtures/doublet_6500N_autochannel.yaml"
29 return yaml.safe_load(DESIGN.read_text())
33 return {w[
"code"]: w
for w
in out[
"warnings"]}
36@pytest.fixture(scope="module")
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)
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
63 out = layout_from_config(
_yaml(), drawings=
False, flows=flows)
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)
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"]
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
85 out = layout_from_config(
_yaml(), drawings=
False, flows=flows)
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)
98 assert codes[f
"cavitation_{k}"][
"level"] == (
"bad" if cav[
"K"] < cav[
"K_crit"]
else "info")
103 f[
"Pv_O"] = f[
"P_inj_O"] - 0.5 * (f[
"P_inj_O"] - f[
"Pc"])
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"
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)
122 """Solid plate, uniform p over r <= b (Timoshenko & Woinowsky-Krieger §19)."""
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))
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))
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)
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)
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]
157@pytest.mark.parametrize("support", ["simply_supported", "clamped"])
160 cfg[
"injector"][
"igniter"] =
None
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
166 assert (pb[
"r_plate"], pb[
"r_loaded"], pb[
"p_gauge"]) == pytest.approx((a, b, p), rel=1e-12)
168 ch = out[
"passages"][k][
"channel"]
169 worst = pb[
"channels"][k]
171 for r
in (ch[
"r_lo"], ch[
"r_center"], ch[
"r_hi"]):
172 t_lig = t - ch[
"depth"]
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"]
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"
test_design_audit_runs_the_manifold_check_and_flags_the_lox_channel(capsys)
_timoshenko(r, a, b, p, nu, support)
test_plate_check_channel_root_stress_is_the_hand_calc(support)
test_cavitation_check_replaces_the_short_wall_rule(flows)
test_plate_moments_reduce_to_roarks_uniform_plate()
test_plate_moments_are_timoshenko(support, r)
test_plate_check_against_a_declared_yield()
test_coned_floor_has_a_square_lip_and_no_oblique_note()
test_a_cavitating_orifice_is_bad(flows)
test_manifold_reports_q_over_dp_and_the_area_for_1_2_4_ports(flows)
test_a_centre_hole_frees_the_radial_moment_at_its_edge()