Compartment Modeling#

In the design of bioreactors and separation vessels, computational fluid dynamics models (CFD) are often used to describe phenomena such as transport mechanisms and mixing behavior. However, CFD simulations can be computationally very expensive, especially when the goal is to also include kinetic models, e.g. to describe (enzymatic) reactions or biomass growth.

To reduce the complexity of the bioreactor models, the reactor volume can be divided into a number of interconnected sub-volumes called compartments. Each compartment represents a zone which can be approximated with a simplified hydrodynamic model such as a continuous stirred-tank reactor (CSTR) or an ideal plug flow reactor (PFR) [10].

Since CADET is capable of simulating networks of unit operations, it is possible to simulate compartment models. To facilitate the setup of these models, a CompartmentBuilder was implemented in CADET-Process. Note that currently only perfectly mixed compartments are implemented using the built-in Cstr model. However, other compartments can easily be added in the future.

Generally, the following data are required:

  • Compartment volumes

  • Compartment connectivity (including flow rates)

  • Initial conditions

Features:

  • Tracer injection experiments

  • Internal recycle and flow-through mode

  • Possibility to define kinetic reaction models

Connectivity#

First, the definition of the compartment model and the connectivity is introduced.

Example 1#

Consider a simple example with five compartments.

../../_images/compartment_simple.svg

Simple compartment model.#

Before configuring the CompartmentBuilder, a ComponentSystem needs to be defined. It serves to ensure consistent parameter entries for the different modules of CADET-Process. For more information see Component System.

from CADETProcess.processModel import ComponentSystem
component_system = ComponentSystem(2)

Then, the ComparmentBuilder is imported and configured. For this purpose, the volume of each compartment needs to be provided, as well as the flow rates between all compartments. The number of compartments is inferred from the length of the vector of volumes passed in the __init__ method. Note that the values for the example are arbitrary.

The flow rate matrix is structured such that each row contains the outgoing streams of the compartment with the row index. E.g.row \(0\) contains all outgoing streams of compartment \(0\).

To instantiate the object, the ComponentSystem is passed the CompartmentBuilder as as well as the vector of compartment volumes and the flow rates.

from CADETProcess.modelBuilder import CompartmentBuilder

volume = [1e-3, 2e-3, 3e-3, 4e-3, 5e-3]
flow_rate_matrix = [
    0,      0.1e-4, 0.2e-4, 0.3e-4, 0.4e-4,
    0.1e-4, 0,      0,      0,      0,
    0.2e-4, 0,      0,      0,      0,
    0.3e-4, 0,      0,      0,      0,
    0.4e-4, 0,      0,      0,      0,
]

builder_simple = CompartmentBuilder(
    component_system,
    volume, flow_rate_matrix,
)

Example 2#

In this example, consider a constant flow through the system.

../../_images/compartment_complex.png

Bioreactor compartment model from Tobo et al. [11].#

To model this system, the Inlet and Outlet are treated as additional pseudo-compartments. For this purpose, it is possible to add the stings inlet and outlet in the vector of volumes.

Although in this example, the overall content volume of the bioreactor does not change, it is generally possible to consider a gradual increase or decrease of volume of each compartment over time. However, it is important that none of the compartment volumes must never become zero during simulation since this cannot be handled by CADET.

volume = ['inlet', 2, 1, 3, 1, 2, 1, 4, 1, 'outlet']

flow_rate_matrix = [
#   0    1    2    3    4    5    6    7    8    9
    0,   0.1, 0,   0,   0,   0,   0,   0,   0,   0,   # 0
    0,   0,   0.3, 0,   0,   0,   0,   0.1, 0,   0,   # 1
    0,   0.1, 0,   0.1, 0,   0,   0,   0.1, 0,   0,   # 2
    0,   0.2, 0,   0,   0,   0.5, 0,   0,   0.1, 0,   # 3
    0,   0,   0,   0.1, 0,   0.1, 0,   0.1, 0,   0,   # 4
    0,   0,   0,   0,   0.3, 0,   0.2, 0.1, 0,   0,   # 5
    0,   0,   0,   0,   0,   0,   0,   0.1, 0,   0.1, # 6
    0,   0,   0,   0.5, 0,   0,   0,   0,   0,   0,   # 7
    0,   0,   0,   0.1, 0,   0,   0,   0,   0,   0,   # 8
    0,   0,   0,   0,   0,   0,   0,   0,   0,   0,   # 9
]

builder_complex = CompartmentBuilder(
    component_system,
    volume, flow_rate_matrix,
)

Initial conditions#

By default, the concentration of every compartment is initially set to \(0~mM\). To override this, there are multiple options. First, the same concentration can be used for all components and all compartments:

builder_simple.init_c = 1

Alternatively, a value for each concentration can be provided which is then set to all compartments:

builder_simple.init_c = [0,1]

Finally, a vector of concentrations for each compartment can be provided as a numpy array (assuming example 1):

import numpy as np

builder_simple.init_c = np.array([
    [0,0],
    [1,1],
    [2,2],
    [3,3],
    [4,4]
])

Tracers#

Often, small amounts of tracer are added to a specific location in the system to measure the mixing behavior of the vessel. To add a tracer injection to the model, the following information needs to be specified:

  • the compartment to which the tracer is added,

  • the tracer concentration in the feed,

  • the flow rate,

  • the injection duration,

  • the start time of the injection (optional, default is \(0\)),

  • Whether the volume of the compartment should be kept constant (optional, default is True).

Here, a \(10~mM\) tracer is added to compartment \(4\) for \(10~s\) with a flow rate of \(0.1~L \cdot s^{-1}\) units.

builder_simple.add_tracer(4, [10, 10], 10, 0.1e-3, t_start=0)
\[\require{mhchem}\]

Kinetic reaction models#

It is possible to combine the CompartmentBuilder with any of the adsorption and reaction models included in CADET-Process. The models are configured as usual and are then added to the builder instance.

For example, consider this simple reaction:

\[ \ce{A <=>[2][1] B} \]

To configure reactions, import and configure the {class}~CADETProcess.processModel.MassActionLaw. For more information, see Chemical Reactions.

from CADETProcess.processModel import MassActionLaw

reaction_model = MassActionLaw(component_system)
reaction_model.add_reaction(
    [0, 1], [-1, 1], k_fwd=2, k_bwd=1
)

builder_simple.bulk_reaction_model = reaction_model

Simulation#

To simulate the system, the Process object of the CompartmentBuilder can be accessed directly. Before passing it to the process simulator, the cycle time needs to be set.

from CADETProcess.simulator import Cadet
process_simulator = Cadet()

process = builder_simple.process
process.cycle_time = 100

simulation_results = process_simulator.run(process)

Visualization#

Since a regular SimulationResults object is returned, it is possible to access the solution objects of each individual compartment and use their default plot() method. For example, here the concentration of the first and fourth compartment is plotted:

_ = simulation_results.solution[f'compartment_1'].outlet.plot()
_ = simulation_results.solution[f'compartment_4'].outlet.plot()
../../_images/67c54a039c34bc5daa5412e00333a554f9b9ec0c57ba4000ae8470f89d1f2f27.png ../../_images/4dc2bf18e7f985a88968b94c4d80dd3fdf7dd6c73830fb7bdc61b5e4805c35ac.png

The effects of the tracer, as well as the reaction are clearly visible.