STAR
Cal STAR — firmware, DAQ server, and engine design
Loading...
Searching...
No Matches
test_branch_direction.py
Go to the documentation of this file.
1"""A symmetric component does not care which way it was drawn.
2
3pid-designer reads a symbol's in/out from its left/right ports, so a vent or
4press solenoid placed mirrored is a branch drawn against its flow. The solve
5used to price every branch at its drawn upstream node -- the low-pressure end
6when the flow runs backwards -- and never let reverse flow choke: a Cv 4 valve
7on 3000 psia nitrogen to atmosphere passed 3.81 kg/s drawn with the flow and
80.47 drawn against it, both converged.
9
10One-way laws keep their drawn direction: a check valve, a regulator, a relief.
11"""
12
13from __future__ import annotations
14
15import pytest
16
17from feedtwin.comps import build_component
18from feedtwin.model import ComponentInstance, Param, Provenance
19from feedtwin.solve.network import Network
20from feedtwin.solve.steady import solve_steady
21
22PSI = 6894.757293168361
23M = Provenance.MANUFACTURER
24
25
26def _valve() -> object:
27 return build_component(
28 ComponentInstance.build(
29 "V",
30 "valve",
31 {"Cv": Param(4.0, "Cv", M, ""), "bore": Param(9.5, "mm", M, "")},
32 )
33 )
34
35
36def _pipe() -> object:
37 return build_component(
38 ComponentInstance.build(
39 "L",
40 "pipe",
41 {"length": Param(1.0, "m", M, ""), "bore": Param(6.0, "mm", M, "")},
42 )
43 )
44
45
46def _flow(tank_psia: float, drawn_with_the_flow: bool, part: object) -> float:
47 """A gas tank to atmosphere through one component, drawn either way.
48 Returned as the flow out of the tank."""
49 net = Network()
50 net.add_node("tank", "nitrogen", 293.0, pressure=tank_psia * PSI)
51 net.add_node("atm", "nitrogen", 293.0, pressure=14.7 * PSI)
52 for node in net.nodes.values():
53 node.phase = "gas"
54 if drawn_with_the_flow:
55 net.add_branch("X", part, "tank", "atm") # type: ignore[arg-type]
56 else:
57 net.add_branch("X", part, "atm", "tank") # type: ignore[arg-type]
58 result = solve_steady(net)
59 assert result.converged
60 return result.flows["X"] if drawn_with_the_flow else -result.flows["X"]
61
62
63@pytest.mark.parametrize("tank_psia", [30.0, 100.0, 500.0, 3000.0])
65 forward = _flow(tank_psia, True, _valve())
66 backward = _flow(tank_psia, False, _valve())
67 assert forward > 0.0
68 assert backward == pytest.approx(forward, rel=1e-6)
69
70
72 """3000 psia to atmosphere is far past the critical ratio either way."""
73 net = Network()
74 net.add_node("tank", "nitrogen", 293.0, pressure=3000.0 * PSI)
75 net.add_node("atm", "nitrogen", 293.0, pressure=14.7 * PSI)
76 for node in net.nodes.values():
77 node.phase = "gas"
78 valve = _valve()
79 net.add_branch("X", valve, "atm", "tank") # type: ignore[arg-type]
80 result = solve_steady(net)
81 assert result.converged
82 ceiling = valve.flow_ceiling(net.conditions("tank", 3000.0 * PSI)) # type: ignore[attr-defined]
83 assert result.flows["X"] == pytest.approx(-ceiling, rel=1e-6)
84 assert result.choked_branches == ["X"]
85
86
88 forward = _flow(100.0, True, _pipe())
89 backward = _flow(100.0, False, _pipe())
90 assert backward == pytest.approx(forward, rel=1e-6)
91
92
94 """Drawn against the gradient it holds: a one-way law is not mirrored."""
95 check = build_component(
96 ComponentInstance.build(
97 "CV",
98 "check_valve",
99 {
100 "Cv": Param(4.0, "Cv", M, ""),
101 "bore": Param(9.5, "mm", M, ""),
102 "cracking_pressure": Param(3.0, "psi", M, ""),
103 },
104 )
105 )
106 assert not check.symmetric
107 assert _valve().symmetric # type: ignore[attr-defined]
None test_a_gas_pipe_drawn_either_way_passes_the_same_flow()
float _flow(float tank_psia, bool drawn_with_the_flow, object part)
None test_a_valve_drawn_either_way_passes_the_same_flow(float tank_psia)
None test_a_check_valve_keeps_its_drawn_direction()