"""Module calculating the isosteric enthalpy for isotherms at different temperatures."""
import numpy
from scipy import constants
from scipy import stats
from pygaps.core.modelisotherm import ModelIsotherm
from pygaps.core.pointisotherm import PointIsotherm
from pygaps.utilities.exceptions import ParameterError
[docs]def isosteric_enthalpy(
isotherms: "list[PointIsotherm | ModelIsotherm]",
loading_points: list = None,
branch: str = 'ads',
verbose: bool = False,
):
r"""
Calculate the isosteric enthalpy of adsorption (in kJ/mol) by applying
Clausius-Clapeyron equation to several isotherms recorded at different
temperatures on the same material.
Parameters
----------
isotherms : list[PointIsotherms | ModelIsotherm]
The isotherms to use in calculation of the isosteric enthalpy. They should all
be measured on the same material.
loading_points : list[float], optional
The loading points at which the isosteric enthalpy should be calculated.
Default will be 50 equally spaced points in the available range.
The points must be within the range of loading of all passed isotherms, or
else the calculation cannot complete.
branch : str
The branch of the isotherms to take, defaults to adsorption branch.
verbose : bool
Whether to print out extra information and generate a graph.
Returns
-------
result_dict : dict
A dictionary with the isosteric enthalpies per loading, with the form:
- ``isosteric_enthalpy`` (array) : the isosteric enthalpy of adsorption in kJ/mol
- ``loading`` (array) : the loading for each point of the isosteric enthalpy, in mmol
- ``slopes`` (array) : the exact log(p) vs 1/T slope for each point
- ``correlation`` (array) : correlation coefficient for each point
- ``std_errors`` (array) : estimated standard errors for each point
Raises
------
ParameterError
When something is wrong with the function parameters.
Notes
-----
*Description*
The isosteric enthalpies are calculated from experimental data using the Clausius-Clapeyron
equation as the starting point:
.. math::
\Big( \frac{\partial \ln P}{\partial T} \Big)_{n_a} = -\frac{\Delta H_{ads}}{R T^2}
Where :math:`\Delta H_{ads}` is the enthalpy of adsorption. In order to approximate the
partial differential, two or more isotherms are measured at different temperatures. The
assumption made is that the enthalpy of adsorption does not vary in the temperature range
chosen. Therefore, the isosteric enthalpy of adsorption can be calculated by using the pressures
at which the loading is identical using the following equation for each point:
.. math::
\Delta H_{ads} = - R \frac{\partial \ln P}{\partial 1 / T}
and plotting the values of :math:`\ln P` against :math:`1 / T` we should obtain a straight
line with a slope of :math:`- \Delta H_{ads} / R.`
*Limitations*
The isosteric enthalpy is sensitive to the differences in pressure between
the two isotherms. If the isotherms measured are too close together, the
error margin will increase.
The method also assumes that enthalpy of adsorption does not vary with
temperature. If the variation is large for the system in question, the
isosteric enthalpy calculation will give unrealistic values.
Even with carefully measured experimental data, there are two assumptions
used in deriving the Clausius-Clapeyron equation: an ideal bulk gas phase
and a negligible adsorbed phase molar volume. These have a significant
effect on the calculated isosteric enthalpies of adsorption, especially at
high relative pressures and for heavy adsorbates.
See Also
--------
pygaps.characterisation.isosteric_enth.isosteric_enthalpy_raw : low level method
"""
# Check more than one isotherm
if len(isotherms) < 2:
raise ParameterError('Pass at least two isotherms.')
# Check same material
if not all(x.material == isotherms[0].material for x in isotherms):
raise ParameterError('Isotherms passed are not measured on the same material.')
# Check same basis
if len(set(x.loading_basis for x in isotherms)) > 1:
raise ParameterError('Isotherm passed are in a different loading basis.')
if len(set(x.material_basis for x in isotherms)) > 1:
raise ParameterError('Isotherm passed are in a different material basis.')
# Get temperatures
temperatures = [x.temperature for x in isotherms]
# Loading
load_args = {
'branch': branch,
'loading_unit': isotherms[0].loading_unit,
'material_unit': isotherms[0].material_unit,
}
loading = loading_points
if loading is None:
# Get a minimum and maximum loading common for all isotherms
min_loading = 1.01 * max([min(x.loading(**load_args)) for x in isotherms])
max_loading = 0.99 * min([max(x.loading(**load_args)) for x in isotherms])
loading = numpy.linspace(min_loading, max_loading, 50)
# Get pressure point for each isotherm at loading
pressures = numpy.array([iso.pressure_at(loading, **load_args) for iso in isotherms]).T
(
iso_enthalpy,
slopes,
correlation,
std_errs,
) = isosteric_enthalpy_raw(
pressures,
temperatures,
)
if verbose:
from pygaps.graphing.calc_graphs import isosteric_enthalpy_plot
isosteric_enthalpy_plot(
loading,
iso_enthalpy,
std_errs,
isotherms[0].units,
)
return {
'loading': loading,
'isosteric_enthalpy': iso_enthalpy,
'slopes': slopes,
'correlation': correlation,
'std_errs': std_errs,
}
[docs]def isosteric_enthalpy_raw(
pressures: list,
temperatures: list,
):
"""
Calculate the isosteric enthalpy of adsorption using several isotherms
recorded at different temperatures on the same material.
This is a 'bare-bones' function to calculate isosteric enthalpy which is
designed as a low-level alternative to the main function.
Designed for advanced use, its parameters have to be manually specified.
Parameters
----------
pressure : array of arrays
A two dimensional array of pressures for each isotherm at same loading point,
in bar. For example, if using two isotherms to calculate the isosteric enthalpy::
[
[p1_iso1, p1_iso2],
[p2_iso1, p2_iso2],
[p3_iso1, p3_iso2],
...
]
temperatures : array
Temperatures of the isotherms are taken, Kelvin.
Returns
-------
iso_enth : array
Calculated isosteric enthalpy.
slopes : array
Slopes fitted for each point.
correlations : array
The correlation of the straight line of each fit.
"""
# Check same lengths
if len(pressures[0]) != len(temperatures):
raise ParameterError(
"There are a different number of pressure points than temperature points"
)
# Convert to numpy arrays, just in case
pressures = numpy.asarray(pressures)
temperatures = numpy.asarray(temperatures)
# Calculate inverse temperatures
inv_t = 1 / temperatures
iso_enth = []
slopes = []
correlations = []
stderrs = []
log_pressures = numpy.log(pressures)
# Calculate enthalpy for each point by a linear fit
for log_p in log_pressures:
slope, intercept, corr_coef, p, std_err = stats.linregress(inv_t, log_p)
iso_enth.append(-constants.gas_constant * slope / 1000)
slopes.append(slope)
correlations.append(corr_coef)
stderrs.append(constants.gas_constant * std_err / 1000)
return iso_enth, slopes, correlations, stderrs