"""This module contains the alpha-s calculation."""
import numpy
from scipy import stats
from pygaps import logger
from pygaps.characterisation.area_bet import area_BET
from pygaps.characterisation.area_lang import area_langmuir
from pygaps.core.adsorbate import Adsorbate
from pygaps.core.baseisotherm import BaseIsotherm
from pygaps.core.modelisotherm import ModelIsotherm
from pygaps.core.pointisotherm import PointIsotherm
from pygaps.utilities.exceptions import CalculationError
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 alpha_s(
isotherm: "PointIsotherm | ModelIsotherm",
reference_isotherm: "PointIsotherm | ModelIsotherm",
reference_area: str = 'BET',
reducing_pressure: float = 0.4,
branch: str = 'ads',
branch_ref: str = 'ads',
t_limits: "tuple[float, float]" = None,
verbose: bool = False
):
r"""
Calculate surface area and pore volume using the alpha-s method.
The ``reference_isotherm`` parameter is an Isotherm class which will form
the x-axis of the alpha-s plot. The optional ``t_limits`` parameter has the
upper and lower limits of the loading the section which should be taken for
analysis.
Parameters
----------
isotherm : PointIsotherm, ModelIsotherm
The isotherm of which to calculate the alpha-s plot parameters.
reference_isotherm : PointIsotherm, ModelIsotherm
The isotherm to use as reference.
reference_area : float, 'BET', 'langmuir', optional
Area of the reference material or function to calculate it
using the reference isotherm.
If not specified, the BET method is used.
reducing_pressure : float, optional
p/p0 value at which the loading is reduced.
Default is 0.4 as it is the closing point for the nitrogen
hysteresis loop.
branch : {'ads', 'des'}, optional
Branch of the isotherm to use. It defaults to adsorption.
branch_ref : {'ads', 'des'}, optional
Branch of the reference isotherm to use. It defaults to adsorption.
t_limits : tuple[float, float], optional
Reference 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 material):
- ``alpha curve`` (list) : Calculated alpha-s 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*
In order to extend the t-plot analysis with other adsorbents and non-standard
thickness curves, the :math:`\alpha_s` method was devised [#]_. Instead of
a formula that describes the thickness of the adsorbed layer, a reference
isotherm is used. This isotherm is measured on a non-porous version of the
material with the same surface characteristics and with the same adsorbate.
The :math:`\alpha_s` values are obtained from this isotherm by regularisation with
an adsorption amount at a specific relative pressure, usually taken as 0.4 since
nitrogen hysteresis loops theoretically close at this value
.. math::
\alpha_s = \frac{n_a}{n_{0.4}}
The analysis then proceeds as in the t-plot method.
The slope of the linear section (:math:`s`) can be used to calculate the
area where 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 calculation uses the known area of the
reference material. If unknown, the area will be calculated here using the
BET or Langmuir method.
.. math::
A = \frac{s A_{ref}}{(n_{ref})_{0.4}}
If the region selected is after a vertical deviation, the intercept
(:math:`i`) 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*
The reference isotherm chosen for the :math:`\alpha_s` method must be a description
of the adsorption on a completely non-porous sample of the same material. It is
often impossible to obtain such non-porous versions, therefore care must be taken
how the reference isotherm is defined.
References
----------
.. [#] D. Atkinson, A.I. McLeod, K.S.W. Sing, J. Chem. Phys., 81, 791 (1984)
See Also
--------
pygaps.characterisation.alphas_plots.alpha_s_raw : low level method
"""
# Check to see if reference isotherm is given
if reference_isotherm is None or not isinstance(reference_isotherm, BaseIsotherm):
raise ParameterError(
"No reference isotherm for alpha s calculation "
"is provided. Must provide an Isotherm instance."
)
if reference_isotherm.adsorbate != isotherm.adsorbate:
raise ParameterError(
"The reference isotherm adsorbate is different than the "
"calculated isotherm adsorbate."
)
if not 0 < reducing_pressure < 1:
raise ParameterError("The reducing pressure is outside the bounds of 0-1 p/p0.")
# Deal with reference area
if reference_area.lower() in ['bet', None]:
try:
reference_area = area_BET(reference_isotherm).get('area')
except Exception as err:
raise CalculationError(
"Could not calculate a BET area for the reference isotherm. "
"Either solve the issue or provide a value for reference_area. "
f"BET area error is :\n{err}"
) from err
elif reference_area.lower() == 'langmuir':
try:
reference_area = area_langmuir(reference_isotherm).get('area')
except Exception as err:
raise CalculationError(
"Could not calculate a Langmuir area for the reference isotherm. "
"Either solve the issue or provide a value for reference_area. "
f"Langmuir area error is :\n{err}"
) from err
elif not isinstance(reference_area, float):
raise ParameterError(
"The reference area should be either a numeric value, 'BET' or 'Langmuir'. "
f"The value specified was {reference_area}."
)
# 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"}
)
# Now for reference isotherm
reference_loading = reference_isotherm.loading_at(
pressure,
pressure_unit=isotherm.pressure_unit,
loading_unit='mmol',
branch=branch_ref,
)
alpha_s_point = reference_isotherm.loading_at(
reducing_pressure,
loading_unit='mmol',
pressure_mode='relative',
branch=branch_ref,
)
# If on an desorption branch, reference data will be reversed
if branch_ref == 'des':
reference_loading = reference_loading[::-1]
# Call alpha s function
results, alpha_curve = alpha_s_raw(
loading,
reference_loading,
alpha_s_point,
reference_area,
liquid_density,
molar_mass,
t_limits=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}")
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(
alpha_curve,
loading,
results,
units,
alpha_s=True,
alpha_reducing_p=reducing_pressure
)
return {
'alpha_curve': alpha_curve,
'results': results,
}
[docs]def alpha_s_raw(
loading: "list[float]",
reference_loading: "list[float]",
alpha_s_point: float,
reference_area: float,
liquid_density: float,
adsorbate_molar_mass: float,
t_limits: "tuple[float,float]" = None,
):
"""
Calculate surface area and pore volume using the alpha-s method.
This is a 'bare-bones' function to calculate alpha-s 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, in mmol/material.
reference_loading : list[float]
Loading of the reference curve corresponding to the same pressures.
alpha_s_point : float
p/p0 value at which the loading is reduced.
reference_area : float
Area of the surface on which the reference isotherm is taken.
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
Reference thickness range in which to perform the calculation.
Returns
-------
results : list
A list of dictionaries with the following components:
- ``section`` (array[float]) : 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
alpha_curve : array
The generated thickness curve at each point using the thickness model.
"""
# Check lengths
if len(loading) == 0:
raise ParameterError("Empty input values!")
if len(loading) != len(reference_loading):
raise ParameterError("The length of the two loading arrays do not match.")
# Ensure numpy arrays, if not already
loading = numpy.asarray(loading)
reference_loading = numpy.asarray(reference_loading)
# The alpha-s method is a generalisation of the t-plot method
# As such, we can just call the t-plot method with the required parameters
alpha_curve = reference_loading / alpha_s_point
results = []
if t_limits is not None:
section = numpy.flatnonzero((alpha_curve > t_limits[0]) & (alpha_curve < t_limits[1]))
result = alpha_s_plot_parameters(
alpha_curve,
loading,
section,
alpha_s_point,
reference_area,
adsorbate_molar_mass,
liquid_density,
)
if result:
results.append(result)
else:
logger.warning("Could not fit a linear regression.")
else:
# Now we need to find the linear regions in the alpha-s for the
# assessment of surface area.
linear_sections = find_linear_sections(alpha_curve, loading)
# For each section we compute the linear fit
for section in linear_sections:
result = alpha_s_plot_parameters(
alpha_curve,
loading,
section,
alpha_s_point,
reference_area,
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, alpha_curve
def alpha_s_plot_parameters(
alpha_curve: "list[float]",
loading: "list[float]",
section: "list[float]",
alpha_s_point: float,
reference_area: float,
molar_mass: float,
liquid_density: float,
):
"""Get the parameters for the linear region of the alpha-s plot."""
slope, intercept, corr_coef, p, stderr = stats.linregress(
alpha_curve[section], loading[section]
)
# Check if slope is good
if slope * (max(alpha_curve) / max(loading)) < 3:
adsorbed_volume = intercept * molar_mass / liquid_density / 1000
area = (reference_area / alpha_s_point * slope).item()
return {
'section': section,
'slope': slope,
'intercept': intercept,
'corr_coef': corr_coef,
'adsorbed_volume': adsorbed_volume,
'area': area,
}
return None