Skip to content

Commit 178155c

Browse files
committed
Add Mixer block for 2-stream algebraic mixing
1 parent 199625a commit 178155c

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

src/pathsim_chem/process/mixer.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#########################################################################################
2+
##
3+
## 2-Stream Mixer Block
4+
##
5+
#########################################################################################
6+
7+
# IMPORTS ===============================================================================
8+
9+
from pathsim.blocks.function import Function
10+
11+
# BLOCKS ================================================================================
12+
13+
class Mixer(Function):
14+
"""Algebraic 2-stream mixer with mass and energy balance.
15+
16+
Mixes two streams by mass balance (additive flows) and energy balance
17+
(flow-weighted temperature mixing). No phase change or reaction.
18+
19+
Mathematical Formulation
20+
-------------------------
21+
.. math::
22+
23+
F_{out} = F_1 + F_2
24+
25+
.. math::
26+
27+
T_{out} = \\frac{F_1 \\, T_1 + F_2 \\, T_2}{F_{out}}
28+
29+
Parameters
30+
----------
31+
None — this is a purely algebraic block.
32+
"""
33+
34+
input_port_labels = {
35+
"F_1": 0,
36+
"T_1": 1,
37+
"F_2": 2,
38+
"T_2": 3,
39+
}
40+
41+
output_port_labels = {
42+
"F_out": 0,
43+
"T_out": 1,
44+
}
45+
46+
def __init__(self):
47+
super().__init__(func=self._eval)
48+
49+
def _eval(self, F_1, T_1, F_2, T_2):
50+
F_out = F_1 + F_2
51+
if F_out > 0:
52+
T_out = (F_1 * T_1 + F_2 * T_2) / F_out
53+
else:
54+
T_out = 0.5 * (T_1 + T_2)
55+
return (F_out, T_out)

tests/process/test_mixer.py

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
########################################################################################
2+
##
3+
## TESTS FOR
4+
## 'process.mixer.py'
5+
##
6+
########################################################################################
7+
8+
# IMPORTS ==============================================================================
9+
10+
import unittest
11+
12+
from pathsim_chem.process import Mixer
13+
14+
15+
# TESTS ================================================================================
16+
17+
class TestMixer(unittest.TestCase):
18+
"""Test the 2-stream mixer block."""
19+
20+
def test_port_labels(self):
21+
"""Test port label definitions."""
22+
self.assertEqual(Mixer.input_port_labels["F_1"], 0)
23+
self.assertEqual(Mixer.input_port_labels["T_1"], 1)
24+
self.assertEqual(Mixer.input_port_labels["F_2"], 2)
25+
self.assertEqual(Mixer.input_port_labels["T_2"], 3)
26+
self.assertEqual(Mixer.output_port_labels["F_out"], 0)
27+
self.assertEqual(Mixer.output_port_labels["T_out"], 1)
28+
29+
def test_mass_balance(self):
30+
"""F_out should equal F_1 + F_2."""
31+
M = Mixer()
32+
M.inputs[0] = 5.0 # F_1
33+
M.inputs[1] = 350.0 # T_1
34+
M.inputs[2] = 3.0 # F_2
35+
M.inputs[3] = 300.0 # T_2
36+
37+
M.update(None)
38+
39+
self.assertAlmostEqual(M.outputs[0], 8.0) # F_out
40+
41+
def test_energy_balance(self):
42+
"""F_1*T_1 + F_2*T_2 should equal F_out*T_out."""
43+
M = Mixer()
44+
F_1, T_1 = 5.0, 350.0
45+
F_2, T_2 = 3.0, 300.0
46+
47+
M.inputs[0] = F_1
48+
M.inputs[1] = T_1
49+
M.inputs[2] = F_2
50+
M.inputs[3] = T_2
51+
52+
M.update(None)
53+
54+
F_out = M.outputs[0]
55+
T_out = M.outputs[1]
56+
self.assertAlmostEqual(F_1 * T_1 + F_2 * T_2, F_out * T_out, places=8)
57+
58+
def test_equal_temperatures(self):
59+
"""When both streams at same T, output T should match."""
60+
M = Mixer()
61+
M.inputs[0] = 5.0
62+
M.inputs[1] = 350.0
63+
M.inputs[2] = 3.0
64+
M.inputs[3] = 350.0
65+
66+
M.update(None)
67+
68+
self.assertAlmostEqual(M.outputs[1], 350.0)
69+
70+
def test_zero_flow_one_stream(self):
71+
"""With one stream at zero flow, output should match the other."""
72+
M = Mixer()
73+
M.inputs[0] = 0.0
74+
M.inputs[1] = 300.0
75+
M.inputs[2] = 5.0
76+
M.inputs[3] = 400.0
77+
78+
M.update(None)
79+
80+
self.assertAlmostEqual(M.outputs[0], 5.0)
81+
self.assertAlmostEqual(M.outputs[1], 400.0)
82+
83+
def test_zero_total_flow(self):
84+
"""With zero total flow, should not crash."""
85+
M = Mixer()
86+
M.inputs[0] = 0.0
87+
M.inputs[1] = 300.0
88+
M.inputs[2] = 0.0
89+
M.inputs[3] = 400.0
90+
91+
M.update(None)
92+
93+
self.assertAlmostEqual(M.outputs[0], 0.0)
94+
95+
96+
# RUN TESTS LOCALLY ====================================================================
97+
98+
if __name__ == '__main__':
99+
unittest.main(verbosity=2)

0 commit comments

Comments
 (0)