"""Kinetics construction."""
from typing import Callable, List, Union
from IPython.display import Math, display
import numpy as np
from numpy.typing import NDArray
from reactord.kinetic.argument import CompositionalArgument
from reactord.mix.abstract_mix import AbstractMix
from reactord.substance.symbolic import Symbolic
from sympy import latex
from .matrix_builder import stoichiometry_matrix_builder
from .reaction_enthalpy import dh_not_specified, dh_specified
[docs]class Kinetic:
"""
Kinetic object builder.
Parameters
----------
mix: AbstractMix
Mixture object.
reactions: dict
Dictionary with kinetic reaction function.
Example:
reactions={"r1": {"eq": a + b > c + d, "rate": r1_rate}}
where "r1" is the reaction name, "eq" is the chemical equation and
r1_rate is defined function of concentration of a and b (in this case)
callable(composition: CompositionalArgument,
temperature: float, constants: dict)
--> rate: float
kinetic_constants: dict
Dictionary with kinetic constants.
There are two keys for each reaction. "a" key is pre-exponential number
and "e" key is activation energy. This dictionary is passed to the
reaction rates functions. Example: kinetic_constants=
{"a": 10, "e": 10000}
rates_argument: str
This argument is used to define how to evaluate the composition
of the reactive mixture inside the reactor.
Options:
'concentration': substance concentration. [mol/m^3]
'partial_pressure': substance partial pressure. [Pa]
"""
def __init__(
self,
mix: AbstractMix,
reactions: dict,
kinetic_constants: dict,
rates_argument: str = "concentration",
) -> None:
# In parameters
self.mix = mix
self.r_argument = rates_argument
self.r_names: List[str] = list(reactions.keys())
self.r_dics: List[dict] = [reactions[name] for name in self.r_names]
self.r_eqs: List[Symbolic] = [rdi.get("eq") for rdi in self.r_dics]
self.r_rates: List[Callable] = [rdi.get("rate") for rdi in self.r_dics]
self._user_r_dhs: List[float] = np.array(
[rdict.get("DH") for rdict in self.r_dics]
)
self.kinetic_constants = kinetic_constants
# Check if all user_dh are specified, else raise error.
if any(self._user_r_dhs) and not all(self._user_r_dhs):
raise NotImplementedError(
"All reactions must have a reaction enthalpy"
" specification or no reaction must have a reaction"
" enthalpy specification."
)
# Argument evaluation functions
if self.r_argument == "concentration":
self._arg_func = self.mix.concentrations
elif self.r_argument == "partial pressure":
self._arg_func = self.mix.partial_pressures
# Build stochiometry matrix from the substances' algebraic expression
self.stoichiometry = stoichiometry_matrix_builder(self.mix, self.r_eqs)
# Kinetic compositional argument object
self.comp_argument = CompositionalArgument(self.mix.names)
@property
def user_r_dh(self):
"""Introduce enthalpy reaction.
Method to user input enthalpy reactions
"""
return self._user_r_dhs
@user_r_dh.setter
def user_r_dh(self, new_dhs: NDArray):
raise ValueError(
"User reaction enthalpies are not mutable, instantiate a new "
"kinetic object."
)
[docs] def evaluate(
self,
mole_fractions: Union[NDArray[np.float64], float],
temperature: Union[NDArray[np.float64], float],
pressure: Union[NDArray[np.float64], float],
) -> NDArray[np.float64]:
"""Evaluate reaction rates.
Parameters
----------
mole_fractions : Union[NDArray[np.float64], float]
moles of each substance
temperature : Union[NDArray[np.float64], float]
Temperature [K]
pressure : Union[NDArray[np.float64], float]
Pressure [Pa]
Returns
-------
NDArray[np.float64]
The rate for each reaction.
"""
self.comp_argument.values = self._arg_func(
mole_fractions, temperature, pressure
)
self.r_rates_profile = np.array(
[
rate(self.comp_argument, temperature, self.kinetic_constants)
for rate in self.r_rates
]
)
return self.r_rates_profile
[docs] def dhs_evaluate(
self,
temperature: Union[NDArray[np.float64], float],
pressure: Union[NDArray[np.float64], float],
):
"""Evaluate reactions' enthalpies.
Parameters
----------
temperature : Union[NDArray[np.float64], float]
Temperature [K]
pressure : Union[NDArray[np.float64], float]
Pressure [Pa]
Returns
-------
NDArray[np.float64]
The enthalpy for each reaction.
"""
return self._enthalpy_func(self, temperature, pressure)
[docs] def set_dh_function(self):
"""Set enthalpy function.
Check if all enthalpy are specified.
"""
# Check if all dh are specified, else raise error.
if all(self._user_r_dhs):
self.std_reaction_enthalpies = np.array([])
self._enthalpy_func = dh_specified
else:
hf = self.mix.get_formation_enthalpies()
self.std_reaction_enthalpies = np.matmul(self.stoichiometry, hf)
self._enthalpy_func = dh_not_specified
@property
def irepr(self):
"""Represent kinetics equations.
Latex format representation of kinetics equation
for use en jupyer notebook
"""
for r_name, eq in zip(self.r_names, self.r_eqs):
ltx = latex(eq._chem_equality).replace("=", r"\rightarrow")
display(Math(f"{r_name}: {ltx}"))
def __len__(self) -> int:
"""Count of reactions.
Returns
-------
int
Number of reactions.
"""
return len(self.r_names)
def __repr__(self) -> str:
"""Represent Mixture's substances and System's reactions.
Returns
-------
str
Latex of reactive system.
"""
output = "Mixture's substances: \n"
for name in self.mix.names:
output = output + f" * {name} \n"
output = output + "\n"
output = output + "System's reactions: \n"
for r_name, eq in zip(self.r_names, self.r_eqs):
ltx = latex(eq._chem_equality).replace("=", r"\rightarrow")
output += f"{r_name}: {ltx} \n"
return output