Source code for CADETProcess.equilibria.reaction_equilibria

from typing import Optional, Sequence

import numpy as np

from CADETProcess.processModel import MassActionLaw

from . import ptc

__all__ = ["calculate_buffer_equilibrium"]


[docs] def calculate_buffer_equilibrium( buffer: Sequence[float], reaction_system: MassActionLaw, constant_indices: Optional[Sequence[int]] = None, reinit: bool = True, verbose: bool = False, ) -> list[float]: """ Calculate buffer equilibrium for given concentration. Parameters ---------- buffer : list of floats Buffer concentration in mM reaction_system : MassActionLaw Reaction rates and stoichiometric matrix for calculating equilibrium. constant_indices : list, optional Indices of fixed target concentration (e.g. proton concentration/pH). reinit: Bool, optional If True, run CADET with initial values to get 'smooth' initial values verbose : Bool, optional If True, print information at every ptc iteration. Returns ------- sol : list of floats. Buffer equilbrium concentrations """ def residual(c: np.ndarray) -> np.ndarray: return dydx_mal( c, reaction_system, constant_indices, buffer.copy(), ) def jacobian(c: np.ndarray) -> np.ndarray: return jac_mal( c, reaction_system, constant_indices, buffer.copy(), ) stats, sol, res, k = ptc( np.array(buffer.copy()), residual, jacobian, 1e-4, # init step size 1e-14, # tolerance (scaled l2) quiet=not (verbose), maxIter=10000, ) return np.round(np.abs(sol), 14).tolist()
[docs] def dydx_mal( c: np.ndarray, reaction_system: MassActionLaw, constant_indices: Optional[Sequence[int]] = None, c_init: Optional[np.ndarray] = None, ) -> np.ndarray: """ Compute the time derivative of concentrations in a mass action law system. Parameters ---------- c : np.ndarray Concentration vector of components. reaction_system : MassActionLaw Reaction rates and stoichiometric matrix for calculating equilibrium. constant_indices : Optional[Sequence[int]] Indices of components whose concentrations are to be held constant. c_init : Optional[np.ndarray] Initial concentration vector (used to fix constants if constant_indices is provided). Returns ------- dydx : np.ndarray Time derivative of concentration vector. """ cc = np.asarray(c, dtype="float64") if constant_indices is not None: if c_init is None: c_init = c for comp in constant_indices: cc[comp] = c_init[comp] exp_fwd = reaction_system.exponents_fwd exp_bwd = reaction_system.exponents_bwd k_fwd = reaction_system.k_fwd k_bwd = reaction_system.k_bwd r = np.zeros(reaction_system.n_reactions) for r_i in range(reaction_system.n_reactions): fwd_indices = np.where(exp_fwd[:, r_i] > 0.0) prod = np.prod(cc[fwd_indices] ** exp_fwd[:, r_i][fwd_indices]) r_fwd = k_fwd[r_i] * prod bwd_indices = np.where(exp_bwd[:, r_i] > 0.0) prod = np.prod(cc[bwd_indices] ** exp_bwd[:, r_i][bwd_indices]) r_bwd = k_bwd[r_i] * prod r[r_i] = r_fwd - r_bwd dydx = np.dot(reaction_system.stoich, r) if constant_indices is not None: for comp in constant_indices: dydx[comp] = 0 return dydx
[docs] def jac_mal( c: np.ndarray, reaction_system: MassActionLaw, constant_indices: Optional[Sequence[int]] = None, c_init: Optional[np.ndarray] = None, ) -> np.ndarray: """ Compute the Jacobian of a mass action law reaction system at given concentrations. Parameters ---------- c : np.ndarray Current concentrations of components. reaction_system : MassActionLaw Reaction system object containing stoichiometry, exponents, and rate constants. constant_indices : Optional[Sequence[int]] Indices of components to be treated as constant (i.e., their derivatives are zeroed out). c_init : Optional[np.ndarray] Initial concentration vector to reset constants if `constant_indices` is provided. Returns ------- jac : np.ndarray Jacobian matrix (n_comp x n_comp) of the rate equations. """ cc = np.asarray(c, dtype="float64") if constant_indices is not None: if c_init is None: c_init = c for comp in constant_indices: cc[comp] = c_init[comp] exp_fwd = reaction_system.exponents_fwd exp_bwd = reaction_system.exponents_bwd k_fwd = reaction_system.k_fwd k_bwd = reaction_system.k_bwd jac_r = np.zeros((reaction_system.n_reactions, reaction_system.n_comp)) for r_i in range(reaction_system.n_reactions): fwd_indices = np.where(exp_fwd[:, r_i] > 0.0) j_fwd = np.zeros_like(cc) j_fwd[fwd_indices] = k_fwd[r_i] bwd_indices = np.where(exp_bwd[:, r_i] > 0.0) j_bwd = np.zeros_like(cc) j_bwd[bwd_indices] = k_bwd[r_i] for comp in range(len(c)): exponents_fwd = exp_fwd[:, r_i] exponents_bwd = exp_bwd[:, r_i] if exponents_fwd[comp] > 0.0: exp_derivative = exponents_fwd.copy() exp_derivative[comp] -= 1.0 prod = np.prod(cc**exp_derivative) j_fwd[comp] *= exponents_fwd[comp] * prod if exponents_bwd[comp] > 0.0: exp_derivative = exponents_bwd.copy() exp_derivative[comp] -= 1.0 prod = np.prod(cc**exp_derivative) j_bwd[comp] *= exponents_bwd[comp] * prod jac_r[r_i, :] = j_fwd - j_bwd jac = np.dot(reaction_system.stoich, jac_r) if constant_indices is not None: for comp in constant_indices: jac[comp, :] = 0 return jac