STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_zero_flow_closures.py
Go to the documentation of this file.
1"""A one-way device between zero flow and its opening pressure is shut.
2
3A check valve's cracking pressure and a regulator's lockup rise both step the
4branch relation at zero flow. With the far side inside that step there was no
5root -- forward needed at least the step, reverse at most nothing -- and every
6solve failed, which is exactly where a tank pressed through a check valve, or
7held by a regulator at lockup, comes to rest. Inside the step the device is
8shut: zero flow, the pressures either side free.
9"""
10
11from __future__ import annotations
12
13import pytest
14
15from feedtwin.comps import build_component
16from feedtwin.model import ComponentInstance, Param, Provenance
17from feedtwin.solve.network import Network
18from feedtwin.solve.steady import solve_steady
19
20PSI = 6894.757293168361
21M = Provenance.MEASURED
22
23
24def _pipe(tag: str, length: float = 1.0) -> object:
25 return build_component(
26 ComponentInstance.build(
27 tag,
28 "pipe",
29 {
30 "length": Param(length, "m", M, ""),
31 "bore": Param(9.5, "mm", M, ""),
32 "roughness": Param(1.5e-6, "m", M, ""),
33 },
34 )
35 )
36
37
38def _checked(drop_psi: float) -> object:
39 """A -> pipe -> check valve (3 psi crack) -> pipe -> B, ``drop_psi`` apart."""
40 net = Network()
41 net.add_node("A", "nitrogen", 293.0, pressure=500.0 * PSI)
42 net.add_node("M", "nitrogen", 293.0)
43 net.add_node("N", "nitrogen", 293.0)
44 net.add_node("B", "nitrogen", 293.0, pressure=(500.0 - drop_psi) * PSI)
45 for node in net.nodes.values():
46 node.phase = "gas"
47 check = build_component(
48 ComponentInstance.build(
49 "CV",
50 "check_valve",
51 {
52 "Cv": Param(1.0, "Cv", M, ""),
53 "bore": Param(9.5, "mm", M, ""),
54 "cracking_pressure": Param(3.0, "psi", M, ""),
55 "leak_reverse": Param(1e-4, "Cv", M, ""),
56 },
57 )
58 )
59 net.add_branch("P1", _pipe("P1"), "A", "M") # type: ignore[arg-type]
60 net.add_branch("CV", check, "M", "N") # type: ignore[arg-type]
61 net.add_branch("P2", _pipe("P2"), "N", "B") # type: ignore[arg-type]
62 return solve_steady(net, raise_on_failure=False, tol=1e-4)
63
64
65@pytest.mark.parametrize("drop_psi", [1.0, 1.5, 2.0, 2.9])
67 result = _checked(drop_psi)
68 assert result.converged # type: ignore[attr-defined]
69 assert result.flows["CV"] == pytest.approx(0.0, abs=1e-12) # type: ignore[attr-defined]
70
71
72@pytest.mark.parametrize("drop_psi", [3.1, 5.0, 20.0])
74 result = _checked(drop_psi)
75 assert result.converged # type: ignore[attr-defined]
76 assert result.flows["CV"] > 0.0 # type: ignore[attr-defined]
77
78
79def _regulated(tank_psia: float, rise_psi: float) -> object:
80 """3000 psia bottle -> regulator (500 psia, 20 psi droop at 0.05 kg/s,
81 ``rise_psi`` of lockup rise) -> pipe -> a tank held at ``tank_psia``."""
82 reg = build_component(
83 ComponentInstance.build(
84 "REG",
85 "regulator",
86 {
87 "setpoint": Param(500.0, "psia", M, ""),
88 "Cv": Param(0.8, "Cv", M, ""),
89 "bore": Param(7.75, "mm", M, ""),
90 "flow_droop": Param(20.0, "psi", M, ""),
91 "rated_flow": Param(0.05, "kg/s", M, ""),
92 "lockup_rise": Param(rise_psi, "psi", M, ""),
93 },
94 model="droop",
95 )
96 )
97 net = Network()
98 net.add_node("COPV", "nitrogen", 293.0, pressure=3000.0 * PSI)
99 net.add_node("R", "nitrogen", 293.0)
100 net.add_node("T", "nitrogen", 293.0, pressure=tank_psia * PSI)
101 for node in net.nodes.values():
102 node.phase = "gas"
103 net.add_branch("REG", reg, "COPV", "R") # type: ignore[arg-type]
104 net.add_branch("L", _pipe("L", 0.5), "R", "T") # type: ignore[arg-type]
105 return solve_steady(net, raise_on_failure=False, tol=1e-4, max_iterations=120)
106
107
108@pytest.mark.parametrize(
109 "tank_psia", [495.0, 499.0, 500.5, 501.5, 502.4, 504.0, 504.9, 506.0, 508.0]
110)
112 """5 psi of rise on a 500 psia setpoint: a tank anywhere from below the
113 setpoint to past lockup has an answer. 500.5-502.4 psia had none. The flow
114 falls to nothing as the tank comes up to lockup (505 psia)."""
115 result = _regulated(tank_psia, 5.0)
116 assert result.converged, tank_psia # type: ignore[attr-defined]
117 flow = result.flows["L"] # type: ignore[attr-defined]
118 if tank_psia >= 504.5:
119 assert flow == pytest.approx(0.0, abs=2e-4), "at or past lockup it is shut"
120 else:
121 assert flow > 0.0
122
123
124def _regulator(rise_psi: float) -> object:
125 return build_component(
126 ComponentInstance.build(
127 "REG",
128 "regulator",
129 {
130 "setpoint": Param(500.0, "psia", M, ""),
131 "Cv": Param(0.8, "Cv", M, ""),
132 "bore": Param(7.75, "mm", M, ""),
133 "flow_droop": Param(20.0, "psi", M, ""),
134 "rated_flow": Param(0.05, "kg/s", M, ""),
135 "lockup_rise": Param(rise_psi, "psi", M, ""),
136 },
137 model="droop",
138 )
139 )
140
141
143 """Lockup (set + rise) at zero flow, the droop line once the rise is paid
144 back, and no step between: the rise is lost at the droop's own rate."""
145 from feedtwin.comps.base import FlowConditions
146
147 reg = _regulator(5.0)
148 flow = FlowConditions(rho=39.0, mu=1.8e-5, p_upstream=3000.0 * PSI)
149 outlet = lambda m: (3000.0 * PSI - reg.pressure_drop(m, flow)) / PSI # type: ignore[attr-defined] # noqa: E731
150 assert outlet(0.0) == pytest.approx(505.0)
151 assert outlet(1e-9) == pytest.approx(505.0, abs=1e-5)
152 # 5 psi of rise against 20 psi per 0.05 kg/s: paid back by 0.0125 kg/s.
153 assert outlet(0.0125) == pytest.approx(500.0 - 5.0)
154 assert outlet(0.05) == pytest.approx(500.0 - 20.0)
155
156
157@pytest.mark.parametrize("mdot", [0.0, 1e-6, 0.01, 0.05, 0.2])
159 """The closure changes nothing for a regulator that declares no rise (the
160 study drawings, LE4): outlet = set - droop m / rated, shut above it."""
161 from feedtwin.comps.base import FlowConditions
162
163 reg = _regulator(0.0)
164 flow = FlowConditions(rho=39.0, mu=1.8e-5, p_upstream=3000.0 * PSI)
165 dp = reg.pressure_drop(mdot, flow) # type: ignore[attr-defined]
166 assert dp == (3000.0 * PSI) - (500.0 * PSI - 20.0 * PSI * mdot / 0.05)
167 shut = reg.is_choked(3000.0 * PSI - 502.6 * PSI, flow) # type: ignore[attr-defined]
168 regulating = reg.is_choked( # type: ignore[attr-defined]
169 3000.0 * PSI - 502.4 * PSI, flow
170 )
171 assert shut and not regulating
None test_a_regulator_inside_its_lockup_rise_solves(float tank_psia)
object _regulated(float tank_psia, float rise_psi)
object _regulator(float rise_psi)
None test_a_check_valve_below_its_cracking_pressure_is_shut(float drop_psi)
object _checked(float drop_psi)
None test_with_no_rise_a_regulator_is_what_it_was(float mdot)
None test_a_check_valve_past_its_cracking_pressure_flows(float drop_psi)
object _pipe(str tag, float length=1.0)