Introduction

Residence time distribution (RTD)

Let’s simulate a protein pulse response measurement on a small flow-through column:

import numpy as np
from bokeh.plotting import figure, show
from bio_rtd import peak_shapes, utils

# Time vector.
t = np.linspace(0, 10, 201)

# Generate noisy data.
y = peak_shapes.emg(t, 2, 0.3, 1.0)  # clean signal
y_noisy = y + (np.random.random(y.shape) - 0.5) * y.max() / 10

# Determine peak start, end and max position.
i_start, i_end = utils.vectors.true_start_and_end(y > 0.1 * y.max())
i_max = y.argmax()

# Plot.
p = figure(plot_width=690, plot_height=350, title="Measurements",
           x_axis_label="t [min]", y_axis_label="c [mg/mL]")
p.line(t, y_noisy, line_width=2, color='green', alpha=0.6,
       legend_label='c [mg/mL]')
for i in [i_max, i_start, i_end]:  # plot circles
    p.circle(t[i], y[i], size=15, fill_alpha=0,
             line_color="blue", line_width=2)
show(p)

Let’s fit an exponentially modified gaussian distribution to the peak using the reference points in blue circles.

Probability distribution classes, such as bio_rtd.pdf.ExpModGaussianFixedDispersion allow the probability distribution functions (pdf) to be dependent on process parameters and inlet flow rate.

import numpy as np
from bokeh.plotting import figure, show
from bio_rtd import pdf, uo, peak_shapes, peak_fitting

t = np.linspace(0, 10, 201)

# Calc rt_mean, sigma and skew from peak points.
rt, sigma, skew = peak_fitting.calc_emg_parameters_from_peak_shape(
    t_peak_max=1.4, t_peak_start=0.6, t_peak_end=3.9,
    relative_threshold=0.1
)

# Define pdf.
pdf_emg = pdf.ExpModGaussianFixedDispersion(t, sigma ** 2 / rt, skew)
pdf_emg.update_pdf(rt_mean=rt)
p_emg = pdf_emg.get_p()

# Generate noisy data.
y = peak_shapes.emg(t, 2, 0.3, 1.0)  # clean signal
y_noisy = y + (np.random.random(y.shape) - 0.5) * y.max() / 10

# Plot.
p = figure(plot_width=690, plot_height=350,
           title="Probability Distribution",
           x_axis_label="t [min]", y_axis_label="c [mg/mL]")
p.line(t, y_noisy, line_width=2, color='green', alpha=0.6,
       legend_label='c [mg/mL] (data)')
p.line(t, p_emg, line_width=2, color='black', alpha=1,
       legend_label='p (pdf)')
show(p)

Let’s expand the example by introducing flow-through unit operation which uses pdf:

import numpy as np
from bokeh.plotting import figure, show
from bio_rtd import pdf, uo, peak_shapes, peak_fitting

t = np.linspace(0, 10, 201)

# Concentration with spike at t = 0
c_in = np.zeros([1, t.size])
c_in[0][0] = 1 / t[1]

# Flow rate
f = np.ones_like(t) * 3.5

# Calc rt_mean, sigma and skew from peak points
rt, sigma, skew = peak_fitting.calc_emg_parameters_from_peak_shape(
    t_peak_max=1.4, t_peak_start=0.6, t_peak_end=3.9,
    relative_threshold=0.1
)

# Define unit operation
ft_uo = uo.fc_uo.FlowThrough(
    t=t, uo_id="ft_example",
    pdf=pdf.ExpModGaussianFixedDispersion(t, sigma ** 2 / rt, skew),
)
ft_uo.v_void = rt * f[0]  # set void volume as rt * flow rate

# Simulate.
f_out, c_out = ft_uo.evaluate(f, c_in)

# Define noisy data.
y = peak_shapes.emg(t, 2, 0.3, 1.0)  # clean signal
y_noisy = y + (np.random.random(y.shape) - 0.5) * y.max() / 10

# Plot.
p = figure(plot_width=690, plot_height=350,
           title="Unit Operation - Pulse Response",
           x_axis_label="t [min]", y_axis_label="c [mg/mL]")
p.line(t, y_noisy, line_width=2, color='green', alpha=0.6,
       legend_label='c [mg/mL] (data)')
p.line(t, c_out[0], line_width=2, color='black', alpha=1,
       legend_label='c [mg/mL] (model)')
show(p)

Simulating breakthrough profile with the same unit operation:

import numpy as np
from bokeh.plotting import figure, show
from bio_rtd import pdf, uo

# Define inlet profiles.
t = np.linspace(0, 10, 201)  # time
c_in = np.ones([1, t.size])  # concentration (constant)
f = np.ones_like(t) * 3.5  # flow rate

# Define unit operation.
ft_uo = uo.fc_uo.FlowThrough(
    t=t, uo_id="ft_example",
    pdf=pdf.ExpModGaussianFixedDispersion(t, 0.3 ** 2 / 2, 1.0))
ft_uo.v_void = 2 * f[0]  # set void volume (rt * flow rate)

# Simulation.
f_out, c_out = ft_uo.evaluate(f, c_in)

# Plot.
p = figure(plot_width=690, plot_height=350,
           title="Unit Operation - Breakthrough",
           x_axis_label="t [min]", y_axis_label="c [mg/mL]")
p.line(t, c_out[0], line_width=2, color='black',
       legend_label='c [mg/mL]')
show(p)

Simulating breakthrough profile with bio_rtd.core.RtdModel with inlet and two unit operations.

import numpy as np
from bokeh.plotting import figure, show
from bio_rtd import pdf, uo
from bio_rtd.core import RtdModel
from bio_rtd.inlet import ConstantInlet

t = np.linspace(0, 10, 201)  # time

# Define inlet
inlet = ConstantInlet(t, inlet_id="sample_inlet",
                      f=3.5, c=np.array([1.0]),
                      species_list=['protein [mg/mL]'])

# Define unit operation.
ft_uo_1 = uo.fc_uo.FlowThrough(
    t=t, uo_id="ft_example",
    pdf=pdf.ExpModGaussianFixedDispersion(t, 0.3 ** 2 / 2, 1.0))
ft_uo_1.rt_target = 2.0

# Define another unit operation.
ft_uo_2 = uo.fc_uo.FlowThrough(t=t, uo_id="ft_example_2",
                               pdf=pdf.TanksInSeries(t, 3))
ft_uo_2.rt_target = 1.2

# Create an RtdModel.
rtd_model = RtdModel(inlet, dsp_uo_chain=[ft_uo_1, ft_uo_2])

# Run simulation.
rtd_model.recalculate()

# Plot.
p = figure(plot_width=690, plot_height=350,
           title="Model with 2 unit operations - Breakthrough",
           x_axis_label="t [min]", y_axis_label="c [mg/mL]")
p.line(t, inlet.get_result()[1][0], line_width=2, color='black',
       legend_label='inlet')
p.line(t, ft_uo_1.get_result()[1][0], line_width=2, color='green',
       legend_label='outlet of uo_1')
p.line(t, ft_uo_2.get_result()[1][0], line_width=2, color='blue',
       legend_label='outlet of uo_2')
p.legend.location = "bottom_right"
show(p)

See Examples section for more examples.

See Templates section on how to set up specific unit operations.

See Features section for brief overview of individual unit operations.

See API Reference for detailed info about parameters, attributes and methods for each unit operation.

Creating custom rtd model

We recommend checking Models and making a local copy of one that most closely resembles you needs and modify it accordingly. To define new instances of unit operations, use the parameter and attribute list from Templates.

Also check Coding section in order to better understand the coding style in bio_rtd.