Steady-State Recycling Process#

In addition to the recycled fraction, fresh feed can also be injected in each cycle, resulting in the formation of a cyclic steady-state. This process, called closed-loop steady-state recycling (CL-SSR), can achieve higher productivity compared to CLR. However, due to additional dispersion in the system periphery, maintaining the separation of components generated during the passage of the column is difficult to realize. Hence, determining the optimal time at which to add new feed is therefore complex. To overcome this problem, a tank can be inserted in which the recycling fraction and new feed are mixed. The recycling fraction and new feed are then injected together in a process called mixed-recycle steady-state recycling (MR-SSR). A schematic flow diagram of the MR-SSR process is shown below.

../../_images/mrssr_flow_sheet.svg

Flow sheet for mixed-recycle steady-state recycling process.#

For this demonstration, consider a two-component system with a Langmuir isotherm.

Component System#

from CADETProcess.processModel import ComponentSystem

component_system = ComponentSystem(['A', 'B'])

Binding Model#

from CADETProcess.processModel import Langmuir

binding_model = Langmuir(component_system, name='langmuir')
binding_model.is_kinetic = False
binding_model.adsorption_rate = [0.02, 0.03]
binding_model.desorption_rate = [1, 1]
binding_model.capacity = [100, 100]

Unit Operations#

from CADETProcess.processModel import (
    Inlet, Cstr, LumpedRateModelWithoutPores, Outlet
)
feed = Inlet(component_system, name='feed')
feed.c = [10, 10]

eluent = Inlet(component_system, name='eluent')
eluent.c = [0, 0]

tank = Cstr(component_system, name='tank')
tank.V = 0.001
tank.c = [10, 10]

column = LumpedRateModelWithoutPores(component_system, name='column')
column.binding_model = binding_model

column.length = 0.6
column.diameter = 0.024
column.axial_dispersion = 4.7e-7
column.total_porosity = 0.7

column.solution_recorder.write_solution_bulk = True

outlet = Outlet(component_system, name='outlet')

Flow Sheet#

from CADETProcess.processModel import FlowSheet

flow_sheet = FlowSheet(component_system)

flow_sheet.add_unit(feed, feed_inlet=True)
flow_sheet.add_unit(eluent, eluent_inlet=True)
flow_sheet.add_unit(tank)
flow_sheet.add_unit(column)
flow_sheet.add_unit(outlet, product_outlet=True)

flow_sheet.add_connection(feed, tank)
flow_sheet.add_connection(tank, column)
flow_sheet.add_connection(eluent, column)
flow_sheet.add_connection(column, tank)
flow_sheet.add_connection(column, outlet)

Process#

To realize the recycling, the output_state of the column needs to be modified. To reduce the number of event times that need to be specified, event dependencies are specified which enforce that always either feed or eluent are being pumped through the column.

../../_images/mrssr_events.svg

Events for mixed-recycle steady-state recycling process with event dependencies.#

from CADETProcess.processModel import Process
process = Process(flow_sheet, 'mrssr')

Q = 60/(60*1e6)

# Create Events and Durations
process.add_event('feed_on', 'flow_sheet.feed.flow_rate', Q)
process.add_event('feed_off', 'flow_sheet.feed.flow_rate', 0.0)
process.add_duration('feed_duration')

process.add_event('inject_on', 'flow_sheet.tank.flow_rate', Q)
process.add_event('inject_off', 'flow_sheet.tank.flow_rate', 0.0)

process.add_event('eluent_on', 'flow_sheet.eluent.flow_rate', Q)
process.add_event_dependency('eluent_on', ['inject_off'])
process.add_event('eluent_off', 'flow_sheet.eluent.flow_rate', 0.0)
process.add_event_dependency('eluent_off', ['inject_on'])

process.add_event('recycle_on', 'flow_sheet.output_states.column', 0)
process.add_event('recycle_off', 'flow_sheet.output_states.column', 1)

process.add_event_dependency('feed_on', ['inject_off'])
process.add_event_dependency('feed_off', ['feed_on', 'feed_duration'], [1, 1])
process.add_event_dependency(
    'inject_off', ['inject_on', 'feed_duration', 'recycle_off', 'recycle_on'],
    [1, 1, 1, -1]
)

Now, the cycle time is set to \(10~min\) and the feed_duration to \(1~min\) and the recycling times are specified.

process.cycle_time = 600
process.feed_duration.time = 60
process.recycle_on.time = 360
process.recycle_off.time = 420

Simulate Process#

Since the process shows a startup behavior before reaching steady state, multiple cycles need to be simulated. For this purpose, a StationarityEvaluator is used (see Cyclic Stationarity).

if __name__ == '__main__':
    from CADETProcess.simulator import Cadet
    process_simulator = Cadet()
    process_simulator.evaluate_stationarity = True

    simulation_results = process_simulator.simulate(process)
    simulation_results.solution.column.outlet.plot()
    simulation_results.solution_cycles.column.outlet[-1].plot()
../../_images/43667c4b055e05a7bc69fe456fb47c2ce648987be30ff3924a6c0c3c02506fc2.png ../../_images/1f21a91ba610fba0158df0f390afb2d03ba15b374d47b48e452a7724fcc5b54d.png

Optimize Fractionation Times#

To evaluate the process performance, fractionation times can determined to maximize yield and ensure purity constraints.

if __name__ == '__main__':
    from CADETProcess.fractionation import FractionationOptimizer
    fractionation_optimizer = FractionationOptimizer()

    fractionator = fractionation_optimizer.optimize_fractionation(
        simulation_results, purity_required=[0.95, 0.95]
    )

    print(fractionator.performance)
    _ = fractionator.plot_fraction_signal()
Performance(mass=array([0.00049825, 0.00042765]), concentration=array([6.34049584, 2.05717073]), purity=array([0.95      , 0.94941365]), recovery=array([0.83041423, 0.7127434 ]), productivity=array([0.01019788, 0.00875283]), eluent_consumption=array([1.03801779, 0.89092925]) mass_balance_difference=array([1.31944574e-06, 1.61649770e-06]))
../../_images/02555b6789f0d88bf66d1f62d1a84311d6d19946502979fa573bb9d238a6f551.png