Skip to content

Commit 40c072d

Browse files
committed
Add implementation for simple dead end filtation unit
1 parent 15a7fe7 commit 40c072d

File tree

4 files changed

+335
-58
lines changed

4 files changed

+335
-58
lines changed

CADETPythonSimulator/residual.py

+67-3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,70 @@ def calculate_residual_visc_cstr():
7777
return 0
7878

7979

80-
def calculate_residual_def():
81-
"""Calculate the residual equations fo a dead end filtration equation."""
82-
raise NotImplementedError
80+
def calculate_residual_cake_vol_def(
81+
V_dot_f: float,
82+
rejection: np.ndarray,
83+
molar_volume: np.ndarray,
84+
c_in: np.ndarray,
85+
V_dot_C: float
86+
) -> float:
87+
"""
88+
Residual equation for the Cake Volume.
89+
90+
Parameters
91+
----------
92+
V_dot_f : float
93+
Flowrate of incoming feed
94+
rejection : float
95+
Rejection of the filter
96+
gamma : float
97+
Portion of suspended material
98+
V_dot_C : float
99+
Change of Cake Volume
100+
"""
101+
return -V_dot_C + np.sum(rejection * molar_volume * c_in * V_dot_f)
102+
103+
104+
def calculate_residual_press_easy_def(
105+
V_dot_Perm: float,
106+
V_C: float,
107+
deltap: float,
108+
A: float,
109+
mu: float,
110+
Rm: float,
111+
alpha: float
112+
) -> float:
113+
"""
114+
Calculate the residual equations fo a dead end filtration equation for the pressure
115+
in the easy model.
116+
117+
Parameters
118+
----------
119+
V_dot_Perm : np.ndarray
120+
FLow of the Permeate through the membrane and Cake
121+
V_C : float
122+
Volume of the Cake
123+
deltap : float
124+
Pressure drop in this unit
125+
A : float
126+
Filtration area
127+
mu : float
128+
dynamic Viscosity
129+
Rm : float
130+
resistance of the medium
131+
alpha : float
132+
Specific cake resistance
133+
"""
134+
hyd_resistance = (Rm + alpha*V_C/A) * mu
135+
136+
return -V_dot_Perm + deltap * A *hyd_resistance
137+
138+
139+
140+
def calculate_residual_visc_def():
141+
"""
142+
Calculate the residual of the Viscosity equation of the CSTR.
143+
"""
144+
warnings.warn("Viscosity of def not yet implemented")
145+
146+
return 0

CADETPythonSimulator/unit_operation.py

+94-41
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@
1515
from CADETPythonSimulator.exception import NotInitializedError, CADETPythonSimError
1616
from CADETPythonSimulator.state import State, state_factory
1717
from CADETPythonSimulator.residual import (
18-
calculate_residual_volume_cstr, calculate_residual_concentration_cstr, calculate_residuals_visc_cstr
18+
calculate_residual_volume_cstr,
19+
calculate_residual_concentration_cstr,
20+
calculate_residual_visc_cstr,
21+
calculate_residual_press_easy_def,
22+
calculate_residual_cake_vol_def,
23+
calculate_residual_visc_def
1924
)
2025
from CADETPythonSimulator.rejection import RejectionBase
2126
from CADETPythonSimulator.cake_compressibility import CakeCompressibilityBase
@@ -595,73 +600,121 @@ class DeadEndFiltration(UnitOperationBase):
595600
Model for cake compressibility.
596601
597602
"""
598-
retentate = {
603+
cake = {
599604
'dimensions': (),
600-
'entries': {'c': 'n_comp', 'viscosity': 1, 'Rc': 1, 'mc': 'n_comp'},
605+
'entries': {'c': 'n_comp',
606+
'viscosity': 1,
607+
'pressure': 1,
608+
'cakevolume': 1,
609+
'permeate': 1
610+
},
601611
'n_inlet_ports': 1,
602612
}
603613
permeate = {
604614
'dimensions': (),
605615
'entries': {'c': 'n_comp', 'viscosity': 1, 'Volume': 1},
606616
'n_outlet_ports': 1,
607617
}
608-
_state_structures = ['retentate', 'permeate']
609-
610-
rejection_model = Typed(ty=RejectionBase)
611-
cake_compressibility_model = Typed(ty=CakeCompressibilityBase)
618+
_state_structures = ['cake', 'permeate']
612619

613620
membrane_area = UnsignedFloat()
614621
membrane_resistance = UnsignedFloat()
622+
specific_cake_resistance = UnsignedFloat()
623+
rejection = Typed(ty=RejectionBase)
615624

616625
_parameters = [
617626
'membrane_area',
618627
'membrane_resistance',
628+
'specific_cake_resistance',
629+
'rejection'
619630
]
620631

621-
def delta_p(self):
622-
raise NotImplementedError()
632+
def compute_residual(
633+
self,
634+
t: float,
635+
) -> NoReturn:
623636

624-
def specific_cake_resistance(self, delta_p: float) -> float:
625-
"""
626-
Compute specific resistance as a function of delta_p.
637+
Q_in = self.Q_in[0]
638+
Q_out = self.Q_out[0]
627639

