Dealing with multiple feeds#

In some cases the fed-batch process uses multiple feeding mediums. This is typically the case in cultivation of mammalians cells. This tutorial will show how to use the pseudo batch transformation to handle multiple feeding mediums. We will, again, use simulated data to showcase the workflow. This fed-batch process seeks to mimic a cultivation of mammalians cells. These simulated cells require two substrates to grow; glucose and glutamine. The bioreactor is fed with two different feed mediums, one containing concentrated glucose and one complex medium that contain glucose, and glutamine. Furthermore, the feeding is not done through a “continuous” exponential feed, but instead as pulse feed, i.e. at certain time points a given volume of each feed is added to the bioreactor.

In the simulation 12 samples are drawn over the course of the fermentation and 0.1 hours after each sample feed is added.

First we will setup the environment and load the data.

[1]:
import matplotlib.pyplot as plt
import numpy as np

from pseudobatch import pseudobatch_transform_pandas
from pseudobatch.datasets import load_cho_cell_like_fedbatch
from pseudobatch.datasets._dataloaders import _prepare_simulated_dataset
/Users/s143838/.virtualenvs/pseudobatch-dev/lib/python3.10/site-packages/tqdm/auto.py:21: TqdmWarning: IProgress not found. Please update jupyter and ipywidgets. See https://ipywidgets.readthedocs.io/en/stable/user_install.html
  from .autonotebook import tqdm as notebook_tqdm
{'stan_version_major': '2', 'stan_version_minor': '29', 'stan_version_patch': '2', 'STAN_THREADS': 'false', 'STAN_MPI': 'false', 'STAN_OPENCL': 'false', 'STAN_NO_RANGE_CHECKS': 'false', 'STAN_CPP_OPTIMS': 'false'}
[2]:
fedbatch_df_measurement = load_cho_cell_like_fedbatch(sampling_points_only=True)

Inspect the dataset#

First, let’s quickly inspect the dataframe

[3]:
(fedbatch_df_measurement
    .filter(['timestamp', 'sample_volume', 'c_Biomass', 'c_Glucose', 'c_Product', 'v_Volume', 'v_Feed_accum1', 'v_Feed_accum2'])
    .head()
)
[3]:
timestamp sample_volume c_Biomass c_Glucose c_Product v_Volume v_Feed_accum1 v_Feed_accum2
0 10.0 170.0 1.644817 47.882089 0.940479 1000.0 0.0 0.0
1 16.0 170.0 3.007637 50.337209 2.106177 935.0 100.0 5.0
2 22.0 170.0 5.466735 50.535418 4.170358 870.0 200.0 10.0
3 28.0 170.0 9.758328 47.107608 7.737765 805.0 300.0 15.0
4 34.0 170.0 16.782229 38.381155 13.547532 740.0 400.0 20.0

This dataframe is structured very similar to the one used in the previous tutorial. This time the fed-batch process utilized two feed streams and therefore there are two accumulated feed columns. It is important to not the even though the feed is added in pulses the pseudo batch still takes accumulated amounts.

In this simulation feed 1 contained only glucose, while feed 2 contain both glucose and Glutamine. We will store the concentrations in some variables here.

[4]:
glucose_in_feed1 = fedbatch_df_measurement.c_Glucose_feed1.iloc[0]
glucose_in_feed2 = 0
Glutamine_in_feed1 = fedbatch_df_measurement.c_Glutamine_feed1.iloc[0]
Glutamine_in_feed2 = fedbatch_df_measurement.c_Glutamine_feed2.iloc[0]

Applying the pseudo batch transformation#

We can now apply the pseudo batch transformation to the data. Notice that to include both feeds we simply supply a list of the columns names in the accumulated_feed_colnames argument. The concentrations_in_feed is specified as follows: The outer list iterates over the measured concentrations (in this case Biomass, Glucose, Product, CO2, Glutamine), while the inner list iterates over the feeding mediums. Important: the order HAS to be the same for concetration_in_feed and both measured_concentration_colnames and accumulated_feed_colname!

[5]:
fedbatch_df_measurement[["c_Biomass_pseudo", "c_Glucose_pseudo", "c_Product_pseudo", "c_CO2_pseudo", "c_Glutamine_pseudo"]] = pseudobatch_transform_pandas(
    df=fedbatch_df_measurement,
    measured_concentration_colnames=['c_Biomass', 'c_Glucose', 'c_Product', 'c_CO2', 'c_Glutamine'],
    reactor_volume_colname='v_Volume',
    accumulated_feed_colname=['v_Feed_accum1', 'v_Feed_accum2'],
    concentration_in_feed=[[0, 0], [glucose_in_feed1, glucose_in_feed2], [0, 0], [0, 0], [Glutamine_in_feed1, Glutamine_in_feed2]],
    sample_volume_colname='sample_volume'
)

We can take a quick look at what the pseudo batch transformed data looks like by plotting it against time.

[6]:
plot_cols = ['c_Biomass_pseudo', 'c_Glucose_pseudo','v_Feed_accum1','c_Product_pseudo',  'c_Glutamine_pseudo', 'v_Feed_accum2']
fig, axes = plt.subplots(nrows=2, ncols=len(plot_cols)//2, figsize=(10, 8))
for ax, col in zip(axes.flatten(), plot_cols):
    ax.scatter(fedbatch_df_measurement.timestamp, fedbatch_df_measurement[col])
    ax.set_title(col)

    if col.startswith('v_'):
        ax.set_ylabel('Volume')

fig.supxlabel('Feeding time [h]')
fig.supylabel('Pseudo batch concentration')
fig.tight_layout()
../_images/Tutorials_4_-_Dealing_with_multiple_feeds_13_0.png

Estimate substrate yields#

We can now estimate the yield coefficient for the two substrates.

[7]:
Yxglucose, _ = np.polyfit(fedbatch_df_measurement.c_Biomass_pseudo.to_numpy(), fedbatch_df_measurement.c_Glucose_pseudo.to_numpy(), 1)
Yxglutamine, _ = np.polyfit(fedbatch_df_measurement.c_Biomass_pseudo.to_numpy(), fedbatch_df_measurement.c_Glutamine_pseudo.to_numpy(), 1)

print("Estimated Yxglucose: ", np.abs(Yxglucose))
print("True Yxglucose: ", fedbatch_df_measurement.Yxs.iloc[0])
print("Estimated Yxglutamine: ", np.abs(Yxglutamine))
print("True Yxglutamine: ", fedbatch_df_measurement.Yxs2.iloc[0])
Estimated Yxglucose:  1.8500000000000019
True Yxglucose:  1.85
Estimated Yxglutamine:  0.02000000000000001
True Yxglutamine:  0.02

As you see the yields are correctly estimated also when multiple feed mediums are used. Please refer to the other tutorials for guides on estimation of rates or other parameters.