Hi Brian and welcome to the CADET-Forum!
Optimizing SMB systems is certainly not trivial but it can be done. It involves some advanced features of CADET-Process which might seem a bit unintuitive at first but I haven’t found a more elegant solution yet. So I’m interested in your feedback.
First of all, calculating key performance indicators such as eluent consumption, recovery, purity etc. requires some additional post-processing in CADET-Process. For this purpose, we provide a fractionation
module.
Depending on how you want to set up your optimization problem, this can be trivial, because you simply define a single fraction per outlet, that simply collects the entire stream. However, since we also allow automatic determination of fractionation times, I would suggest setting up a FractionationOptimizer
.
You can include this in your processing pipeline for defining optimization problems. For a very similar example, refer to one of our SMB case studies.
process = smb_builder.build_process()
optimization_problem.add_evaluation_object(process)
optimization_problem.add_variable(
'switch_time.time',
lb=150, ub=600,
transform='auto'
)
optimization_problem.add_variable(
'cycle_time',
)
optimization_problem.add_variable_dependency(
'cycle_time', 'switch_time.time', lambda x: 4 * x
)
optimization_problem.add_variable(
'flow_sheet.eluent.flow_rate',
lb=1e-7, ub=10e-7,
transform='auto'
)
optimization_problem.add_variable(
name='w_r',
parameter_path='flow_sheet.output_states.zone_III_outlet',
lb=0, ub=1,
pre_processing=lambda x: [x[0], 1 - x[0]],
)
optimization_problem.add_variable(
name='w_e',
parameter_path='flow_sheet.output_states.zone_I_outlet',
lb=0, ub=1,
pre_processing=lambda x: [x[0], 1 - x[0]],
)
# Setup Simulator
process_simulator = Cadet(options.cadet_path)
process_simulator.evaluate_stationarity = True
optimization_problem.add_evaluator(process_simulator)
# Setup Fractionator
frac_opt = FractionationOptimizer()
optimization_problem.add_evaluator(
frac_opt,
kwargs={'purity_required': [0.95, 0.95]}
)
# Setup Objectives
eluent_consumption = EluentConsumption()
optimization_problem.add_objective(
eluent_consumption,
n_objectives=2,
requires=[process_simulator, frac_opt],
minimize=False,
)
Note that I added both switch_time
and cycle_time
as optimization variables. By adding a variable dependency, cycle_time
is always kept at 4*swich_time
. At some point we could consider allowing event dependencies on the cycle time but for now this is a “good enough” workaround.
Then, w_r
and w_e
need a special pre_processing
feature. This ensures that at no point the sum of the split ratios split is larger than one. Again, this could be improved in the future by separating the assembly of actual values before setting them on FlowSheet
level, but this way we always avoid inconsistent states.
For process evaluation, the fractionation optimizer is set up. It will automatically check all outlets that were specified as product_outlet
in the FlowSheet
and ensures that only regions with a cumulative purity higher than the one specified are accounted for.
Finally, the EluentConsumption
performance indicator automatically detects Inlets
that were specified as eluent_source
and determines the volume that has entered the system. For it to be evaluated, it requires the process_simulator
, as well as the fractionation
optimizer.
Note that we suggest using multi-objective optimization.
Hope that helps. Let us know if you have further questions.
Edit: I just realized that some of these features might actually be still in beta
so you would have to install the dev
version of CADET-Process.
pip install git+https://github.com/fau-advanced-separations/CADET-Process.git@dev
I hope to release a new version soon™ .
By the way, we’re happy to announce our very first CADET-Workshop in the United States, hosted by our friends at RPI in Troy, NY, May 22-24, 2024. It would be great to meet you there and I’d be more than happy to do offer some more hands-on work with you on your particular problem(s). For more information on how to register, see also here!