"""This module contains the t-plot calculation."""
import typing as t
import numpy
from scipy import stats
from pygaps import logger
from pygaps.characterisation.models_thickness import get_thickness_model
from pygaps.core.adsorbate import Adsorbate
from pygaps.core.modelisotherm import ModelIsotherm
from pygaps.core.pointisotherm import PointIsotherm
from pygaps.utilities.exceptions import ParameterError
from pygaps.utilities.math_utilities import find_linear_sections
from pygaps.utilities.pygaps_utilities import get_iso_loading_and_pressure_ordered
[docs]def t_plot(
isotherm: "PointIsotherm | ModelIsotherm",
thickness_model: "str | t.Callable[[float], float]" = 'Harkins/Jura',
branch: str = 'ads',
t_limits: "tuple[float, float]" = None,
verbose: bool = False,
):
r"""
Calculate surface area and pore volume using a t-plot.
The ``thickness_model`` parameter is a string which names the thickness
equation which should be used. Alternatively, a user can implement their own
thickness model, either as an Isotherm or a function which describes the
adsorbed layer. In that case, instead of a string, pass the Isotherm object
or the callable function as the ``thickness_model`` parameter. The
``t_limits`` specifies the upper and lower limits of the thickness section
which should be taken for analysis.
Parameters
----------
isotherm : PointIsotherm, ModelIsotherm
The isotherm of which to calculate the t-plot parameters.
thickness_model : str, PointIsotherm, ModelIsotherm, `callable`, optional
Name of the thickness model to use. Defaults to the Harkins and Jura
thickness curve.
branch : {'ads', 'des'}, optional
Branch of the isotherm to use. It defaults to adsorption.
t_limits : tuple[float, float], optional
Thickness range in which to perform the calculation.
verbose : bool, optional
Prints extra information and plots graphs of the calculation.
Returns
-------
dict
A dictionary containing the t-plot curve, as well as a list of dictionaries
with calculated parameters for each straight section. The basis of these
results will be derived from the basis of the isotherm (per mass or per
volume of adsorbent):
- ``thickness curve`` (list) : Calculated thickness curve
- ``results`` (list of dicts):
- ``section`` (array) : the points of the plot chosen for the line
- ``area`` (float) : calculated surface area, from the section parameters
- ``adsorbed_volume`` (float) : the amount adsorbed in the pores as calculated per section
- ``slope`` (float) : slope of the straight trendline fixed through the region
- ``intercept`` (float) : intercept of the straight trendline through the region
- ``corr_coef`` (float) : correlation coefficient of the linear region
Raises
------
ParameterError
When something is wrong with the function parameters.
CalculationError
When the calculation itself fails.
Notes
-----
*Description*
The t-plot method [#]_ attempts to relate the adsorption on a material with an ideal
curve which describes the thickness of the adsorbed layer on a surface. A plot is
constructed, where the isotherm loading data is plotted versus thickness values obtained
through the model.
It stands to reason that, in the case that the experimental adsorption curve follows
the model, a straight line will be obtained with its intercept through the origin.
However, since in most cases there are differences between adsorption in the pores
and ideal surface adsorption, the t-plot will deviate and form features which can
be analysed to describe the material characteristics.
- a sharp vertical deviation will indicate condensation in a type of pore
- a gradual slope will indicate adsorption on the wall of a particular pore
The slope of the linear section can be used to calculate the area where the adsorption
is taking place. If it is of a linear region at the start of the curve, it will represent
the total surface area of the material. If at the end of the curve, it will instead
represent external surface area of the material. The formula to calculate the area is:
.. math::
A = \frac{s M_m}{\rho_{l}}
where :math:`\rho_{l}` is the liquid density of the adsorbate at experimental
conditions
If the region selected is after a vertical deviation, the intercept of the line
will no longer pass through the origin. This intercept be used to calculate the
pore volume through the following equation:
.. math::
V_{ads} = \frac{i M_m}{\rho_{l}}
*Limitations*
Since the t-plot method is observing the differences between the isotherm
and an ideal adsorption curve, care must be taken to ensure that the model
actually describes the thickness of a layer of adsorbate on the surface of
the adsorbent. This is more difficult than it appears as no universal
thickness curve exists. When selecting a thickness model, make sure that it
is applicable to both the material and the adsorbate. Interactions at
loadings that occur on the t-plot lower than the monolayer thickness do not
have any physical meaning.
A second notable caveat is that the geometry of the pore will change the
packing of the molecules, therefore the total thickness. Therefore, at
smaller pores (<10 x the size of the guest molecule)
References
----------
.. [#] “Studies on Pore Systems in Catalysts V. The t Method”,
B. C. Lippens and J. H. de Boer, J. Catalysis, 4, 319 (1965)
See Also
--------
pygaps.characterisation.t_plots.t_plot_raw : low level method
"""
# Function parameter checks
if thickness_model is None:
raise ParameterError(
"Specify a model to generate the thickness curve"
" e.g. thickness_model=\"Halsey\""
)
# Get adsorbate properties
adsorbate = Adsorbate.find(isotherm.adsorbate)
molar_mass = adsorbate.molar_mass()
liquid_density = adsorbate.liquid_density(isotherm.temperature)
# Read data in
pressure, loading = get_iso_loading_and_pressure_ordered(
isotherm, branch, {
"loading_basis": "molar",
"loading_unit": "mmol"
}, {"pressure_mode": "relative"}
)
# Get thickness model
t_model = get_thickness_model(thickness_model)
# Call t-plot function
results, t_curve = t_plot_raw(
loading,
pressure,
t_model,
liquid_density,
molar_mass,
t_limits,
)
if verbose:
if not results:
logger.info("Could not find linear regions, attempt a manual limit.")
else:
for index, result in enumerate(results):
logger.info(f"For linear region {index + 1}")
logger.info(
f"The slope is {result.get('slope'):.4g} "
f"and the intercept is {result.get('intercept'):.4g}, "
f"with a correlation coefficient of {result.get('corr_coef'):.4g}"
)
logger.info(
f"The adsorbed volume is {result.get('adsorbed_volume'):.3g} cm3/{isotherm.material_unit} "
f"and the area is {result.get('area'):.4g} m2/{isotherm.material_unit}"
)
from pygaps.graphing.calc_graphs import tp_plot
units = isotherm.units
units.update({"loading_basis": "molar", "loading_unit": "mmol"})
tp_plot(t_curve, loading, results, units)
return {
't_curve': t_curve,
'results': results,
}
[docs]def t_plot_raw(
loading: "list[float]",
pressure: "list[float]",
thickness_model: t.Callable[[float], float],
liquid_density: float,
adsorbate_molar_mass: float,
t_limits: "tuple[float,float]" = None,
):
"""
Calculate surface area and pore volume using a t-plot.
This is a 'bare-bones' function to calculate t-plot parameters which is
designed as a low-level alternative to the main function.
Designed for advanced use, its parameters have to be manually specified.
Parameters
----------
loading : list[float]
Amount adsorbed at the surface, mmol/material.
pressure : list[float]
Relative pressure corresponding to the loading.
thickness_model : callable[[float], float]
Function which which returns the thickness of the adsorbed layer at a pressure p.
liquid_density : float
Density of the adsorbate in the adsorbed state, in g/cm3.
adsorbate_molar_mass : float
Molar mass of the adsorbate, in g/mol.
t_limits : tuple[float, float], optional
Thickness range in which to perform the calculation.
Returns
-------
results : list
A list of dictionaries with the following components:
- ``section`` (array) : the indices of points chosen for the line fit
- ``area`` (float) : calculated surface area, from the section parameters
- ``adsorbed_volume`` (float) : the amount adsorbed in the pores as
calculated per section
- ``slope`` (float) : slope of the straight trendline fixed through the region
- ``intercept`` (float) : intercept of the straight trendline through the region
- ``corr_coef`` (float) : correlation coefficient of the linear region
thickness_curve : array
The generated thickness curve at each point using the thickness model.
"""
# Check lengths
if len(pressure) == 0:
raise ParameterError("Empty input values!")
if len(pressure) != len(loading):
raise ParameterError("The length of the pressure and loading arrays do not match.")
# Ensure numpy arrays, if not already
loading = numpy.asarray(loading)
pressure = numpy.asarray(pressure)
# Generate the thickness curve for the pressure points
thickness_curve = thickness_model(pressure)
results = []
# If limits are not None, then the user requested specific limits
if t_limits is not None:
section = numpy.flatnonzero((thickness_curve > t_limits[0])
& (thickness_curve < t_limits[1]))
result = t_plot_parameters(
thickness_curve,
loading,
section,
adsorbate_molar_mass,
liquid_density,
)
if result:
results.append(result)
else:
logger.warning("Could not fit a linear regression.")
# If not, attempt to find limits manually
else:
# Now we need to find the linear regions in the t-plot for the
# assessment of surface area.
linear_sections = find_linear_sections(thickness_curve, loading)
# For each section we compute the linear fit
for section in linear_sections:
result = t_plot_parameters(
thickness_curve,
loading,
section,
adsorbate_molar_mass,
liquid_density,
)
if result:
results.append(result)
if not results:
logger.warning("Could not determine linear regions, attempt a manual limit.")
return results, thickness_curve
def t_plot_parameters(
thickness_curve: list,
loading: list,
section: slice,
molar_mass: float,
liquid_density: float,
):
"""Calculate the parameters from a linear section of the t-plot."""
slope, intercept, corr_coef, p, stderr = stats.linregress(
thickness_curve[section], loading[section]
)
# Check if slope is good
if slope * (max(thickness_curve) / max(loading)) < 3:
adsorbed_volume = intercept * molar_mass / liquid_density / 1000
area = slope * molar_mass / liquid_density
return {
'section': section,
'slope': slope,
'intercept': intercept,
'corr_coef': corr_coef,
'adsorbed_volume': adsorbed_volume,
'area': area,
}
return None