628-
Parameters
629-
----------
630-
delta_p : float
631-
Pressure difference.
640+
c_in = self.states['cake']['c']
641+
c_in_dot = self.state_derivatives['cake']['c']
632642

633-
Returns
634-
-------
635-
float
636-
Specific cake resistance.
643+
V_C = self.states['cake']['cakevolume']
644+
V_dot_C = self.state_derivatives['cake']['cakevolume']
637645

638-
"""
639-
raise self.cake_compressibility_model.specific_cake_resistance(delta_p)
646+
V_p = self.states['cake']['permeate']
647+
Q_p = self.state_derivatives['cake']['cakevolume']
640648

641-
def compute_residual(
642-
self,
643-
t: float,
644-
y: np.ndarray,
645-
y_dot: np.ndarray,
646-
residual: np.ndarray
647-
) -> NoReturn:
648-
# 0, 1, 2
649-
# y = Vp, Rc, mc
650-
# TODO: Needs to be extended to include c_in / c_out
651-
# y = [*c_i_in], viscosity_in, Vp, Rc, mc, [*c_i_out], viscosity_out
649+
viscosity_in = self.states['cake']['viscosity']
650+
651+
c = self.states['permeate']['c']
652+
c_dot = self.state_derivatives['permeate']['c']
652653

653-
c_in = y[0: self.n_comp]
654-
viscosity_in = y[self.n_comp]
654+
V = self.states['permeate']['Volume']
655+
V_dot = self.state_derivatives['permeate']['Volume']
655656

656-
densities = self.component_system.densities
657+
deltap = self.states['cake']['pressure']
657658

658-
residual[self.n_dof_coupling + 0] = ((self.membrane_area*self.delta_p(t)/viscosity_in)/(self.membrane_resistance+y[1])) - y_dot[0]
659-
residual[self.n_dof_coupling + 1] = (1/self.membrane_area) * (y_dot[2] * self.specific_cake_resistance(self.p(t))) - y_dot[1]
659+
#parameters
660+
molecular_weights = self.component_system.molecular_weights
661+
molar_volume = self.component_system.molecular_volumes
662+
membrane_area = self.parameters['membrane_area']
663+
membrane_resistance = self.parameters['membrane_resistance']
664+
specific_cake_resistance = self.parameters['specific_cake_resistance']
660665

661-
residual[self.n_dof_coupling + 2] = ((self.c(t) * y_dot[0]) / (1-self.c(t)/self.density)) - y_dot[2]
666+
rejection = np.array(
667+
[self.rejection.get_rejection(mw) for mw in molecular_weights])
662668

663-
self.residuals['retentate']
664-
self.residuals['permeate']
669+
# Handle inlet DOFs, which are simply copied to the residual
670+
self.residuals['cake']['c'] = c_in
671+
self.residuals['cake']['cakevolume'] = calculate_residual_cake_vol_def(
672+
Q_in,
673+
rejection,
674+
molar_volume,
675+
c_in,
676+
V_dot_C
677+
)
678+
679+
self.residuals['cake']['pressure'] = calculate_residual_press_easy_def(
680+
Q_p,
681+
V_C,
682+
deltap,
683+
membrane_area,
684+
viscosity_in,
685+
membrane_resistance,
686+
specific_cake_resistance
687+
)
688+
689+
self.residuals['cake']['permeate'] = calculate_residual_volume_cstr(
690+
V_C,
691+
V_dot_C,
692+
Q_in,
693+
Q_p
694+
)
695+
696+
self.residuals['cake']['viscosity'] = calculate_residual_visc_def()
697+
698+
new_c_in = (1-rejection)*c_in
699+
700+
self.residuals['permeate']['c'] = calculate_residual_concentration_cstr(
701+
c,
702+
c_dot,
703+
V,
704+
V_dot,
705+
Q_p,
706+
Q_out,
707+
new_c_in
708+
)
709+
710+
self.residuals['permeate']['Volume'] = calculate_residual_volume_cstr(
711+
V,
712+
V_dot,
713+
Q_p,
714+
Q_out
715+
)
716+
717+
self.residuals['permeate']['viscosity'] = calculate_residual_visc_cstr()
665718

666719

667720

tests/test_residual.py

+112-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import pytest
33

44
from CADETPythonSimulator.residual import (
5-
calculate_residual_volume_cstr, calculate_residual_concentration_cstr
5+
calculate_residual_volume_cstr,
6+
calculate_residual_concentration_cstr,
7+
calculate_residual_cake_vol_def,
8+
calculate_residual_press_easy_def
69
)
710
from CADETPythonSimulator.exception import CADETPythonSimError
811

@@ -172,6 +175,114 @@ def test_calculation_cstr(self, parameters):
172175
np.testing.assert_equal(residual, parameters["expected"])
173176

174177

178+
# Testcase 1: Membrane rejects all
179+
TestCaseDEFCake_rejects_all = {
180+
"values": {
181+
"V_dot_f": 1.0,
182+
"rejection": np.array([1, 1]),
183+
"molar_volume": np.array([1, 1]),
184+
"c_in": np.array([0.5, 0.5]),
185+
"V_dot_C": 1.0
186+
},
187+
"expected": 0
188+
}
189+
190+
191+
# Testcase 2: Membrane rejects nothing
192+
TestCaseDEFCake_rejects_not = {
193+
"values": {
194+
"V_dot_f": 1.0,
195+
"rejection": np.array([0, 0]),
196+
"molar_volume": np.array([1, 1]),
197+
"c_in": np.array([0.5, 0.5]),
198+
"V_dot_C": 0.0
199+
},
200+
"expected": 0
201+
}
202+
203+
# Testcase 3: Membrane rejects only Component 2
204+
TestCaseDEFCake_rejects_2 = {
205+
"values": {
206+
"V_dot_f": 1.0,
207+
"rejection": np.array([0, 1]),
208+
"molar_volume": np.array([1, 1]),
209+
"c_in": np.array([0.5, 0.5]),
210+
"V_dot_C": 0.5
211+
},
212+
"expected": 0
213+
}
214+
215+
# Testcase 4: Component 2 is larger then 1
216+
TestCaseDEFCake_C2_le_C1 = {
217+
"values": {
218+
"V_dot_f": 1.0,
219+
"rejection": np.array([1, 1]),
220+
"molar_volume": np.array([0.5, 1]),
221+
"c_in": np.array([0.5, 0.5]),
222+
"V_dot_C": 0.75
223+
},
224+
"expected": 0
225+
}
226+
227+
228+
@pytest.mark.parametrize(
229+
"parameters",
230+
[
231+
TestCaseDEFCake_rejects_all,
232+
TestCaseDEFCake_rejects_not,
233+
TestCaseDEFCake_rejects_2,
234+
TestCaseDEFCake_C2_le_C1
235+
]
236+
)
237+
class TestResidualCakeVolDEF():
238+
def test_calculation_def(self, parameters):
239+
param_vec_cake_vol = parameters["values"].values()
240+
np.testing.assert_equal(
241+
calculate_residual_cake_vol_def(*param_vec_cake_vol),
242+
parameters["expected"]
243+
)
244+
245+
246+
# Case 1 : Equally large hyraulic resistance
247+
TestCaseDEFPressureDrop = {
248+
"values": {
249+
"V_dot_P": 1,
250+
"V_C": 1,
251+
"deltap": 0.5,
252+
"A": 1,
253+
"mu": 1,
254+
"Rm": 1,
255+
"alpha": 1,
256+
},
257+
"expected": 0
258+
}
259+
260+
# Case 2 : No cake yet
261+
TestCaseDEFPressureDrop_no_cake = {
262+
"values": {
263+
"V_dot_P": 0.5,
264+
"V_C": 0,
265+
"deltap": 0.5,
266+
"A": 1,
267+
"mu": 1,
268+
"Rm": 1,
269+
"alpha": 1,
270+
},
271+
"expected": 0
272+
}
273+
274+
275+
@pytest.mark.parametrize(
276+
"parameters",
277+
[
278+
TestCaseDEFPressureDrop,
279+
TestCaseDEFPressureDrop_no_cake
280+
]
281+
)
282+
class TestResidualPressureDropDEF():
283+
def test_calculation_def(self, parameters):
284+
param_vec_pressure = parameters["values"].values()
285+
residual = calculate_residual_press_easy_def(*param_vec_pressure)
175286
np.testing.assert_equal(residual, parameters["expected"])
176287

177288

@@ -198,14 +309,12 @@ def test_calculation_cstr(self, parameters):
198309
class TestResidualError():
199310

200311
def test_calculation_vol_cstr_error(self, parameters):
201-
202312
param_vec_volume = parameters["values"].values()
203313

204314
with pytest.raises(CADETPythonSimError):
205315
calculate_residual_volume_cstr(*list(param_vec_volume)[2:6])
206316

207317
def test_calculation_concentration_cstr_error(self, parameters):
208-
209318
param_vec_volume = parameters["values"].values()
210319

211320
with pytest.raises(CADETPythonSimError):

0 commit comments

Comments
 (0)