Source code for CADETProcess.tools.yamamoto

from dataclasses import dataclass

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit

from CADETProcess import CADETProcessError
from CADETProcess.processModel import TubularReactorBase, StericMassAction


__all__ = ['GradientExperiment', 'plot_experiments', 'YamamotoResults', 'fit_parameters']


[docs] class GradientExperiment(): def __init__( self, time, c_salt, c_protein, gradient_volume, c_salt_start=None, c_salt_end=None): self.time = np.array(time) self.c_salt = np.array(c_salt) c_protein = np.array(c_protein) if len(c_protein.shape) == 1: c_protein = np.expand_dims(c_protein, axis=1) self.c_protein = c_protein self.gradient_volume = gradient_volume if c_salt_start is None: c_salt_start = np.min(c_salt) self.c_salt_start = c_salt_start if c_salt_end is None: c_salt_end = np.max(c_salt) self.c_salt_end = c_salt_end @property def n_proteins(self): return self.c_protein.shape[1] @property def c_salt_at_max(self): """np.array: Salt concentration at protein peak maximum in mM.""" max_c_protein = np.argmax(self.c_protein, axis=0) return self.c_salt[max_c_protein] @property def t_at_max(self): max_c_protein = np.argmax(self.c_protein, axis=0) return self.time[max_c_protein]
[docs] def calculate_normalized_gradient_slope(self, column_volume, total_porosity): """Calculate normalized concentration gradient slope. Parameters ---------- column_volume : float Void volume of column in m³. total_porosity : float Total porosity of column. Returns ------- normalized_slope : float Gradient slope in mM. """ slope = (self.c_salt_end - self.c_salt_start) / self.gradient_volume vol_factor = column_volume - total_porosity*column_volume return slope * vol_factor
[docs] def plot(self, fig=None, ax=None, sec_ax=None): if ax is None: fig, ax = plt.subplots() ax.set_xlabel('$Time / s$') ax.set_ylabel('$c_{Protein} / mM$') sec_ax = ax.twinx() sec_ax.set_ylabel('$c_{Salt} / mM$') ax.plot(self.time, self.c_protein, label='Protein') c_p_max = np.max(self.c_protein, axis=0) ax.vlines(self.t_at_max, ymin=0, ymax=c_p_max, color='k', linestyle='--') sec_ax.plot(self.time, self.c_salt, 'k', label='Salt') sec_ax.plot(self.t_at_max, self.c_salt_at_max, 'ro') fig.tight_layout() return fig, ax, sec_ax
[docs] def plot_experiments(experiments): fig = ax = sec_ax = None for exp in experiments: fig, ax, sec_ax = exp.plot(fig, ax, sec_ax)
[docs] def yamamoto_equation(log_c_salt_at_max_M, lambda_, nu, k_eq): """ Parameters ---------- log_c_salt_at_max_M : float log10 of salt concentrations in M at protein peak maximum. lambda_ : float Resin capacity in mM. nu : float Characteristic charge. k_eq : float, optional Equilibrium constant. Returns ------- TYPE DESCRIPTION. """ lambda_M = lambda_/1000 return np.multiply((nu + 1), log_c_salt_at_max_M) - np.log10(k_eq * lambda_M**nu * (nu + 1))
[docs] class YamamotoResults: """Parameter values determined using Yamamoto's method.""" def __init__(self, column, experiments, log_gradient_slope, log_c_salt_at_max_M): self.column = column self.experiments = experiments self.log_gradient_slope = log_gradient_slope self.log_c_salt_at_max_M = log_c_salt_at_max_M @property def characteristic_charge(self): return self.column.binding_model.characteristic_charge[1:] @property def k_eq(self): return self.column.binding_model.adsorption_rate[1:]
[docs] def plot(self, fig=None, ax=None): if ax is None: fig, ax = plt.subplots() ax.set_ylabel('Normalized Gradient Slope $GH$ / $M$') ax.set_xlabel('Peak Salt Concentration $I_R$ / $M$') n_proteins = self.experiments[0].n_proteins for i_p in range(n_proteins): k_eq = self.k_eq[i_p] nu = self.characteristic_charge[i_p] x = [ min(self.log_c_salt_at_max_M[:, i_p])*1.05, max(self.log_c_salt_at_max_M[:, i_p])*0.95 ] y = yamamoto_equation( log_c_salt_at_max_M=x, lambda_=self.column.binding_model.capacity, nu=nu, k_eq=k_eq, ) ax.plot(x, y, 'k') ax.plot(self.log_c_salt_at_max_M, self.log_gradient_slope, 'ro') fig.tight_layout() return fig, ax
[docs] def fit_parameters(experiments, column): """Fit parameters using Yamamoto's method. Parameters ---------- experiments : list List of GradientExperiments. column : Column Column object with binding model Returns ------- yamamoto_results : YamamotoResults Parameter values. """ if not isinstance(column, TubularReactorBase): raise TypeError("Expected Column Model.") if not isinstance(column.binding_model, StericMassAction): raise TypeError("Yamamoto's method only works for Steric Mass Action model.") n_proteins = column.n_comp - 1 for exp in experiments: if exp.n_proteins != n_proteins: raise CADETProcessError("Experiments must have same number of proteins.") normalized_gradient_slope = [ exp.calculate_normalized_gradient_slope(column.volume, column.total_porosity) for exp in experiments ] log_gradient_slope = np.log10(np.divide(normalized_gradient_slope, 1000)) nu = np.zeros((experiments[0].n_proteins,)) k_eq = np.zeros((experiments[0].n_proteins,)) log_c_salt_at_max_M = np.zeros((len(experiments), experiments[0].n_proteins,)) for i_p in range(experiments[0].n_proteins): c_salt_at_max = [exp.c_salt_at_max[i_p] for exp in experiments] log_c_salt_at_max_M[:, i_p] = np.log10(np.array(c_salt_at_max)/1000) nu[i_p], k_eq[i_p] = _fit_yamamoto( log_c_salt_at_max_M[:, i_p], log_gradient_slope, column.binding_model.capacity ) column.binding_model.characteristic_charge = [0, *nu.tolist()] column.binding_model.adsorption_rate = [0, *k_eq.tolist()] yamamoto_results = YamamotoResults( column, experiments, log_gradient_slope, log_c_salt_at_max_M ) return yamamoto_results
def _fit_yamamoto(log_c_salt_at_max_M, log_gradient_slope, lambda_): """ Parameters ---------- log_c_salt_at_max_M : list log10 of salt concentrations at protein peak maximum in mM. log_gradient_slope : TYPE DESCRIPTION. lambda_ : TYPE Resin capacity in mM. Returns ------- nu : float Characteristic charge. k_eq : TYPE, optional Equilibrium constant. """ bounds = ((0, 1e-10), (1000, 1000)) def yamamoto_wrapper(c_s, nu, k_eq): return yamamoto_equation(c_s, lambda_, nu, k_eq) results, pcov = curve_fit( yamamoto_wrapper, log_c_salt_at_max_M, log_gradient_slope, bounds=bounds, p0=(1, 1) ) return results