Skip to content

Commit 596667e

Browse files
committed
Add implementation for simple dead end filtation unit
1 parent e9d9be6 commit 596667e

File tree

4 files changed

+335
-58
lines changed

4 files changed

+335
-58
lines changed

CADETPythonSimulator/residual.py

Lines changed: 67 additions & 3 deletions
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

Lines changed: 94 additions & 41 deletions
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
@@ -593,73 +598,121 @@ class DeadEndFiltration(UnitOperationBase):
593598
Model for cake compressibility.
594599
595600
"""
596-
retentate = {
601+
cake = {
597602
'dimensions': (),
598-
'entries': {'c': 'n_comp', 'viscosity': 1, 'Rc': 1, 'mc': 'n_comp'},
603+
'entries': {'c': 'n_comp',
604+
'viscosity': 1,
605+
'pressure': 1,
606+
'cakevolume': 1,
607+
'permeate': 1
608+
},
599609
'n_inlet_ports': 1,
600610
}
601611
permeate = {
602612
'dimensions': (),
603613
'entries': {'c': 'n_comp', 'viscosity': 1, 'Volume': 1},
604614
'n_outlet_ports': 1,
605615
}
606-
_state_structures = ['retentate', 'permeate']
607-
608-
rejection_model = Typed(ty=RejectionBase)
609-
cake_compressibility_model = Typed(ty=CakeCompressibilityBase)
616+
_state_structures = ['cake', 'permeate']
610617

611618
membrane_area = UnsignedFloat()
612619
membrane_resistance = UnsignedFloat()
620+
specific_cake_resistance = UnsignedFloat()
621+
rejection = Typed(ty=RejectionBase)
613622

614623
_parameters = [
615624
'membrane_area',
616625
'membrane_resistance',
626+
'specific_cake_resistance',
627+
'rejection'
617628
]
618629

619-
def delta_p(self):
620-
raise NotImplementedError()
630+
def compute_residual(
631+
self,
632+
t: float,
633+
) -> NoReturn:
621634

622-
def specific_cake_resistance(self, delta_p: float) -> float:
623-
"""
624-
Compute specific resistance as a function of delta_p.
635+
Q_in = self.Q_in[0]
636+
Q_out = self.Q_out[0]
625637

626-
Parameters
627-
----------
628-
delta_p : float
629-
Pressure difference.
638+
c_in = self.states['cake']['c']
639+
c_in_dot = self.state_derivatives['cake']['c']
630640

631-
Returns
632-
-------
633-
float
634-
Specific cake resistance.
641+
V_C = self.states['cake']['cakevolume']
642+
V_dot_C = self.state_derivatives['cake']['cakevolume']
635643

636-
"""
637-
raise self.cake_compressibility_model.specific_cake_resistance(delta_p)
644+
V_p = self.states['cake']['permeate']
645+
Q_p = self.state_derivatives['cake']['cakevolume']
638646

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

651-
c_in = y[0: self.n_comp]
652-
viscosity_in = y[self.n_comp]
652+
V = self.states['permeate']['Volume']
653+
V_dot = self.state_derivatives['permeate']['Volume']
653654

654-
densities = self.component_system.densities
655+
deltap = self.states['cake']['pressure']
655656

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

659-
residual[self.n_dof_coupling + 2] = ((self.c(t) * y_dot[0]) / (1-self.c(t)/self.density)) - y_dot[2]
664+
rejection = np.array(
665+
[self.rejection.get_rejection(mw) for mw in molecular_weights])
660666

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

664717

665718

tests/test_residual.py

Lines changed: 112 additions & 3 deletions
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

173176
residual = calculate_residual_volume_cstr(*param_vec_volume)
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)