Skip to content

Commit 9916c99

Browse files
committed
[WIP] Overhaul Fractionator
1 parent ac37de1 commit 9916c99

File tree

5 files changed

+93
-101
lines changed

5 files changed

+93
-101
lines changed

CADETProcess/fractionation/fractionationOptimizer.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ def _setup_fractionator(
125125
Fractionator
126126
The Fractionator object that has been set up using the provided arguments.
127127
"""
128-
frac = Fractionator(
128+
frac = Fractionator.from_simulation_results(
129129
simulation_results,
130130
components=components,
131131
use_total_concentration_components=use_total_concentration_components,

CADETProcess/fractionation/fractionator.py

Lines changed: 54 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import os
21
from collections import defaultdict
32
from functools import wraps
43
from typing import Any, Callable, Optional
@@ -7,21 +6,20 @@
76
from addict import Dict
87
from matplotlib.axes import Axes
98

10-
from CADETProcess import CADETProcessError, plotting, settings
9+
from CADETProcess import CADETProcessError, plotting
1110
from CADETProcess.dataStructure import String
1211
from CADETProcess.dynamicEvents import Event, EventHandler
1312
from CADETProcess.fractionation.fractions import Fraction, FractionPool
1413
from CADETProcess.performance import Performance
15-
from CADETProcess.processModel import ComponentSystem, Process
16-
from CADETProcess.simulationResults import SimulationResults
14+
from CADETProcess.processModel import ComponentSystem, ProcessMetaInformation
1715
from CADETProcess.solution import SolutionIO, slice_solution
1816

1917
__all__ = ["Fractionator"]
2018

2119

2220
class Fractionator(EventHandler):
2321
"""
24-
Class for Chromatogram Fractionation.
22+
Class for fractionation of Chromatograms.
2523
2624
This class is responsible for setting events for starting and ending fractionation,
2725
handling multiple chromatograms, and calculating various performance metrics.
@@ -33,6 +31,7 @@ class Fractionator(EventHandler):
3331
performance_keys : list
3432
Keys for performance metrics including mass, concentration, purity, recovery,
3533
productivity, and eluent consumption.
34+
3635
"""
3736

3837
name = String(default="Fractionator")
@@ -47,9 +46,10 @@ class Fractionator(EventHandler):
4746

4847
def __init__(
4948
self,
50-
simulation_results: SimulationResults,
49+
chromatograms: SolutionIO | list[SolutionIO],
50+
process_meta_information: ProcessMetaInformation,
5151
components: Optional[list[str]] = None,
52-
use_total_concentration_components: bool = True,
52+
use_total_concentration_components: Optional[bool] = True,
5353
*args: Any,
5454
**kwargs: Any,
5555
) -> None:
@@ -58,83 +58,75 @@ def __init__(
5858
5959
Parameters
6060
----------
61-
simulation_results : SimulationResults
62-
Simulation results containing chromatograms.
63-
components : list, optional
64-
List of components to be fractionated. Default is None.
65-
use_total_concentration_components : bool, optional
66-
Use total concentration components. Default is True.
61+
chromatograms : SolutionIO | list[SolutionIO]
62+
Chromatograms to be fractionated.
63+
process_meta_information: ProcessMetaInformation
64+
Process meta information.
65+
components : Optional[list[str]]
66+
List of components to be fractionated. If None, all components are
67+
considered. The default is None.
68+
use_total_concentration_components : Optional[bool]
69+
If True, use the total concentration of components, i.e. the sum of all
70+
subspecies of each component. The default is True.
6771
*args
6872
Variable length argument list.
6973
**kwargs
7074
Arbitrary keyword arguments.
71-
"""
72-
self.components: Optional[list[str]] = components
73-
self.use_total_concentration_components: bool = use_total_concentration_components
74-
self.simulation_results = simulation_results
75-
76-
super().__init__(*args, **kwargs)
7775
78-
@property
79-
def simulation_results(self) -> SimulationResults:
80-
"""SimulationResults: The simulation results containing the chromatograms."""
81-
return self._simulation_results
82-
83-
@simulation_results.setter
84-
def simulation_results(self, simulation_results: SimulationResults) -> None:
8576
"""
86-
Set the simulation results.
77+
if not isinstance(chromatograms, list):
78+
chromatograms = [chromatograms]
8779

88-
Parameters
89-
----------
90-
simulation_results : SimulationResults
91-
Simulation results containing chromatograms.
80+
self.process_meta_information = process_meta_information
9281

93-
Raises
94-
------
95-
TypeError
96-
If simulation_results is not of type SimulationResults.
97-
CADETProcessError
98-
If the simulation results do not contain any chromatograms.
99-
"""
100-
if not isinstance(simulation_results, SimulationResults):
101-
raise TypeError("Expected SimulationResults")
82+
for chrom in chromatograms:
83+
if chrom.component_system is not self.component_system:
84+
raise CADETProcessError("Component systems do not match.")
10285

103-
if len(simulation_results.chromatograms) == 0:
104-
raise CADETProcessError("Simulation results do not contain chromatogram")
86+
component_system = chromatograms[0].component_system
10587

106-
self._simulation_results = simulation_results
88+
if components is not None:
89+
for comp in components:
90+
if comp not in component_system:
91+
raise CADETProcessError(
92+
f"Could not find component {comp} in component system."
93+
)
94+
95+
self.components = components
96+
self.use_total_concentration_components = use_total_concentration_components
10797

10898
self._chromatograms = [
10999
slice_solution(
110100
chrom,
111101
components=self.components,
112102
use_total_concentration_components=self.use_total_concentration_components,
113103
)
114-
for chrom in simulation_results.chromatograms
104+
for chrom in chromatograms
115105
]
116106

117107
m_feed = np.zeros((self.component_system.n_comp,))
118108
counter = 0
119-
for comp, indices in simulation_results.component_system.indices.items():
109+
for comp, indices in self.component_system.indices.items():
120110
if comp in self.component_system.names:
121-
m_feed_comp = simulation_results.process.m_feed[indices]
111+
m_feed_comp = process_meta_information.m_feed[indices]
122112
if self.use_total_concentration_components:
123113
m_feed[counter] = np.sum(m_feed_comp)
124114
counter += 1
125115
else:
126116
n_species = len(indices)
127117
m_feed[counter : counter + n_species] = m_feed_comp
128118
counter += n_species
129-
self.m_feed: np.ndarray = m_feed
119+
self.m_feed = m_feed
130120

131121
self._fractionation_states = Dict({chrom: [] for chrom in self.chromatograms})
132122
self._chromatogram_events = Dict({chrom: [] for chrom in self.chromatograms})
133123

134-
self._cycle_time = self.process.cycle_time
124+
self._cycle_time = self.process_meta_information.cycle_time
135125

136126
self.reset()
137127

128+
super().__init__(*args, **kwargs)
129+
138130
@property
139131
def component_system(self) -> ComponentSystem:
140132
"""ComponentSystem: The component system of the chromatograms."""
@@ -196,13 +188,23 @@ def chromatogram_events(self) -> dict[SolutionIO, list[Event]]:
196188
return chrom_events
197189

198190
@property
199-
def process(self) -> Process:
200-
"""Process: The process from the simulation results."""
201-
return self.simulation_results.process
191+
def process_meta_information(self) -> ProcessMetaInformation:
192+
"""Process: from the simulation results."""
193+
return self._process_meta_information
194+
195+
@process_meta_information.setter
196+
def process_meta_information(
197+
self,
198+
process_meta_information: ProcessMetaInformation
199+
) -> None:
200+
if not isinstance(process_meta_information, ProcessMetaInformation):
201+
raise TypeError("Expected ProcessMetaInformation.")
202+
203+
self._process_meta_information = process_meta_information
202204

203205
@property
204206
def n_comp(self) -> int:
205-
"""int: Number of components to be fractionized."""
207+
"""int: Number of components to be fractionated."""
206208
return self.chromatograms[0].n_comp
207209

208210
@property
@@ -745,41 +747,6 @@ def section_dependent_parameters(self) -> Dict:
745747
"""dict: Section dependent parameters of the fractionator."""
746748
return self.parameters
747749

748-
def save(
749-
self,
750-
case_dir: str,
751-
start: float = 0,
752-
end: Optional[float] = None,
753-
) -> None:
754-
"""
755-
Save chromatogram and purity plots to a specified directory.
756-
757-
Parameters
758-
----------
759-
case_dir : str
760-
Directory name within the working directory to save plots.
761-
start : float, optional
762-
Start time for plotting purity, default is 0.
763-
end : Optional[float]
764-
End time for plotting purity. If None, includes all data.
765-
"""
766-
path = os.path.join(settings.working_directory, case_dir)
767-
768-
for index, chrom in enumerate(self.chromatograms):
769-
chrom.plot(save_path=path + f"/chrom_{index}.png")
770-
chrom.plot_purity(
771-
start=start, end=end, save_path=path + "/chrom_purity.png"
772-
)
773-
774-
for chrom in enumerate(self.chromatograms):
775-
self.plot_fraction_signal(
776-
chromatogram=chrom,
777-
start=start,
778-
end=end,
779-
save_path=path + f"/fractionation_signal_{index}.png",
780-
index=index,
781-
)
782-
783750
def __str__(self) -> str:
784751
"""str: String representation of the fractionator."""
785752
return self.__class__.__name__
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import unittest
2+
3+
from CADETProcess.fractionation import Fractionator
4+
from CADETProcess.processModel import ComponentSystem
5+
from solution_fixtures import TestSolutionIOConstant, TestSolutionIOGaussian
6+
7+
component_system = ComponentSystem(['A', 'B'])
8+
9+
test_solution_const = TestSolutionIOConstant(component_system)
10+
test_solution_gauss = TestSolutionIOGaussian(component_system)
11+
12+
13+
frac = Fractionator()
14+
15+
16+
class Test_Fractionator(unittest.TestCase):
17+
18+
def __init__(self, methodName='runTest'):
19+
super().__init__(methodName)
20+
21+
22+
if __name__ == '__main__':
23+
unittest.main()

tests/test_fractions.py renamed to tests/fractionation/test_fractions.py

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,37 @@
11
import unittest
22

3-
import CADETProcess
43
import numpy as np
4+
from CADETProcess import CADETProcessError
5+
from CADETProcess.fractionation import Fraction, FractionPool
56

67

78
class Test_Fractions(unittest.TestCase):
89
def create_fractions(self):
910
m_0 = np.array([0, 0])
10-
frac0 = CADETProcess.fractionation.Fraction(m_0, 1)
11+
frac0 = Fraction(m_0, 1)
1112
m_1 = np.array([3, 0])
12-
frac1 = CADETProcess.fractionation.Fraction(m_1, 2)
13+
frac1 = Fraction(m_1, 2)
1314
m_2 = np.array([1, 2])
14-
frac2 = CADETProcess.fractionation.Fraction(m_2, 3)
15+
frac2 = Fraction(m_2, 3)
1516
m_3 = np.array([0, 2])
16-
frac3 = CADETProcess.fractionation.Fraction(m_3, 3)
17+
frac3 = Fraction(m_3, 3)
1718
m_4 = np.array([0, 0])
18-
frac4 = CADETProcess.fractionation.Fraction(m_4, 0)
19+
frac4 = Fraction(m_4, 0)
1920

2021
return frac0, frac1, frac2, frac3, frac4
2122

2223
def create_pools(self):
2324
fractions = self.create_fractions()
2425

25-
pool_waste = CADETProcess.fractionation.FractionPool(n_comp=2)
26+
pool_waste = FractionPool(n_comp=2)
2627
pool_waste.add_fraction(fractions[0])
2728
pool_waste.add_fraction(fractions[1])
2829
pool_waste.add_fraction(fractions[2])
2930

30-
pool_1 = CADETProcess.fractionation.FractionPool(n_comp=2)
31+
pool_1 = FractionPool(n_comp=2)
3132
pool_1.add_fraction(fractions[3])
3233

33-
pool_2 = CADETProcess.fractionation.FractionPool(n_comp=2)
34+
pool_2 = FractionPool(n_comp=2)
3435
pool_2.add_fraction(fractions[4])
3536

3637
return pool_waste, pool_1, pool_2
@@ -69,9 +70,9 @@ def test_n_comp(self):
6970
self.assertEqual(pools[0].fractions[0].n_comp, 2)
7071

7172
m_wrong_n_comp = np.array([0, 0, 0])
72-
frac_wrong_n_comp = CADETProcess.fractionation.Fraction(m_wrong_n_comp, 1)
73+
frac_wrong_n_comp = Fraction(m_wrong_n_comp, 1)
7374

74-
with self.assertRaises(CADETProcess.CADETProcessError):
75+
with self.assertRaises(CADETProcessError):
7576
pools[0].add_fraction(frac_wrong_n_comp)
7677

7778
def test_pool_mass(self):

tests/test_solution.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def __init__(self, *args, breaks=None, values=None, **kwargs):
6767

6868
if breaks is None:
6969
mid = self.cycle_time / 2
70-
breaks = [(mid - mid / (i+2), mid + mid / (i+2)) for i in range(self.n_comp)]
70+
breaks = [(mid - mid / (i + 2), mid + mid / (i + 2)) for i in range(self.n_comp)]
7171

7272
if values is None:
7373
values = np.arange(self.n_comp) + 1
@@ -89,13 +89,14 @@ def __init__(self, *args, mu=None, sigma=None, **kwargs):
8989
super().__init__(*args, **kwargs)
9090

9191
if mu is None:
92-
mu = [(i+1) * self.cycle_time / (self.n_comp + 1) for i in range(self.n_comp)]
92+
mu = [(i + 1) * self.cycle_time / (self.n_comp + 1) for i in range(self.n_comp)]
9393
if sigma is None:
9494
sigma = np.arange(self.n_comp) + 1
9595

9696
for comp_i, (mu_i, sigma_i) in enumerate(zip(mu, sigma)):
9797
self.solution[:, comp_i] = stats.norm.pdf(self.time, mu_i, sigma_i)
9898

99+
99100
comp_2 = ComponentSystem(2)
100101

101102
comp_2_3_species = ComponentSystem()

0 commit comments

Comments
 (0)