"""Abstract_mix Module."""
from abc import ABCMeta, abstractmethod
from typing import Callable, List, Union
import numpy as np
from numpy.typing import NDArray
from reactord.mix.viscosity_mixing_rules.grunbergnissan import grunberg_nissan
from reactord.mix.viscosity_mixing_rules.herningzipperer import (
herning_zipperer,
)
from reactord.mix.viscosity_mixing_rules.linearmix import linear
from reactord.substance.substance import Substance
[docs]class AbstractMix(metaclass=ABCMeta):
"""Mixture objects abstract class.
Parameters
----------
metaclass : AbstractMix
Mixture objects interface.
phase_nature : str
Liquid or gas.
substance_list : List[Substance]
List of substances.
viscosity_mixing_rule : str
Viscosity mixing rule method. Options available: "linear",
"grunberg_nissan", "herning_zipperer".
Attributes
----------
names : NDArray[str]
Names of the mixture's substances.
molecular_weights : NDArray[np.float64]
The molecular weights of the mixture's substances. [g/mol]
normal_boiling_points : NDArray[np.float64]
The normal boiling points of the mixture's substances. [K]
normal_melting_points : NDArray[np.float64]
The normal melting points of the mixture's substances. [K]
critical_temperatures : NDArray[np.float64]
The critical temperatures of the mixture's substances. [K].
critical_pressures : NDArray[np.float64]
The critical pressures of the mixture's substances. [Pa]
acentric_factors : NDArray[np.float64]
The acentric factors of the mixture's substances.
formation_enthalpies : NDArray[np.float64]
Standard state molar enthalpies of formation of the mixture's
substances. [J/mol]
formation_enthalpies_ig : NDArray[np.float64]
Ideal-gas molar enthalpies of formation of the mixture's substances.
[J/mol]
"""
substances: List[Substance] = []
phase_nature: str = "gas"
viscosity_mixing_rule: str = "linear"
_viscosity_mixing_rule_function: Callable = linear
def __init__(
self,
substance_list: List[Substance],
phase_nature: str,
viscosity_mixing_rule: str,
) -> None:
self.substances = substance_list
self.phase_nature = phase_nature.lower()
self.viscosity_mixing_rule = viscosity_mixing_rule.lower()
# Check if phase nature is correct
if self.phase_nature not in ["gas", "liquid"]:
raise ValueError(
f"{self.phase_nature} is not a valid phase nature"
)
# Choose the function for viscosity mixing rule.
# TODO: TAL VEZ ESTO DEBERIA SER ALGO INYECTABLE SINO ESTAS LIMITADO AL
# HARDCODEO DEL SOURCE DE AbstractMix
if self.viscosity_mixing_rule == "grunberg_nissan":
self._viscosity_mixing_rule_function = grunberg_nissan
elif self.viscosity_mixing_rule == "herning_zipperer":
self._viscosity_mixing_rule_function = herning_zipperer
elif self.viscosity_mixing_rule == "linear":
self._viscosity_mixing_rule_function = linear
else:
raise ValueError(
f"""{self.viscosity_mixing_rule} is not a valid viscosity
mixing rule."""
)
# =====================================================================
# Put substances attributes in arrays.
# =====================================================================
self.names = np.array([])
self.molecular_weights = np.array([])
self.normal_boiling_points = np.array([])
self.normal_melting_points = np.array([])
self.critical_temperatures = np.array([])
self.critical_pressures = np.array([])
self.acentric_factors = np.array([])
self.formation_enthalpies = np.array([])
self.formation_enthalpies_ig = np.array([])
for substance in self.substances:
self.names = np.append(self.names, substance.name)
self.molecular_weights = np.append(
self.molecular_weights, substance.molecular_weight
)
self.normal_boiling_points = np.append(
self.normal_boiling_points, substance.normal_boiling_point
)
self.normal_melting_points = np.append(
self.normal_melting_points, substance.normal_melting_point
)
self.critical_temperatures = np.append(
self.critical_temperatures, substance.critical_temperature
)
self.critical_pressures = np.append(
self.critical_pressures, substance.critical_pressure
)
self.acentric_factors = np.append(
self.acentric_factors, substance.acentric_factor
)
self.formation_enthalpies = np.append(
self.formation_enthalpies, substance.formation_enthalpy
)
self.formation_enthalpies_ig = np.append(
self.formation_enthalpies_ig, substance.formation_enthalpy_ig
)
# Check for duplicates:
if np.size(np.unique(self.names)) != np.size(self.names):
raise ValueError("All mix's substances must have different names.")
# =========================================================================
# Mixtures' common methods
# =========================================================================
[docs] def mole_fractions(
self, moles: Union[float, NDArray[np.float64]]
) -> Union[float, NDArray[np.float64]]:
r"""Calculate the mole fractions of the mixture's substances.
Multiple mixture compositions can be specified by a moles matrix. Each
column of the matrix represents each mixture and each row represent
each substance's mole fraction.
.. math::
z_i = \frac {n_i} {\sum_{i=0}^{N} n_i}
| :math:`z_i`: mole fraction of the mix's :math:`i`-th substance.
| :math:`n_i`: moles of the mix's :math:`i`-th substance.
| :math:`N`: total number of substances in the mixture.
Parameters
----------
moles: Union[float, NDArray[np.float64]]
Mole fractions of each substance specified in the same order as the
mix's substances order.
Returns
-------
Union[float, NDArray[np.float64]]
The array of the molar fractions of the mix's substances. Each
column of the matrix represents each mixture and each row represent
each substance's mole fraction.
"""
total_moles = np.sum(moles, axis=0)
mole_fractions = np.divide(moles, total_moles)
return mole_fractions
[docs] def mix_molecular_weight(
self, mole_fractions: Union[float, NDArray[np.float64]]
) -> Union[float, NDArray[np.float64]]:
r"""Calculate the molecular weight of the mixture.
Multiple mixture compositions can be specified by a mole_fractions
matrix. Each column of the matrix represents each mixture and each row
represent each substance's mole fraction. In this case, the return
is a 1D array with the molecular weight of each mix.
.. math::
M_{mix} = \sum_{i=0}^{N} M_i z_i
| :math:`M_{mix}`: molecular weight of the mixture.
| :math:`N`: total number of substances in the mixture.
| :math:`M_i`: molecular weight of the mix's :math:`i`-th substance.
| :math:`z_i`: mole fraction of the mix's :math:`i`-th substance.
Parameters
----------
mole_fractions : Union[float, NDArray[np.float64]]
Mole fractions of each substance specified in the same order as the
mix's substances order.
Returns
-------
Union[float, NDArray[np.float64]]
The molecular weight of the mixture. [kg/kmol]
Requires
--------
molecular_weight defined on each mix's Substance.
"""
return np.dot(self.molecular_weights, mole_fractions)
[docs] def molar_density(
self,
mole_fractions: Union[float, NDArray[np.float64]],
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
r"""Calculate the mixture's molar density.
Multiple mixture compositions can be specified by a mole_fractions
matrix. Each column of the matrix represents each mixture and each row
represent each substance's mole fraction. Also with temperature and
pressure vectors following de NumPy broadcasting rules.
.. math::
\rho_{mix} = \frac {1} {v_{(\vec{z}, T, P)}}
| :math:`\rho_{mix}`: mix's molar density.
| :math:`v`: mix's molar volume.
| :math:`T`: temperature.
| :math:`P`: pressure.
| :math:`\vec{z}`: substances' mole fractions.
Parameters
----------
mole_fractions : Union[float, NDArray[np.float64]]
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
Mixture's molar density. [mol/m3]
"""
molar_volume = self.volume(mole_fractions, temperature, pressure)
return np.divide(1, molar_volume)
[docs] def mass_density(
self,
mole_fractions: Union[float, NDArray[np.float64]],
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
r"""Calculate the mixture's mass density.
Multiple mixture compositions can be specified by a mole_fractions
matrix. Each column of the matrix represents each mixture and each row
represent each substance's mole fraction. Also with temperature and
pressure vectors following de NumPy broadcasting rules.
.. math::
\rho_{mix}^{mass} = \rho_{mix}^{mol} M_{mix}
| :math:`\rho_{mix}^{mass}`: mix's masic density.
| :math:`\rho_{mix}^{mol}`: mix's molar density.
| :math:`M_{mix}`: molecular weight of the mixture.
Parameters
----------
mole_fractions : Union[float, NDArray[np.float64]]
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
Mixture's mass density. [kg/m3]
Requires
--------
molecular_weight defined on each mix's Substance.
"""
mass_density = (
self.molar_density(mole_fractions, temperature, pressure)
* self.mix_molecular_weight(mole_fractions)
/ 1000
)
return mass_density
[docs] def mix_viscosity(
self,
mole_fractions: NDArray,
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
"""Evaluate the viscosity of the mixture with the chosen mixing rule.
Multiple mixture compositions can be specified by a moles matrix. Each
column of the matrix represents each mixture and each row represents
each substance's mole fractions. Also with temperature and pressure
vectors following de NumPy broadcasting rules.
Parameters
----------
mole_fractions : NDArray
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
Mixture's viscosity. [Pa s]
"""
mixture_viscosities = self._viscosity_mixing_rule_function(
self, mole_fractions, temperature, pressure
)
return mixture_viscosities
# Compositional measure functions
[docs] def partial_pressures(
self,
mole_fractions: NDArray,
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
"""Calculate the partial pressures of the mixture's substances.
Multiple mixture compositions can be specified by a mole_fractions
matrix. Each column of the matrix represents each mixture and each row
represent each substance's mole fraction. Also with temperature and
pressure vectors following de NumPy broadcasting rules.
.. math::
P_i = P z_i
| :math:`P_i`: partial pressure of the :math:`i`-th mix's substance.
| :math:`P`: total system's pressure.
| :math:`z_i`: mole fraction of the mix's :math:`i`-th substance.
Parameters
----------
mole_fractions : NDArray
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
An array that contains the partial pressures of mix's substances.
Each column of the matrix represents each mixture and each row
represent each substance's partial pressure. [Pa]
"""
partial_pressures = np.multiply(mole_fractions, pressure)
return partial_pressures
[docs] def concentrations(
self,
mole_fractions: NDArray,
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
r"""Concentrations of the mixture's substances.
Multiple mixture compositions can be specified by a moles matrix. Each
column of the matrix represents each mixture and each row represents
each substance's mole fractions. Also with temperature and pressure
vectors following de NumPy broadcasting rules.
.. math::
C_i = z_i \rho_{mix}
| :math:`C_i`: concentration of the mix's :math:`i`-th substance.
| :math:`z_i`: mole fraction of the mix's :math:`i`-th substance.
| :math:`\rho_{mix}`: mix's molar density.
Parameters
----------
mole_fractions : NDArray
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
An array that contains the concentrations of the mixture's
substances. [mol/m³]
"""
molar_densities = self.molar_density(
mole_fractions, temperature, pressure
)
return np.multiply(molar_densities, mole_fractions)
def __len__(self) -> int:
"""Return the number of substances in the mixture.
Redifine the magic method __len__
Returns
-------
int
The number of substances in the mixture.
"""
return len(self.substances)
# =========================================================================
# Mixtures specifics methods
# =========================================================================
[docs] @abstractmethod
def volume(
self,
mole_fractions: NDArray,
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
"""Return the molar volume of the mixture.
Multiple mixture compositions can be specified by a moles matrix. Each
column of the matrix represents each mixture and each row represents
each substance's mole fractions. Also with temperature and pressure
vectors following de NumPy broadcasting rules.
Parameters
----------
mole_fractions : NDArray
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
Mixture's molar volume. [m³/mol]
Raises
------
NotImplementedError
Abstract method not implemented.
"""
raise NotImplementedError("Abstract method not implemented")
[docs] @abstractmethod
def mix_heat_capacity(
self,
mole_fractions: NDArray,
temperature: Union[float, NDArray[np.float64]],
pressure: Union[float, NDArray[np.float64]],
) -> Union[float, NDArray[np.float64]]:
"""Calculate the mixture's heat capacity.
Multiple mixture compositions can be specified by a moles matrix. Each
column of the matrix represents each mixture and each row represents
each substance's mole fractions. Also with temperature and pressure
vectors following de NumPy broadcasting rules.
Parameters
----------
mole_fractions : NDArray
Mole fractions of each substance specified in the same order as the
mix's substances order.
temperature: Union[float, NDArray[np.float64]]
Temperature. [K]
pressure: Union[float, NDArray[np.float64]]
Pressure. [Pa]
Returns
-------
Union[float, NDArray[np.float64]]
Mixture's heat capacity. [J/mol]
Raises
------
NotImplementedError
Abstract method not implemented.
"""
raise NotImplementedError("Abstract method not implemented")