Skip to content

Commit f7d077d

Browse files
committed
Add Valve block with Cv pressure-drop flow equation
1 parent 0642c4c commit f7d077d

2 files changed

Lines changed: 156 additions & 0 deletions

File tree

src/pathsim_chem/process/valve.py

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#########################################################################################
2+
##
3+
## Pressure-Drop Valve Block
4+
##
5+
#########################################################################################
6+
7+
# IMPORTS ===============================================================================
8+
9+
import numpy as np
10+
11+
from pathsim.blocks.function import Function
12+
13+
# BLOCKS ================================================================================
14+
15+
class Valve(Function):
16+
"""Algebraic pressure-drop valve with standard flow equation.
17+
18+
Models an isenthalpic valve (no temperature change for liquids)
19+
with flow proportional to the square root of the pressure drop.
20+
21+
Mathematical Formulation
22+
-------------------------
23+
.. math::
24+
25+
F = C_v \\sqrt{|P_{in} - P_{out}|} \\cdot \\mathrm{sign}(P_{in} - P_{out})
26+
27+
.. math::
28+
29+
T_{out} = T_{in}
30+
31+
Parameters
32+
----------
33+
Cv : float
34+
Valve flow coefficient. Must be positive.
35+
"""
36+
37+
input_port_labels = {
38+
"P_in": 0,
39+
"P_out": 1,
40+
"T_in": 2,
41+
}
42+
43+
output_port_labels = {
44+
"F": 0,
45+
"T_out": 1,
46+
}
47+
48+
def __init__(self, Cv=1.0):
49+
if Cv <= 0:
50+
raise ValueError(f"'Cv' must be positive but is {Cv}")
51+
52+
self.Cv = Cv
53+
super().__init__(func=self._eval)
54+
55+
def _eval(self, P_in, P_out, T_in):
56+
dP = P_in - P_out
57+
F = self.Cv * np.sqrt(np.abs(dP)) * np.sign(dP)
58+
return (F, T_in)

tests/process/test_valve.py

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
########################################################################################
2+
##
3+
## TESTS FOR
4+
## 'process.valve.py'
5+
##
6+
########################################################################################
7+
8+
# IMPORTS ==============================================================================
9+
10+
import unittest
11+
import numpy as np
12+
13+
from pathsim_chem.process import Valve
14+
15+
16+
# TESTS ================================================================================
17+
18+
class TestValve(unittest.TestCase):
19+
"""Test the pressure-drop valve block."""
20+
21+
def test_init_default(self):
22+
"""Test default Cv."""
23+
V = Valve()
24+
self.assertEqual(V.Cv, 1.0)
25+
26+
def test_init_custom(self):
27+
"""Test custom Cv."""
28+
V = Valve(Cv=5.0)
29+
self.assertEqual(V.Cv, 5.0)
30+
31+
def test_init_validation(self):
32+
"""Test input validation."""
33+
with self.assertRaises(ValueError):
34+
Valve(Cv=0)
35+
with self.assertRaises(ValueError):
36+
Valve(Cv=-1)
37+
38+
def test_port_labels(self):
39+
"""Test port label definitions."""
40+
self.assertEqual(Valve.input_port_labels["P_in"], 0)
41+
self.assertEqual(Valve.input_port_labels["P_out"], 1)
42+
self.assertEqual(Valve.input_port_labels["T_in"], 2)
43+
self.assertEqual(Valve.output_port_labels["F"], 0)
44+
self.assertEqual(Valve.output_port_labels["T_out"], 1)
45+
46+
def test_forward_flow(self):
47+
"""Flow should be positive when P_in > P_out."""
48+
V = Valve(Cv=2.0)
49+
V.inputs[0] = 200000.0 # P_in
50+
V.inputs[1] = 100000.0 # P_out
51+
V.inputs[2] = 350.0 # T_in
52+
53+
V.update(None)
54+
55+
F = V.outputs[0]
56+
self.assertGreater(F, 0)
57+
expected = 2.0 * np.sqrt(100000.0)
58+
self.assertAlmostEqual(F, expected, places=4)
59+
60+
def test_reverse_flow(self):
61+
"""Flow should be negative when P_in < P_out."""
62+
V = Valve(Cv=2.0)
63+
V.inputs[0] = 100000.0 # P_in
64+
V.inputs[1] = 200000.0 # P_out
65+
V.inputs[2] = 350.0
66+
67+
V.update(None)
68+
69+
F = V.outputs[0]
70+
self.assertLess(F, 0)
71+
72+
def test_zero_dp(self):
73+
"""No flow when pressures are equal."""
74+
V = Valve(Cv=2.0)
75+
V.inputs[0] = 101325.0
76+
V.inputs[1] = 101325.0
77+
V.inputs[2] = 350.0
78+
79+
V.update(None)
80+
81+
self.assertAlmostEqual(V.outputs[0], 0.0)
82+
83+
def test_temperature_passthrough(self):
84+
"""Temperature should pass through unchanged (isenthalpic)."""
85+
V = Valve()
86+
V.inputs[0] = 200000.0
87+
V.inputs[1] = 100000.0
88+
V.inputs[2] = 375.0
89+
90+
V.update(None)
91+
92+
self.assertAlmostEqual(V.outputs[1], 375.0)
93+
94+
95+
# RUN TESTS LOCALLY ====================================================================
96+
97+
if __name__ == '__main__':
98+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)