https://raw.githubusercontent.com/pauliacomi/pyGAPS/master/docs/logo.svg

Overview#

pyGAPS (Python General Adsorption Processing Suite) is a framework for adsorption data analysis and fitting, written in Python 3.

status

Project Status: Active – The project has reached a stable, usable state and is being actively developed. Commits since latest release

docs

Documentation Status

license

Project License

tests

GHA-CI Build Status Coverage Status
Requirements Status

package

PyPI Package latest release PyPI Wheel
Supported versions Supported implementations

Features#

  • Advanced adsorption data import and manipulation.

  • A fully feature thermodynamic backend allowing conversions between pressures, temperatures, loadings, etc.

  • Routine analysis such as BET/Langmuir surface area, t-plots, alpha-s plots, Dubinin plots etc.

  • Pore size distribution calculations for mesopores (BJH, Dollimore-Heal).

  • Pore size distribution calculations for micropores (Horvath-Kawazoe).

  • Pore size distribution calculations using kernels (DFT, QSDFT, ...)

  • Isotherm fitting with various models (Henry, Langmuir, DS/TS Langmuir, etc..)

  • Enthalpy of adsorption calculations.

  • IAST predictions for binary and multicomponent adsorption.

  • Parsing to and from multiple formats such as AIF, Excel, CSV and JSON.

  • Simple methods for isotherm graphing and comparison.

  • An database backend for storing and retrieving data.

Documentation#

pyGAPS is built with three key mantras in mind:

  • Opinionated: there are many places where the code will suggest or default to what the it considers a good practice. As examples: the standard units, pore size distribution methods and BET calculation limits.

  • Flexible: while the defaults are there for a reason, you can override pretty much any parameter. Want to pass a custom adsorbate thickness function or use volumetric bases? Can do!

  • Transparent: all code is well documented and open source. There are no black boxes.

In-depth explanations, examples and theory can be found in the online documentation. If you are familiar with Python and adsorption and want to jump right in, look at the quickstart section. Examples for each of the capabilities specified above can be found documented here. Most of the pages are actually Jupyter Notebooks, you can download them and run them yourself from the /docs/examples folder.

To become well familiarised with the concepts introduced by pyGAPS, such as what is an Isotherm, how units work, what data is required and can be stored etc., a deep dive is available in the manual.

Finally, having a strong grasp of the science of adsorption is recommended, to understand the strengths and shortcomings of various methods. We have done our best to explain the theory and application range of each capability and model. To learn more, look at the reference or simply call help() from a python interpreter (for example help(pygaps.PointIsotherm).

Support and sponsorship#

This project is graciously sponsored by Surface Measurement Systems, by employing Paul Iacomi, the core maintainer. The work would not be possible without their contribution, keeping this open source project alive.

https://raw.githubusercontent.com/pauliacomi/pyGAPS/master/docs/figures/SMS-Logo.jpg

If you are interested in implementing a particular feature, or obtaining professional level support, contact us here Bugs or questions?.

Citing#

Please consider citing the related paper we published if you use the program in your research.

Paul Iacomi, Philip L. Llewellyn, Adsorption (2019). pyGAPS: A Python-Based Framework for Adsorption Isotherm Processing and Material Characterisation. DOI: https://doi.org/10.1007/s10450-019-00168-5

Installation#

The easiest way to install pyGAPS is from the command line. You can use pip:

pip install pygaps

or Anaconda/Conda:

conda install -c conda-forge pygaps

If you are just starting out, Anaconda/Conda is a good bet since it manages virtual environments for you. Check out Installation for more details.

Development#

To install the development branch, clone the repository from GitHub. Then install the package with pip either in regular or developer mode.

git clone https://github.com/pauliacomi/pyGAPS

# then install editable/develop mode
# adding [dev] will install the development dependencies
pip install -e ./pyGAPS[dev]

If you want to contribute to pyGAPS or develop your own code from the package, check out the detailed information in CONTRIBUTING.rst.

Bugs or questions?#

For any bugs found, please open an issue or, even better, submit a pull request. It'll make my life easier. This also applies to any features which you think might benefit the project. I'm also more than happy to answer any questions. Shoot an email to mail( at )pauliacomi.com or find me at https://pauliacomi.com or on Twitter.

Installation#

pyGAPS is made for modern versions of Python, currently requiring at least Python 3.7.

Command line#

The installation process should take care of the dependencies for you. If using pip all you need to do is:

pip install pygaps

We recommend using the Anaconda/Conda environment, as it preinstalls most required dependencies as well as making managing environments a breeze. To create a new environment with pyGAPS:

conda create -n myenv python=3.8
conda activate myenv
conda install pygaps

To install the development branch, clone the repository from Github. Then install the package, in regular or editable mode

git clone https://github.com/pauliacomi/pyGAPS

# then install
pip install ./pyGAPS

# alternatively in developer mode
pip install -e ./pyGAPS

Dependencies#

The main packages that pyGAPS depends on are

  • The common data science packages: numpy, scipy, pandas and matplotlib.

  • The CoolProp backend for physical properties calculation (can also be connected to REFPROP if locally available).

  • gemmi for parsing AIF files.

  • xlrd, xlwt, openpyxl for parsing to and from Excel files.

  • requests, for communicating with the NIST ISODB.

The pyIAST package used to be a required dependency, but has since been integrated in the pyGAPS framework. More info about pyIAST can be found in the manuscript:

C. Simon, B. Smit, M. Haranczyk. pyIAST: Ideal Adsorbed Solution Theory (IAST) Python Package. Computer Physics Communications. (2015)

This page was generated from docs/examples/quickstart.ipynb. To start an interactive version: Binder badge

Quickstart#

This document should get you started with pyGAPS, providing several common operations like creating/reading isotherms, running characterisation routines, model fitting and data export/plotting.

You can download this code as a Jupyter Notebook and run it for yourself! See banner at the top for the link.

Creating an isotherm#

First, we need to import the package.

[1]:
%matplotlib inline
import pygaps as pg

The backbone of the framework is the PointIsotherm class. This class stores the isotherm data alongside isotherm properties such as the material, adsorbate and temperature, as well as providing easy interaction with the framework calculations. There are several ways to create a PointIsotherm object:

  • directly from arrays

  • from a pandas.DataFrame

  • parsing AIF, json, csv, or excel files

  • loading manufacturer reports (Micromeritics, Belsorp, 3P, Quantachrome, etc.)

  • loading from an sqlite database

See the isotherm creation part of the documentation for a more in-depth explanation. For the simplest method, the data can be passed in as arrays of pressure and loading. There are three other required metadata parameters: the adsorbent material name, the adsorbate molecule used, and the temperature at which the data was recorded.

[2]:
isotherm = pg.PointIsotherm(
    pressure=[0.1, 0.2, 0.3, 0.4, 0.5, 0.4, 0.35, 0.25, 0.15, 0.05],
    loading=[0.1, 0.2, 0.3, 0.4, 0.5, 0.45, 0.4, 0.3, 0.15, 0.05],
    material='Carbon X1',
    adsorbate='N2',
    temperature=77,
)
WARNING: 'pressure_mode' was not specified, assumed as 'absolute'
WARNING: 'pressure_unit' was not specified, assumed as 'bar'
WARNING: 'material_basis' was not specified, assumed as 'mass'
WARNING: 'material_unit' was not specified, assumed as 'g'
WARNING: 'loading_basis' was not specified, assumed as 'molar'
WARNING: 'loading_unit' was not specified, assumed as 'mmol'
WARNING: 'temperature_unit' was not specified, assumed as 'K'

We can see that most units are set to defaults and we are being notified about it. Let's assume we don't want to change anything for now.

To see a summary of the isotherm metadata, use the print function:

[3]:
print(isotherm)
Material: Carbon X1
Adsorbate: nitrogen
Temperature: 77.0K
Units:
        Uptake in: mmol/g
        Pressure in: bar

Unless specified, the loading is read in mmol/g and the pressure is read in bar. We can specify (and convert) units to anything from weight% vs Pa, mol/cm3 vs relative pressure to cm3/mol vs torr. Read more about how pyGAPS handles units in this section of the manual. The isotherm can also have other properties which are passed in at creation.

Alternatively, the data can be passed in the form of a pandas.DataFrame. This allows for other complementary data, such as ambient pressure, isosteric enthalpy, or other simultaneous measurements corresponding to each point to be saved.

The DataFrame should have at least two columns: the pressures at which each point was recorded, and the loadings for each point. The loading_key and pressure_key parameters specify which column in the DataFrame contain the loading and pressure, respectively. We will also take the opportunity to set our own units, and a few extra metadata properties.

[4]:
import pandas as pd

# create (or import) a DataFrame
data = pd.DataFrame({
    'pressure': [0.1, 0.2, 0.3, 0.4, 0.5, 0.45, 0.35, 0.25, 0.15, 0.05],
    'loading': [0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.4, 0.3, 0.15, 0.05],
    'Enthalpy [kJ/mol]': [15, 14, 13.5, 13, 12, 11, 10, 10, 10, 10],
})

isotherm = pg.PointIsotherm(
    isotherm_data=data,
    pressure_key='pressure',
    loading_key='loading',

    material='Carbon X1',
    adsorbate='N2',
    temperature=20,

    pressure_mode='relative',
    pressure_unit=None,  # we are in relative mode
    loading_basis='mass',
    loading_unit='g',
    material_basis='volume',
    material_unit='cm3',
    temperature_unit='°C',

    material_batch='Batch 1',
    iso_type='characterisation'
)

All passed metadata is stored in the isotherm.properties dictionary.

[5]:
print(isotherm.properties)
{'material_batch': 'Batch 1', 'iso_type': 'characterisation'}

A summary and a plot can be generated by using the print_info function. Notice how the units are automatically taken into account in the plots.

[6]:
isotherm.print_info(y2_range=[0, 20])
Material: Carbon X1
Adsorbate: nitrogen
Temperature: 293.15K
Units:
        Uptake in: g/cm3
        Relative pressure
Other properties:
        material_batch: Batch 1
        iso_type: characterisation

(
    <AxesSubplot:xlabel='Pressure [$p/p^0$]', ylabel='Loading [$g\\/cm^{-3}$]'>,
    <AxesSubplot:ylabel='Enthalpy [kJ/mol]'>
)
_images/examples_quickstart_12_2.png

pyGAPS also comes with a variety of parsers that allow isotherms to be saved or loaded. Here we can use the JSON parser to get an isotherm previously saved on disk. For more info on parsing to and from various formats see the manual and the associated examples.

[7]:
import pygaps.parsing as pgp
isotherm = pgp.isotherm_from_json(r'data/carbon_x1_n2.json')

We can then inspect the isotherm data using various functions:

[8]:
isotherm.data()
[8]:
pressure loading branch
0 1.884754e-07 0.510281 0
1 4.498150e-07 1.022560 0
2 1.058960e-06 1.541660 0
3 2.360800e-06 2.059750 0
4 4.935335e-06 2.580030 0
... ... ... ...
126 1.717322e-01 12.969600 1
127 1.478162e-01 12.792700 1
128 1.243666e-01 12.582000 1
129 1.028454e-01 12.337100 1
130 8.843120e-02 12.138200 1

131 rows × 3 columns

[9]:
isotherm.pressure(branch="des")
array([0.94224978, 0.9175527 , 0.88026274, 0.85005033, 0.8250035 ,
       0.79809718, 0.75394244, 0.72825927, 0.70260524, 0.67857171,
       0.65424575, 0.63045607, 0.60668388, 0.58312446, 0.55678356,
       0.53354765, 0.50847557, 0.48454113, 0.46082723, 0.4361253 ,
       0.41206068, 0.38803881, 0.36445995, 0.33972693, 0.3160004 ,
       0.29139658, 0.26721247, 0.24362584, 0.21954373, 0.195449  ,
       0.17173219, 0.14781621, 0.12436657, 0.1028454 , 0.0884312 ])

To see just a plot of the isotherm, use the plot function:

[10]:
isotherm.plot()
_images/examples_quickstart_19_0.png

Isotherms can be plotted in different units/modes, or can be permanently converted. If conversion is desired, find out more in this section. For example, using the previous isotherm:

[11]:
# This just displays the isotherm in a different unit
isotherm.plot(pressure_unit='torr', loading_basis='percent')
# The isotherm is still internally in the same units
print(f"Isotherm is still in {isotherm.pressure_unit} and {isotherm.loading_unit}.")
Isotherm is still in bar and mmol.
_images/examples_quickstart_21_1.png
[12]:
# While the underlying units can be completely converted
isotherm.convert(pressure_mode='relative')
print(f"Isotherm is now permanently in {isotherm.pressure_mode} pressure.")
isotherm.plot()
Isotherm is now permanently in relative pressure.
_images/examples_quickstart_22_1.png

Now that the PointIsotherm is created, we are ready to do some analysis of its properties.


Isotherm analysis#

The framework has several isotherm analysis tools which are commonly used to characterise porous materials such as:

  • BET surface area

  • the t-plot method / alpha s method

  • mesoporous PSD (pore size distribution) calculations

  • microporous PSD calculations

  • DFT kernel fitting PSD methods

  • isosteric enthalpy of adsorption calculation

  • and much more...

All methods work directly with generated Isotherms. For example, to perform a t-plot analysis and get the results in a dictionary use:

[13]:
import pprint
import pygaps.characterisation as pgc

result_dict = pgc.t_plot(isotherm)
pprint.pprint(result_dict)
{'results': [{'adsorbed_volume': 0.06225102073354812,
              'area': 1033.1609271782331,
              'corr_coef': 0.9998068073094798,
              'intercept': 1.7912656114940457,
              'section': [22, 23, 24, 25, 26, 27, 28, 29],
              'slope': 29.72908103651894},
             {'adsorbed_volume': 0.410455160252538,
              'area': 139.1041773312518,
              'corr_coef': 0.9836601580118213,
              'intercept': 11.81079771796286,
              'section': [64,
                          65,
                          66,
                          67,
                          68,
                          69,
                          70,
                          71,
                          72,
                          73,
                          74,
                          75,
                          76,
                          77,
                          78,
                          79,
                          80,
                          81,
                          82,
                          83,
                          84,
                          85,
                          86,
                          87,
                          88,
                          89,
                          90,
                          91,
                          92],
              'slope': 4.002705920842157}],
 't_curve': array([0.14381104, 0.14800322, 0.1525095 , 0.15712503, 0.1617626 ,
       0.16612841, 0.17033488, 0.17458578, 0.17879119, 0.18306956,
       0.18764848, 0.19283516, 0.19881473, 0.2058225 , 0.21395749,
       0.2228623 , 0.23213447, 0.2411563 , 0.24949659, 0.25634201,
       0.2635719 , 0.27002947, 0.27633547, 0.28229453, 0.28784398,
       0.29315681, 0.29819119, 0.30301872, 0.30762151, 0.31210773,
       0.31641915, 0.32068381, 0.32481658, 0.32886821, 0.33277497,
       0.33761078, 0.34138501, 0.34505614, 0.34870159, 0.35228919,
       0.35587619, 0.35917214, 0.36264598, 0.36618179, 0.36956969,
       0.37295932, 0.37630582, 0.37957513, 0.38277985, 0.38608229,
       0.3892784 , 0.3924393 , 0.39566979, 0.39876923, 0.40194987,
       0.40514492, 0.40824114, 0.41138787, 0.41450379, 0.41759906,
       0.42072338, 0.42387825, 0.42691471, 0.43000525, 0.44357547,
       0.46150731, 0.47647445, 0.49286816, 0.50812087, 0.52341251,
       0.53937129, 0.55659203, 0.57281485, 0.5897311 , 0.609567  ,
       0.62665975, 0.64822743, 0.66907008, 0.69046915, 0.71246898,
       0.73767931, 0.76126425, 0.79092372, 0.82052677, 0.85273827,
       0.88701466, 0.92485731, 0.96660227, 1.01333614, 1.06514197,
       1.1237298 , 1.19133932, 1.27032012, 1.36103511, 1.45572245,
       1.55317729])}

If in an interactive environment, such as iPython or Jupyter, it is useful to see the details of the calculation directly. To do this, increase the verbosity of the method to display extra information, including graphs:

[14]:
result_dict = pgc.area_BET(isotherm, verbose=True)
BET area: a = 1110 m2/g
The BET constant is: C = 372.8
Minimum pressure point is 0.0105 and maximum is 0.0979
Statistical monolayer at: n = 0.0114 mol/g
The slope of the BET fit: s = 87.7
The intercept of the BET fit: i = 0.236

_images/examples_quickstart_27_1.png
_images/examples_quickstart_27_2.png

Depending on the method, parameters can be specified to tweak the way the calculations are performed. For example, if a mesoporous size distribution is desired using the Dollimore-Heal method on the desorption branch of the isotherm, assuming the pores are cylindrical and that adsorbate thickness can be described by a Halsey-type thickness curve, the code will look like:

[15]:
result_dict = pgc.psd_mesoporous(
    isotherm,
    psd_model='DH',
    branch='des',
    pore_geometry='cylinder',
    thickness_model='Halsey',
    verbose=True,
)
_images/examples_quickstart_29_0.png

For more information on how to use each method, check the manual and the associated examples.


Isotherm fitting#

The framework comes with functionality to fit point isotherm data with common isotherm models such as Henry, Langmuir, Temkin, Virial etc.

The model is contained in the ModelIsotherm class. The class is similar to the PointIsotherm class, and shares the parameters and metadata. However, instead of point data, it stores model coefficients for the model it's describing.

To create a ModelIsotherm, the same parameters dictionary / pandas.DataFrame procedure can be used. But, assuming we've already created a PointIsotherm object, we can just pass it to the pygaps.model_iso function.

[16]:
import pygaps.modelling as pgm

model_iso = pgm.model_iso(isotherm, model='DSLangmuir', verbose=True)
model_iso
Attempting to model using DSLangmuir.
Model DSLangmuir success, RMSE is 0.0402
<ModelIsotherm 38c2a31aae475e0e5f6285194152baf0>: 'nitrogen' on 'Carbon X1' at 77.355 K
_images/examples_quickstart_32_2.png

A minimisation procedure will then attempt to fit the model's parameters to the isotherm points. If successful, the ModelIsotherm is returned.

In the user wants to screen several models at once, the model can be given as guess, to try to find the best fitting model. Below, we will attempt to fit several simple available models, and the one with the best RMSE will be returned. This method may take significant processing time, and there is no guarantee that the model is physically relevant.

[17]:
model_iso = pgm.model_iso(isotherm, model='guess', verbose=True)
Attempting to model using Henry.
Model Henry success, RMSE is 0.352
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.101
Attempting to model using DSLangmuir.
Model DSLangmuir success, RMSE is 0.0402
Attempting to model using DR.
Model DR success, RMSE is 0.0623
Attempting to model using Freundlich.
Model Freundlich success, RMSE is 0.035
Attempting to model using Quadratic.
Model Quadratic success, RMSE is 0.0402
Attempting to model using BET.
Model BET success, RMSE is 0.0515
Attempting to model using TemkinApprox.
Model TemkinApprox success, RMSE is 0.0971
Attempting to model using Toth.
Model Toth success, RMSE is 0.0358
Attempting to model using JensenSeaton.
Model JensenSeaton success, RMSE is 0.0253
Best model fit is JensenSeaton.
_images/examples_quickstart_34_1.png

More advanced settings can also be specified, such as the parameters for the fitting routine or the initial parameter guess. For in-depth examples and discussion check the manual and the associated examples.

To print the model parameters use the same print method as before.

[18]:
# Prints isotherm parameters and model info
model_iso.print_info()
Material: Carbon X1
Adsorbate: nitrogen
Temperature: 77.355K
Units:
        Uptake in: mmol/g
        Relative pressure
Other properties:
        plot_fit: False
        material_batch: X1
        iso_type: physisorption
        user: PI
        instrument: homemade1
        activation_temperature: 150.0
        lab: local
        treatment: acid activated
JensenSeaton isotherm model.
RMSE = 0.02527
Model parameters:
        K = 5.516e+08
        a = 16.73
        b = 0.3403
        c = 0.1807
Model applicable range:
        Pressure range: 1.86e-07 - 0.946
        Loading range: 0.51 - 21.6

_images/examples_quickstart_36_1.png

We can calculate the loading at any pressure using the internal model by using the loading_at function.

[19]:
# Returns the loading at 1 bar calculated with the model
model_iso.loading_at(1.0)
17.46787643653736
[20]:
# Returns the loading for three points in the 0-1 bar range
pressure = [0.1,0.5,1]
model_iso.loading_at(pressure)
array([12.09708239, 14.86182185, 17.46787644])

Plotting#

pyGAPS makes graphing both PointIsotherm and ModelIsotherm objects easy to facilitate visual observations, inclusion in publications and consistency. Plotting an isotherm is as simple as:

[21]:
import pygaps.graphing as pgg

pgg.plot_iso(
    [isotherm, model_iso],                          # Two isotherms
    branch='ads',                                   # Plot only the adsorption branch
    lgd_keys=['material', 'adsorbate', 'type'],     # Text in the legend, as taken from the isotherms
)
_images/examples_quickstart_41_0.png

Here is a more involved plot, where we create the figure beforehand, and specify many more customisation options.

[22]:
import matplotlib.pyplot as plt

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(8, 4))

pgg.plot_iso(
    [isotherm, model_iso],
    ax=ax1,
    branch='all',
    pressure_mode="relative%",
    x_range=(None, 80),
    color=["r", "k"],
    lgd_keys=['adsorbate', 'branch', 'type'],
)

model_iso.plot(
    ax=ax2,
    x_points=isotherm.pressure(),
    loading_unit="mol",
    y1_range=(None, 0.023),
    marker="s",
    color="k",
    y1_line_style={
        "linestyle": "--",
        "markersize": 3
    },
    logx=True,
    lgd_pos="upper left",
    lgd_keys=['material', 'key', 'type'],
)
_images/examples_quickstart_43_0.png

Many settings can be specified to change the look and feel of the graphs. More explanations can be found in the manual and in the examples section.

Manual#

An in-depth discussion of the concepts introduced in pyGAPS are presented in this part of the documentation. These concepts should be understood in order to work with the program, else one runs at risk of encountering unexplained errors or even worse, encountering bad results without any errors.

A discussion regarding the core functionality and classes of pyGAPS is followed by details on how to work with isotherms: import them, use characterisation functions, fit various models and plot or export the results.

Core Classes#

The Isotherm classes#
Overview#

In pyGAPS, an adsorption isotherm can be represented in two ways: as a PointIsotherm or as a ModelIsotherm. These two classes have many common methods and attributes, but they differ in the way they define the relationship between pressure (concentration) and loading (uptake):

  • The PointIsotherm class is a collection of discrete points, stored in a pandas.DataFrame object.

  • The ModelIsotherm class is a dictionary of parameters which are used to describe a model of adsorption behaviour (Henry, Langmuir, etc..).

Both classes are derived from the BaseIsotherm class. This class holds parameters of the isotherm besides data, such as the adsorbate used, the material's name and the temperature at which the isotherm was measured. These parameters are then inherited by the two isotherm objects. The user is unlikely to use this class directly.

To work with pyGAPS isotherms check out the following topics:

Note

A detailed explanation of each isotherm class and methods is written in their docstrings and can be also read in the reference.

Creating isotherms#
Creating a PointIsotherm#

There are several ways to create a PointIsotherm object:

  • Passing a pressure and a loading array.

  • From a pandas.DataFrame. This is the most extensible way to create a PointIsotherm, as other data types can be specified.

  • Parsed from one of the formats known to pyGAPS:

  • Imported from one of many device manufacturer formats. See parsing manufacturer files.

  • From an internal database: pyGAPS contains functionality to store and retrieve constructed isotherms in an sqlite database. See database.

This section will explain how to create an isotherm from raw data. The fastest way is to pass pressure and loading arrays to the constructor as the pressure and loading parameters.

The other information that needs to be passed to the constructor is isotherm metadata. This is information about the material the isotherm was measured on, the adsorbate which was used, as well as data about the temperature, units used and so on. The isotherm metadata must include:

  • The material name (material or m).

  • The adsorbate used (adsorbate or a).

  • The temperature at which the data was recorded (temperature or t).

As such, the simplest creation statement is:

point_isotherm = pygaps.PointIsotherm(
    pressure=[1,2,3,4],
    loading=[1,2,3,4],
    m='carbon',
    a='nitrogen',
    t=77,
)

The isotherm units can also be specified. If not given, the framework will assume default values: temperature in Kelvin, absolute pressure in bar and amount adsorbed in terms of mmol per g (molar basis loading per mass basis material). Options are:

  • pressure_mode specifies if the pressure is absolute or relative (p/p0). If not passed, the pressure is assumed to be absolute.

  • pressure_unit specifies the unit the pressure is measured in, if applicable. It can be bar, Pa, kPa, etc. and it defaults to bar.

  • loading_basis specifies if the amount adsorbed is defined in terms of moles, gas volume, liquid volume, mass, fractional or percentage. If not passed, it is assumed to be molar.

  • loading_unit specifies the unit the amount adsorbed is in. Depending on the loading_basis it can be a mass, volume or molar unit. By default, the loading is read in mmol.

  • material_basis specifies if the quantity of material is defined in terms of moles, volume or mass. If not passed, it is assumed to be on a mass basis.

  • material_unit specifies the unit the material itself is in. Depending on the material_basis it can be a mass, volume or molar unit. By default, the material is is read in g.

  • temperature_unit for the given temperature, K by default.

Other user metadata can be passed as well, and will be stored in the isotherm object properties dictionary. Will these components, an isotherm can now be created. A more complex instantiation is given below, with explanations.

point_isotherm = pygaps.PointIsotherm(
    pressure=[1,2,3],               # pressure here
    loading=[1,2,3],                # loading here

    # Required metadata
    material='carbon',              # Required
    adsorbate='nitrogen',           # Required
    temperature=77,                 # Required

    # Unit parameters can be specified
    pressure_mode='absolute',       # Working in absolute pressure
    pressure_unit='bar',            # with units of bar
    loading_basis='molar',          # Working on a loading molar basis
    loading_unit='mmol',            # with units of mmol
    material_basis='mass',          # Working on a per mass material basis
    material_unit='g',              # with units of g

    # Finally some other isotherm metadata
    apparatus='X1',                 # User specific
    activation_temperature=150,     # User specific
    user='John',                    # User specific
    DOI='10.000/mydoi',             # User specific
    something='something',          # User specific
)

The code does its best to attempt to guess whether the data passed is part of an adsorption branch, desorption branch or has both. It does this by looking at whether pressure is increasing or decreasing between two consecutive points and marks branches internally. If the data isn't well conditioned, this functionality will likely not produce good results. In this case, the user can specify whether the data is an adsorption or desorption branch by using the branch argument. The user can also divide the data directly by passing a list of int as the branch parameter. See more in the PointIsotherm reference.

Caution

The data in the columns is assumed to be free of errors and anomalies. Negative pressures or loadings, noisy signals or erroneous points may give undefined behaviour.

Alternatively, a pandas.DataFrame can be passed in. This allows for more data than just pressure and loading to be stored in a single isotherm. The DataFrame should have at least two columns: one for pressure and one for loading. Other data columns can represent secondary measurements: calorimetry data, magnetic field strengths, and are stored in the PointIsotherm.

If a DataFrame is used, pressure_key and loading_key are two required parameters specifying which column in the DataFrame contains the pressure and loading, respectively. As an example:

point_isotherm = pygaps.PointIsotherm(
    # First the pandas.DataFrame
    isotherm_data=pandas.DataFrame({
        'pressure' : [1, 2, 3, 4, 5, 3, 2],             # required
        'loading' : [1, 2, 3, 4, 5, 3, 2],              # required
        'enthalpy' : [15, 15, 15, 15, 15, 15, 15],
        'xrd_peak_1' : [0, 0, 1, 2, 2, 1, 0],
    }),

    # and the keys to what the columns represent.
    loading_key='loading',          # The loading column
    pressure_key='pressure',        # The pressure column

    # Required metadata
    material='carbon',              # Required
    adsorbate='nitrogen',           # Required
    temperature=77,                 # Required
)
Creating a ModelIsotherm#

To create a ModelIsotherm, one can either use raw similar to above, or create one from an existing PointIsotherm.

ModelIsotherm creation from raw data is almost identical to the PointIsotherm creation. The same data and parameters can be used, with a few differences:

  • model specifies which model/models to use to attempt to fit the data.

  • branch will specify which single isotherm branch (adsorption or desorption) is represented by the model. It defaults to the adsorption branch.

  • param_guess specifies the initial model parameter guesses where fitting optimisation should start. The parameter is optional, and will be automatically filled unless the user specifies it.

  • param_bounds specifies the bounds for optimisation parameters.

  • optimization_params is a dictionary which will be passed to scipy.optimise.least_squares.

  • Finally, the verbose parameter can be used to increase the amount of information printed during the model fitting procedure.

Note

The ModelIsotherm cannot be used to model tertiary data. Therefore, only loading and pressure will be saved. Any other columns in the DataFrame will be ignored.

The code to generate a ModelIsotherm is then:

model_isotherm = pygaps.ModelIsotherm(
    pressure=[1,2,3],               # pressure here
    loading=[1,2,3],                # loading here

    # Now the model details can be specified
    model='Henry',                  # Want to fit using the Henry model
    branch='ads',                   # on the adsorption branch
    param_guess={"K" : 2}           # from an initial guess of 2 for the constant
    param_bounds={"K" : [0, 20]}    # a lower bound of 0 and an upper bound of 20
    verbose='True',                 # and increased verbosity.

    # Required metadata
    material='carbon',              # Required
    adsorbate='nitrogen',           # Required
    temperature=77,                 # Required

    # Unit parameters can be specified
    pressure_mode='absolute',       # Working in absolute pressure
    pressure_unit='bar',            # with units of bar
    material_basis='mass',          # Working on a mass material basis
    material_unit='kg',             # with units of kg
    loading_basis='mass',           # Working on a loading mass basis
    loading_unit='g',               # with units of g

    # Finally some other isotherm metadata
    apparatus='X1',                 # User specific
    activation_temperature=150,     # User specific
    user='John',                    # User specific
    DOI='10.000/mydoi',             # User specific
    something='something',          # User specific
)

ModelIsotherms can also be constructed from PointIsotherms and vice-versa. The best model can also be guessed automatically. As an example:

model_isotherm = pygaps.model_iso(
    point_isotherm,                 # a PointIsotherm
    model=['Henry', 'Langmuir'],    # Try multiple models and return best fit
    verbose='True',                 # and increased verbosity.
)

For more info on isotherm modelling read the section of the manual.

Accessing isotherm data#

Once an isotherm is created, it is useful to check if it contains the correct parameters or make a plot of the isotherm. The isotherm classes can be inspected using the following functions:

  • The Python print(iso) will display all isotherm properties.

  • The iso.plot() function will display an isotherm plot (plot()).

  • The iso.print_info() function combines the two above (print_info()).

To access the isotherm data, one of several functions can be used. There are individual methods for each data type: pressure, loading and other_data. The first two are applicable to both PointIsotherms and ModelIsotherms. PointIsotherm methods return the actual discrete data, while ModelIsotherms use their internal model to generate data with the characteristics required.

All data-specific functions can return either a numpy.array object or a pandas.Series, depending on the whether the indexed parameter is False (default) or True. Other optional parameters can specify the unit, the mode/basis, the branch the data is returned from as well as a particular range for slicing data. For example:

# Will return the loading points of the adsorption part of the
# isotherm in the range if 0.5-0.9 cm3(STP)

isotherm.loading(
    branch='ads',
    loading_unit='cm3(STP)',
    limits = (0.5, 0.9),
)

The other_data function is built for accessing user-specific data stored in the isotherm object. Its use is similar to the loading and pressure functions, but the column of the DataFrame where the data is held should be specified in the function call as the key parameter. It is only applicable to the PointIsotherm object.

# Will return the enthalpy points of the desorption part of the
# isotherm in the range if 10-40 kJ/mol as an indexed
# ``pandas.Series``

isotherm.other_data(
    'enthalpy',
    branch = 'des',
    limits = (10, 40),
    indexed = True,
)

For the PointIsotherm, a special data() function returns all or parts of the internal pandas.DataFrame. This can be used to inspect the data directly or retrieve the DataFrame. To access the DataFrame directly, use the data_raw parameter.

# Will return the pandas.DataFrame in the PointIsotherm
# containing the adsorption branch

isotherm.data(branch = 'ads')

# Or access the underlying DataFrame

isotherm.data_raw

Besides functions which give access to the internal data points, the isotherm object can also return the value of pressure and loading at any point specified by the user. To differentiate them from the functions returning internal data, the functions have _at in their name.

In the ModelIsotherm class, the internal model is used to calculate the data required. In the PointIsotherm class, the functions rely on an internal interpolator, which uses the scipy.interpolate module. To optimize performance working with isotherms, the interpolator is constructed in the same units as the isotherm. If the user requests the return values in a different unit or basis, they will be converted after interpolation. If a large number of requests are to be made in a different unit or basis, it is better to first convert the entire isotherm data in the required mode using the conversion functions.

The point methods are:

The methods take parameters that describe the unit/mode of both the input parameters and the output parameters.

isotherm.loading_at(
    1,
    pressure_unit='atm',        # the pressure is passed in atmospheres (= 1 atm)
    branch='des',               # use the desorption branch of the isotherm
    loading_unit='mol',         # return the loading in mol
    material_basis='mass',      # return the adsorbent in mass basis
    material_unit='g',          # with a unit of g
)

Caution

Interpolation can be dangerous. pyGAPS does not implicitly allow interpolation outside the bounds of the data, although the user can force it to by passing an interp_fill parameter to the interpolating functions, usually if the isotherm is known to have reached the maximum adsorption plateau. Otherwise, the user is responsible for making sure the data is fit for purpose.

Converting isotherm units, modes and basis#

The PointIsotherm class also includes methods which can be used to permanently convert the internal data. This is useful in certain cases, like when you want to export the converted isotherm. To understand how units work in pyGAPS, see this section. If what is desired is instead a slice of data in a particular unit, it is easier to get it directly via the data access functions above. The conversion functions are:

  • convert() which can handle any conversion quantities.

  • convert_pressure() will permanently convert the unit or mode of pressure, for example from bar to atm.

  • convert_loading() will permanently convert the unit or basis loading of the isotherm, for example from molar in mmol to mass in g.

  • convert_material() will permanently convert the adsorbent material units or basis, for example from a mass basis in g to a mass basis in kg.

  • convert_temperature() will permanently the temperature unit.

Important

In order for pyGAPS to correctly convert between some modes and basis, the user might have to take some extra steps to provide the required information for these conversions (adsorbate molar mass for instance, which is calculated automatically for known adsorbates).

These conversion functions also reset the internal interpolator to the particular unit and basis set requested. An example of how to convert the pressure from an relative mode into an absolute mode, with units of atm:

isotherm.convert_pressure(
    mode_to='absolute',
    unit_to='atm',
)

Or a complicated conversion using the convenience function.

isotherm.convert(
    pressure_mode='absolute',
    pressure_unit='atm',
    loading_basis='fraction',
    material_basis='volume',
    material_unit='cm3',
)

Note

The ModelIsotherm model parameters cannot be converted permanently to new states (although the data can still be obtained in that state by using the data functions). For fast calculations, it is better to first convert a PointIsotherm, then re-fit the ModelIsotherm.

Converting to relative pressures#

To convert an absolute pressure in a relative pressure, the critical pressure of the gas at the experiment temperature must be known. Of course, this conversion only works when the isotherm is measured in a subcritical regime. To calculate the vapour pressure, pyGAPS relies on the CoolProp thermodynamic library. Therefore, the name of the gas in a format CoolProp understands must be passed to the CoolProp API. pyGAPS does this by having an internal list of adsorbates, which is loaded from its database at import-time. The steps are:

  • The isotherm.adsorbate is linked to a pygaps.Adsorbate class at isotherm creation.

  • User requests conversion from absolute to relative pressure for an isotherm object.

  • CoolProp backend calculates the vapour pressure (p0) for the adsorbate.

  • The relative pressure is calculated by dividing by p0.

If using common gasses, the user should not be worried about this process, as an extensive list of adsorbates is available. However, if a new adsorbate is to be used, the user might have to add it to the list themselves. For more info on this see the Adsorbate class manual.

Converting loading basis#

For loading basis conversions, the relationship between the two bases must be known. Between a mass and a volume basis, density of the adsorbent is needed and between mass and molar basis, the specific molar mass is required.

For most adsorbates, these properties are also calculated using the thermodynamic backend. The molar mass is independent of any variables, while the gas/liquid density is a function of temperature.

Converting material basis#

For the material basis, the same properties (density and molar mass) are required, depending on the conversion requested. These properties are specific to each material and cannot be calculated. Therefore, they have to be specified by the user.

Similar to the list of adsorbates described above, pyGAPS stores a list of Material objects. This is linked to the isotherm.material. To specify the properties, the user must manually set density and the molar mass for an isotherm material. For more info on this see the Material class manual.

Exporting an isotherm#

To export an isotherm, pyGAPS provides several choices to the user:

  • Converting the isotherm to an AIF format., using the isotherm_to_aif() function

  • Converting the isotherm in a JSON format, using the isotherm_to_json() function

  • Converting the isotherm to a CSV file, using the isotherm_to_csv() function

  • Converting the isotherm to an Excel file, using the isotherm_to_xl() function

  • Uploading the isotherm to a sqlite database, either using the internal database or a user-specified external one. For more info on interacting with the sqlite database see the respective section of the manual.

More info can be found on the respective parsing pages of the manual.

Ensuring isotherm uniqueness#

An interesting question is how to ensure an isotherm is unique. To this end, each Isotherm generates an id, which is an md5 hash of the isotherms parameters and data/model. The id is also used internally for database storage.

The id is generated automatically every time the isotherm.iso_id is called. The hashlib.md5 function is used to obtain a hash of the json string. It can be read as:

point_isotherm.iso_id

This means that we can perform identity checks such as:

if point_isotherm1 == point_isotherm2:
    print("same data")

if iso in list_of_isos:
    print("isotherm in collection")

Note

Both ModelIsotherm and PointIsotherm classes are supported and contain an ID.

The Adsorbate class#
Overview#

In order for many of the calculations included in pyGAPS to be performed, properties of the adsorbed phase must be known. To make the process as simple and as painless as possible, the Adsorbate class is provided.

At creation, an isotherm asks for an adsorbate parameter, a string which pyGAPS looks up in an internal list (pygaps.ADSORBATE_LIST). If any known adsorbate name/alias matches, this connects the isotherm object and the existing adsorbate class. This global list is populated as import-time with the adsorbates stored in the internal database. The user can also add their own adsorbate to the list, or upload it to the database for permanent storage.

Any calculations/conversions then rely on the Adsorbate for providing parameters such as saturation pressure, molar mass and density.

Note

For a complete list of methods and individual descriptions look at the Adsorbate class reference.

Creating an Adsorbate#

The creation process of an Adsorbate is similar to that of other pyGAPS classes. Some parameters are strictly required for instantiation, while others are recognised and can then be accessed by class members. All other parameters passed are saved as well in an internal dictionary called properties.

An example of how to create an adsorbate:

my_adsorbate = pygaps.Adsorbate(
    'butane',                         # Required
    formula = 'C4H10',                # Recognised
    alias = ['n-butane', 'Butane'],   # Recognised
    backend_name = 'butane',          # Recognised, Required for CoolProp interaction
    saturation_pressure = 2.2,        # Recognised
    carbon_number = 4,                # User specific
)

To view a summary of the adsorbate properties:

my_adsorbate.print_info()

Hint

Some properties are recognised and available from the class:

my_adsorbate.formula
>> 'C4H10'

All other custom properties are found in the Adsorbate.properties dictionary.

my_adsorbate.properties["carbon_number"]
>> 4

Note

It is unlikely that you will need to manually create an adsorbate, as more than 150 compounds are already included with pyGAPS.

Retrieving an Adsorbate#

A selection of the most common gas and vapour adsorbates is already stored in the internal database. At import-time, they are automatically loaded into memory and stored in pygaps.ADSORBATE_LIST.

len(pygaps.ADSORBATE_LIST)
>> 176

To retrieve an Adsorbate from this list, the easiest way is by using the class method: find() which works with any of the compound aliases.

# all return the same Adsorbate instance
ads = pygaps.Adsorbate.find("butane")
ads = pygaps.Adsorbate.find("n-butane")
ads = pygaps.Adsorbate.find("c4h10")
Adsorbate class methods#

The Adsorbate class has methods which allow the properties of the adsorbate to be either calculated using the CoolProp or REFPROP backend or retrieved as a string from the internal dictionary. The properties which can be calculated are:

For example, for the Adsorbate created above, to get the vapour pressure at 25 degrees in bar.

my_adsorbate.saturation_pressure(298, unit='bar')
>> 2.8

Caution

The properties calculated are only valid if the backend equation of state is usable at the required states and is accurate enough. Be aware of the limitations of CoolProp and REFPROP. More info here.

Each method also accepts a bool parameter calculate, True by default. If set to False, the property will not be calculated by the thermodynamic backend. Instead, the value from the properties dictionary will be returned. This is static and supplied by the user, but can be useful for adsorbates without a thermodynamic backend.

my_adsorbate.saturation_pressure(298, calculate=False)
>> 2.2              # Value in the `properties` dictionary

For calculations of other properties, the CoolProp backend can be accessed directly using the backend property. To calculate the reducing temperature for example.

my_adsorbate.backend.T_reducing()
Adsorbate management#

If an Adsorbate is manually created, a user can add it to the list of adsorbates by appending it.

# To store in the main list
pygaps.ADSORBATE_LIST.append(my_adsorbate)

A useful shorthand is to pass an optional parameter store at creation

# Automatically stored in ADSORBATE_LIST
ads = pygaps.Adsorbate("acetylene", store=True)

Warning

This makes the adsorbate available only in the current session. No permanent changes to the internal adsorbates are made this way.

To permanently store a custom adsorbate for later use or make modifications to exiting adsorbates, the user must upload it to the internal database. This can be done as:

import pygaps.parsing as pgp

# To permanently store in the database
pgp.adsorbate_to_db(ads_new)

# To store any modifications to an adsorbate in the database
pgp.adsorbate_to_db(ads_modified, overwrite=True)

For more info, check out the sqlite section of the manual.

The Material class#
Overview#

Similarly to Adsorbate, a Material is a helper wrapper class around pyGAPS concepts. The user might want to store details about adsorbent materials they use. The information can range from date of synthesis, material density, etc. For this case, pyGAPS provides the Material class.

The isotherm required property of material is used to create or connect a BaseIsotherm instance to a Material. Each time an isotherm is created, pyGAPS looks in the main material list (pygaps.MATERIAL_LIST) for an instance with the same name. This list is populated as import-time with materials stored in the internal database. The user can also add their own material to the list, or upload it to the database for permanent storage. If the material does not exist in pygaps.MATERIAL_LIST, pyGAPS will create a new instance for the isotherm.

Any calculations/conversions then rely on the Material for providing parameters such as material molar mass and density.

Note

For a complete list of methods and individual descriptions look at the Material reference.

Creating a Material#

To create an instance of a Material, parameters must contain a value for the material name, with anything else being optional. Some are recognised as special properties (density, ...), while other parameters passed are saved as well in an internal dictionary called properties.

An example of how to create a material:

my_material = pygaps.Material(
    'carbon',                   # Name
    density=1,                  # Recognised
    molar_mass=256,             # Recognised
    batch='X1',                 # User specific
    owner='Test User',          # User specific
    form='powder',              # User specific
    treatment='acid etching'    # User specific
)

To view a summary of the material properties:

my_material.print_info()

Hint

All custom properties are found in the Material.properties dictionary.

my_material.properties["form"]
>> "powder"
Material class methods#

The Material class has some specific methods which denote recognised properties:

These are not calculated, just looked up in the properties dictionary.

my_material.molar_mass()
>> 256
Material management#

In pyGAPS, materials can be stored in the internal sqlite database. At import-time, the list of all materials is automatically loaded into memory and stored in pygaps.MATERIAL_LIST. The easiest way to retrieve a material from the list is to use the find() class method. It takes the material name as parameter.

carbon = pygaps.Material.find('carbon')

At first the database will be empty. To populate the database with materials, the user should create the materials first and then append them to the list for temporary storage, or upload them to the database for permanent storage.

# To store in the main list
pyGAPS.MATERIAL_LIST.append(my_material)

A useful shorthand is to pass an optional parameter store at creation

# Automatically stored in MATERIAL_LIST
mat = pygaps.Material("MOF", store=True)

Warning

This makes the material available only in the current session. No permanent changes to the materials are made this way.

To permanently store a custom material for later use or make modifications to exiting materials, the user must upload it to the internal database. This can be done as:

import pygaps.parsing as pgp

# To permanently store in the database
pgp.material_to_db(mat_new)

# To store any modifications to an material in the database
pgp.material_to_db(mat_modified, overwrite=True)

For more info, check out the sqlite section of the manual.

Important secondary concepts#

Units in pyGAPS#
Overview#

When computers work with physical data, units are always a variable that introduces confusion. This page attempts to explain how pyGAPS handles units and other such real world concepts such as relative pressure and mass or volume basis.

Units can be specified for the following isotherm properties:

An explanation follows of the concepts follows.

Pressure#

Pressure is commonly represented as an absolute value. in units such as bar, atm, Pa, etc. This is known as the absolute pressure mode. You can convert freely between different units:

# convert to torr
isotherm.convert_pressure(
    unit_to='torr',
)

Another common option in adsorption is having the pressure represented relative to the saturation pressure of the adsorbate, or relative pressure mode. In precise terms, relative pressure is a dimensionless value which is obtained by dividing absolute pressure (\(p\)) by the saturation / vapour pressure of the adsorbate at the measurement temperature (\(p^0_T\)). For its calculation, pyGAPS uses equations of state from a thermodynamic backend like CoolProp or REFPROP. More info here.

Note

Relative pressure only has meaning when the isotherm is recorded in a sub-critical regime.

Some example isotherm conversions with different pressure modes / units:

# relative pressure mode
isotherm.convert_pressure(
    mode_to='relative',
)

# or to relative percent mode
isotherm.convert_pressure(
    mode_to='relative%',
)

# absolute pressure mode
# unit must be specified here
isotherm.convert_pressure(
    mode_to='absolute',
    unit_to='torr',
)

Internally, pressure conversions are handled by the c_pressure() function.

Adsorbate loading#

Adsorbate loading refers to the quantity of gas (adsorbate) which is contained in the material on which the isotherm is measured i.e. "moles of nitrogen adsorbed" or "grams of CO2 captured" or "percent weight adsorbed".

Note

Currently pyGAPS does not differentiate between excess and absolute amount adsorbed.

The adsorbate loading is usually given in mmol or cm3(STP), both of which are representations of a molar basis. Sometimes it is useful if, instead of a molar basis, loading is represented in terms on a mass basis or volume basis. It is also possible to represent uptake as a fraction or percent of the material basis.

For these conversions, properties such as molar mass and density of the adsorbate are required. This info is obtained automatically using an equation of state from either CoolProp or REFPROP.

Note

The thermodynamic backends cannot predict properties outside in the critical adsorbate regime.

Examples of isotherm conversion on loading:

# to a mass basis
isotherm.convert_loading(
    basis_to='mass',
    unit_to='g',
)

# to percentage
isotherm.convert_loading(
    basis_to='percent',
)

Internally, loading conversions are handled by the c_loading() function.

Material quantity#

Material quantity refers to the amount of material that the adsorption takes place on, i.e. amount adsorbed "per gram of carbon" or "per centimetre cube of zeolite" or "per mole of MOF". The scientific community regularly uses a mass basis, while a volumetric basis is more important in industry where adsorbent bed design sizing is required. pyGAPS allows the basis to be changed to either mass, volume or molar.

Depending on the conversion, the density or molar mass of the material is needed and should be provided by the user. To specify this in a material see below and, check out the Material manual.

Example of isotherm conversion on material:

# must be specified, in g/cm3
isotherm.material.properties['density'] = 2

# now conversion is possible
isotherm.convert_material(
    basis_to='volume',
    unit_to='cm3',
)

Internally, material conversions are handled by the c_material().

Temperature#

For convenience, isotherm temperatures can also be converted between Kelvin or Celsius. This is done as:

isotherm.convert_temperature(unit_to='°C')
How units impact characterisation and modelling#

Most characterisation methods automatically take the required form of the units without the user having to convert it beforehand. Therefore, if for example the area_bet() function is called, the conversion will be made automatically in order to return the surface area in square metres.

Warning

The basis of the material is unchanged however. Therefore, if the isotherm was in a volume basis with units of cm3 before the calculation above, the returned surface area will be in square meters per cubic centimetre of material.

Low-level convert#

The way units are converted under the hood is through the use of dictionaries that store conversion factors between the different unit types. The user can use the functions directly by importing the pygaps.units.converter_mode and pygaps.units.converter_unit modules.

An example pressure conversion:

from pygaps.units.converter_mode import c_pressure

converted = c_pressure(
    1,
    mode_from='absolute',
    unit_from='bar',
    mode_to='absolute',
    unit_to='Pa',
)

An example loading conversion:

from pygaps.units.converter_mode import c_loading

converted = c_loading(
    1,
    mode_from='molar',
    unit_from='mol',
    mode_to='mass',
    unit_to='mg',
)

An example material conversion:

from pygaps.units.converter_mode import c_material

converted = c_material(
    1,
    mode_from='mass',
    unit_from='g',
    mode_to='volume',
    unit_to='cm3',
)
High-level convert#

In regular usage, the framework handles units for the user, with no need to use the low-level functions. At isotherm creation, the units can be specified through the use of keywords.

In order to access the data in a different unit than specified at instantiation, most methods can accept the same keywords.

The isotherm internal data can also be permanently converted into another unit, pressure mode or basis. This is not normally required, but can be done if the isotherm is to be exported in different units. To do this, check out this section of the manual.

Equations of state#

In order to calculate adsorbate properties such as molar mass, vapour pressure or surface tension, pyGAPS makes use of CoolProp or REFPROP. This thermodynamic backend allows for the calculation of multiple fluid state variables.

Note

Not all adsorbates have a thermodynamic backend known to CoolProp. See here for the list of fluids. If not available, the user can provide their own static values for required properties like so.

CoolProp has the ability to use either the open source HEOS or the proprietary REFPROP backend. pyGAPS defaults to using the HEOS backend, but it the user has REFPROP installed and configured on their computer, they can enable it by using the switching function:

pygaps.backend_use_refprop()

To go back to the standard CoolProp backend, use:

pygaps.backend_use_coolprop()

Warning

If REFPROP is not previously installed and configured on the user's computer, calculations will fail.

Functionality#

Data import and export#
Overview#

Besides creating an isotherm from raw data, explained in detail in this section of the manual, there are other options on how to import or export isotherms.

  • Imported from one of many device manufacturer formats. See parsing manufacturer files.

  • From an internal database: pyGAPS contains functionality to store and retrieve constructed isotherms in an sqlite database. See database.

Note

Most functions can import/export both a PointIsotherm and a ModelIsotherm.

JSON parsing#

Importing and exporting isotherms in a JSON format is a great alternative to a CSV or Excel files and is the recommended pyGAPS way of sharing isotherms. The JSON format has several advantages, such as being a web standard, and near-universal parsing capabilities, not to mention the ease of extensibility afforded by the structure. An example JSON isotherm can be found here.

Caution

The JSON format is, by definition, unsorted. Therefore, even though pyGAPS sorts the keys alphabetically before returning the string, one should not rely on their order.

The framework provides two functions for JSON parsing:

Assuming we have an isotherm which was previously created, use the following code to convert it to a JSON string or file.

import pygaps.parsing as pgp

# to a string
json_string = pgp.isotherm_to_json(my_isotherm)

# to a file
pgp.isotherm_to_json(my_isotherm, 'path/to/file.json')

# or for convenience
my_isotherm.to_json('path/to/file.json')

To parse JSON into an isotherm, use the from function.

import pygaps.parsing as pgp
my_isotherm = pgp.isotherm_from_json(json_string_or_path)

For detailed information about JSON parsing functions, check out the json module reference.

AIF parsing#

The Adsorption Information File (AIF) is a recently developed file format [1], analogous to a Crystallographic Information File (CIF) file. It is meant to be an extensible text file, comprising of isotherm points and extensive metadata which facilitate the standardisation, sharing and publication of isotherms. An example AIF can be downloaded here.

pyGAPS can import and export AIF through:

Assuming we have an isotherm which was previously created, use the following code to convert it to a AIF string or file.

import pygaps.parsing as pgp

# to a string
aif_string = pgp.isotherm_to_aif(my_isotherm)

# to a file
pgp.isotherm_to_aif(my_isotherm, 'path/to/file.aif')

# or for convenience
my_isotherm.to_aif('path/to/file.aif')

To parse an AIF file as an isotherm, use the from function.

import pygaps.parsing as pgp
my_isotherm = pgp.isotherm_from_aif(aif_string_or_path)

For more info about AIF parsing, check out the aif module reference.

CSV parsing#

CSV files can also be used as a convenient storage for isotherms. However, the format is not as flexible as the alternatives. The CSV files created will have all the isotherm metadata as key-value pairs, followed by a section which includes the data or model of the isotherm. An example CSV isotherm can be found here.

The main functions pertaining to CSV parsing are:

Assuming we have an isotherm which was previously created, use the following code to convert it to a CSV string or file.

import pygaps.parsing as pgp

# to a string
csv_string = pgp.isotherm_to_csv(my_isotherm)

# to a file
pgp.isotherm_to_csv(my_isotherm, 'path/to/file.csv')

# or for convenience
my_isotherm.to_csv('path/to/file.csv')

To parse CSV into an isotherm, use the from function.

import pygaps.parsing as pgp
my_isotherm = pgp.isotherm_from_csv(csv_string_or_path)

For more info about CSV parsing, check out the csv module reference.

Excel parsing#

The isotherms can also be imported or exported in an Excel format, if required. This is done with the help of the xlrd/xlwt python packages. An example excel isotherm can be found here.

The framework provides two functions for Excel files:

To export an isotherm to an Excel file, pass the isotherm object, as well as the path where the file should be created.

import pygaps.parsing as pgp

# export the isotherm
pgp.isotherm_to_xl(my_isotherm, 'path/to/file.xls')

# or for convenience
my_isotherm.to_xl('path/to/file.xls')

To parse an Excel file as an isotherm, use the from function.

import pygaps.parsing as pgp
my_isotherm = pgp.isotherm_from_xl('path/to/file.xls')

For more info about Excel parsing, check out the excel module reference.

Manufacturer-specific parsing#

Most commercial adsorption apparatus can output the recorded isotherm as an Excel (.xls/.xlsx), a CSV (.csv) or a text file. Many of these can be imported using the isotherm_from_commercial() function.

Currently pyGAPS includes functionality to import:

  • SMS DVS .xlsx files: iso = isotherm_from_commercial(path, "smsdvs", "xlsx")

  • Microtrac BEL .dat files: iso = isotherm_from_commercial(path, "bel", "dat")

  • Microtrac BEL .xls files: iso = isotherm_from_commercial(path, "bel", "xl")

  • Microtrac BEL .csv files: iso = isotherm_from_commercial(path, "bel", "csv")

  • Micromeritics .xls files: iso = isotherm_from_commercial(path, "mic", "xl")

  • 3P .xlsx report files: iso = isotherm_from_commercial(path, "3p", "xl")

  • Quantachrome Raw Isotherm .txt files: iso = isotherm_from_commercial(path, "qnt", "txt-raw")

Isotherms from the NIST ISODB#

The NIST ISODB is a database of published adsorption isotherms. pyGAPS can pull a specific isotherm from the NIST ISODB by using the isotherm_from_isodb() function. The ISODB isotherm filename should be specified as a parameter.

import pygaps.parsing as pgp
isotherm = pgp.isotherm_from_isodb('10.1002adfm.201200084.Isotherm3')

Caution

This functionality relies on public APIs from NIST. No guarantee can be made regarding future availability.

Sqlite parsing#

pyGAPS includes an internal sqlite database where Isotherms can be saved for later use, as well as samples, adsorbates, etc. The database functionality is an extensive part of the framework, and it has its own section of the manual.

Adsorbent material characterisation#
Overview#

One of the main features of the pyGAPS framework is to allow standard isotherm characterisation techniques to be carried out in bulk for high throughput testing, as well as to disconnect adsorption data processing from the machine used to record it.

The framework currently provides the following functionality for material characterisation:

More info about each function and its usage can be found on their respective page.

Caution

Before using the provided characterisation functions, make sure you are aware of how units work and how the backend calculates adsorbate properties.

Characterisation examples#

The best way to get familiarized with characterization functions is to check out the Jupyter notebooks in the examples section.

Isotherm model fitting#
Overview#

In adsorption, a model is a physical or empirical relationship between bulk phase adsorbate pressure and the amount adsorbed on the material, or loading. Therefore it can be expressed as a function:

\[ \begin{align}\begin{aligned}n = f(p, ...)\\or\\p = f(n, ...)\end{aligned}\end{align} \]

Many types of theoretical models have been developed which attempt to describe the phenomenon of adsorption. While none can accurately describe all situations, different behaviours, interactions and pressure ranges, experimental data can be often be reliably fitted to a suitable model.

Caution

It is left to the best judgement of the user when to apply a specific model.

Modelling in pyGAPS#

In pyGAPS, the ModelIsotherm() is the class which contains a model and other needed metadata. While it is instantiated using discrete data, it does not store it directly. Another principal difference from the PointIsotherm class is that, while the former can contain both the adsorption and desorption branch of the physical isotherm, the latter contains a model for only one branch, determined at initialisation.

Currently the models implemented are:

For an explanation of each model, visit its respective reference page. Custom models can also be added to the list if you are willing to write them. See the procedure below.

Working with models#

A ModelIsotherm can be created from raw values, as detailed in the isotherms section. However, for most use case scenarios, the user will want to create a ModelIsotherm starting from a previously created PointIsotherm class.

To do so, the class includes a specific class method, from_pointisotherm(), which allows a PointIsotherm to be used. Alternatively, a utility function pygaps.modelling.model_iso is provided. An example is:

model_isotherm = pygaps.ModelIsotherm.from_pointisotherm(
    point_isotherm,
    branch='ads'
    model='Henry',
)
# or using the convenience function
import pygaps.modelling as pgm
model_isotherm = pgm.model_iso(
    point_isotherm,
    branch='ads'
    model='Henry',
)

Alternatively, a list of model names can be passed that will be fitted sequentially, returning the model with the best RMSE fit. If model='guess', pyGAPS will attempt to fit some of the common models.

Caution

This mode should be used carefully, as there's no guarantee that the the best fitting model is the one with any physical significance. It it also worth noting that, since a lot of models may be evaluated, this option will take significantly more resources than simply specifying the model manually.

As a consequence, some models which require a lot of overhead, such as the virial model, have been excluded from this option.

import pygaps.modelling as pgm

# Attempting all basic models
model_isotherm = pgm.model_iso(
    point_isotherm,
    branch='des',
    model='guess',
)

# With a subset of models instead
model_isotherm = pgm.model_iso(
    point_isotherm,
    branch='des',
    model=['Henry', 'Langmuir', 'BET', 'Virial'],
)

Once the a ModelIsotherm is generated, it can be used as a regular PointIsotherm, as it contains the same common methods. Some slight differences exist:

  • ModelIsotherm``s do not contain the ``isotherm.data() method, as they contain no data. Instead the user can access the isotherm.model.params property, to get a dictionary of the calculated model parameters.

  • The isotherm.loading() and isotherm.pressure() functions will return equidistant points over the whole range of the isotherm instead of returning actual datapoints.

  • Some models calculate pressure as a function of loading, others calculate loading as a function of pressure. If the model function cannot be inverted, the requested data will have to be computed using numerical methods. Depending on the model, the minimisation may or may not converge.

Comparing models and data#

ModelIsotherms can easily be plotted using the same function as PointIsotherms. For example, to graphically compare a model and an experimental isotherm:

import pygaps.graphing as pgg
pgg.plot_iso([model_isotherm, point_isotherm])

One may notice that the loading is calculated at different pressure points from the PointIsotherm. This is done to keep the plotting function general. If the user wants the pressure points to be identical one can pass the pressure or loading points in the plotting function as the x_points and y1_points, respectively.

pgg.plot_iso(
    [model_isotherm, point_isotherm],
    x_points=point_isotherm.loading(),
)
Turning a model to points#

Sometimes, a user might want to generate a PointIsotherm based on a model. A class method from_modelisotherm() is provided for this purpose. The function method takes as parameters a ModelIsotherm, and a pressure_points keyword. This can be used to specify the array of points where the loading is calculated. If a PointIsotherm is passed instead, the loading is calculated at each of the points of this isotherm.

# Create a PointIsotherm from the model
new_point_isotherm = pygaps.PointIsotherm.from_modelisotherm(
    model_isotherm,
    pressure_points=[1,2,3,4],
)

# Use a previous PointIsotherm as reference
new_point_isotherm = pygaps.PointIsotherm.from_modelisotherm(
    model_isotherm,
    pressure_points=point_isotherm,
)
Modelling examples#

Check out the Jupyter notebook in the examples section

Custom models#

Custom models can be implemented. In the pygaps/modelling/base_model.py folder, there is a model template (IsothermBaseModel) which contains the functions which should be inherited by a custom model.

The parameters to be specified are the following:

  • The model name name and whether it calculates pressure or loading calculates.

  • Dictionaries with the model parameters names param_names and possible bounds param_bounds.

  • A function that returns an initial guess for the model parameters (initial_guess()).

  • A fitting function that determines the model parameters starting from the loading and pressure data (fit()). Alternatively, the template fitting function can be used directly if suitable.

  • Functions that return the loading and pressure calculated from the model parameters (loading(pressure) and pressure(loading)). These can be calculated analytically or numerically.

  • A function which returns the spreading pressure, if the model is to be used for IAST calculations (spreading_pressure(pressure)).

Once the model is written, it should be added to the list of usable models. This can be found in the pygaps/modelling/__init__.py file.

Don't forget to write some tests to make sure that the model works as intended. You can find the current parametrised tests in tests/modelling/test_models_isotherm.py.

Ideal Adsorbed Solution Theory#
Overview#

Adsorption behaviours of gas mixtures can be predicted from pure component isotherms by using the Ideal Adsorbed Solution Theory (IAST).

The main IAST code was written by Cory Simon [1], and was then incorporated in pyGAPS. A very good explanation of the method, complete with use cases and recommendations can still be found on the pyIAST documentation

With the inclusion of the source code, several changes have been introduced, to improve the usability of the method and to conform it to the rest of the code. A tutorial of IAST within pyGAPS follows.

IAST calculations in pyGAPS#

To use the IAST functionality, a list of pure component isotherms is needed. The isotherms can be either:

  • A ModelIsotherm, where the model will be used for the calculation of spreading pressure. Some models cannot be used for IAST calculations.

  • A PointIsotherm, where the spreading pressure calculation will use interpolated data.

The original pyIAST functions still exist, as iast_point() and reverse_iast(). They can be used to determine the adsorbed fraction of each adsorbate given their partial pressures, or vice-versa.

To use:

import pygaps.iast as pgi
iast_loadings = pgi.iast_point(
    isotherms=[iso1, iso2, iso3],
    partial_pressures=[0.1, 1.0, 2.3],
)

Since IAST is often used for binary mixture adsorption prediction, several new functions have been introduced which make it easier to do common calculations and generate graphs:

  • iast_point_fraction() is a version of IAST requiring bulk fluid fractions and total pressure instead of partial pressures for each component.

    import pygaps.iast as pgi
    
    result_dict = pgi.iast_point_fraction(
        isotherms=[iso1, iso2, iso3],
        gas_mole_fraction=[0.1, 0.5, 0.4],
        total_pressure=2,
    )
    
  • iast_binary_svp() is a function to calculate the selectivity of a known composition mixture as a function of pressure. This example will plot selectivities over a pressure range of 0.01 to 10 of an equimolar mixture of methane and ethane:

    import numpy
    import pygaps.iast as pgi
    
    result_dict = pgi.iast_binary_svp(
        isotherms=[ch4, c2h6],
        mole_fractions=[0.5, 0.5],
        pressures=numpy.linspace(0.01, 10, 30),
    )
    
  • iast_binary_vle() is a function to calculate the gas-adsorbed equilibrium at a constant pressure, over the entire range of molar fractions. This example will plot the gas-adsorbed equilibrium for all molar fractions of methane in ethane at a pressure of 2 bar:

    import pygaps.iast as pgi
    
    result_dict = pgi.iast_binary_vle(
        isotherms=[ch4, c2h6],
        total_pressure=2,
    )
    
IAST examples#

Check out the Jupyter notebook in the examples section for a demonstration.

Plotting#
Overview#

While the user can of course take the isotherm data and generate their own customised plots, pyGAPS includes a few plotting functions which make it easier for standard plots to be generated.

The main plotting tool is in the plot_iso() function, which handles the plotting of all isotherm types. Some common use-case scenarios for the functionality are:

  • Visualising the data after isotherm instantiation.

  • Quickly comparing several isotherms.

  • Checking the overlap of a model isotherm and the point data.

  • Generating graphs for a publication.

The function can take many parameters which will modify the graph style, colours etc. The function also accepts keywords to specify the unit, pressure mode and basis of the graphs. A complete list of parameters can be found in the reference.

The function also returns the matplotlib.Axes, to allow for further customisation for the resulting plot.

Examples#

Check out the Jupyter notebook in the examples section.

Database#
Overview#

The framework provides capabilities to interact with an sqlite database, in order to store objects such as PointIsotherm, ModelIsotherm, Adsorbate, Material.

The database was initially envisioned as a centralised data storage for the MADIREL Laboratory in Marseille. To generalize the concept for this framework, the database has been simplified to what the authors consider bare bones, but still extensible, functionality. Suggests for further improvements are welcome.

Note

For most purposes, the internal database is adequate for storage and retrieval. However, it can be difficult to locate it on the disk, and, depending on the amount of data it stores, it can grow to a considerable size. Consider using a separate database file.

Database structure#

A diagram of the database schema can be seen below:

Database schema.

The database is configured to use foreign keys, in order to prevent data redundancy and to enforce error checking. This will make sure that no stored isotherm has an unknown or misspelled adsorbate but it also means that some groundwork is required before uploading the first isotherm.

Database methods#

All the functions which interact with a database take the database path as their first argument. If the internal database is to be used, the parameter passed should be the pygaps.DATABASE reference. A complete list of methods can be found in the sqlite reference.

Database example#

Check out the Jupyter notebook in the examples section

Blank database creation#

The internal database is already created and usable when pyGAPS is installed. If the user decides to have an external database, they can either copy the internal database (located in the pygaps/database directory) or generate an empty one using the db_create command.

from pygaps.utilities.sqlite_db_creator import db_create
db_create("path/to/database")

Examples#

The examples in this section are actually in the form of Jupyter Notebooks which are turned into webpages with nbsphinx. You can find them for download in the github folder.

This page was generated from docs/examples/import.ipynb. To start an interactive version: Binder badge

Reading isotherms#

The first thing to do is to read previously created isotherms. Example data can be found in the data directory, saved in the pyGAPS JSON format, which we will now open. First, we'll do the necessary top-level imports for the session.

[1]:
from pathlib import Path
import pygaps.parsing as pgp

json_path = Path.cwd() / 'data'

Then we'll import the json files, by using the isotherm_from_json method which reads an isotherm from a file (or a string). There are four folders:

  • One containing nitrogen adsorption data at 77 kelvin

[2]:
# Get the nitrogen data at 77 kelvin
isotherms_n2_77k_paths = Path(json_path / 'characterisation').rglob("*.json")
isotherms_n2_77k = [pgp.isotherm_from_json(filepath) for filepath in isotherms_n2_77k_paths]
print('Selected', len(isotherms_n2_77k), 'isotherms with nitrogen at 77K')
Selected 5 isotherms with nitrogen at 77K
  • Another with room-temperature adsorption of \(CO_2\) combined with microcalorimetry

[3]:
# Get the combined isotherm-calorimetry data
isotherms_calorimetry_paths = Path(json_path / 'calorimetry').rglob("*.json")
isotherms_calorimetry = [
    pgp.isotherm_from_json(filepath) for filepath in isotherms_calorimetry_paths
]
print('Selected', len(isotherms_calorimetry), 'room temperature calorimetry isotherms')
Selected 2 room temperature calorimetry isotherms
  • Some room-temperature isotherms which we will use for IAST calculations

[4]:
# Get the isotherms for IAST calculations
isotherms_iast_paths = Path(json_path / 'iast').rglob("*.json")
isotherms_iast = [pgp.isotherm_from_json(filepath) for filepath in isotherms_iast_paths]
print('Selected', len(isotherms_iast), 'isotherms for IAST calculation')
Selected 2 isotherms for IAST calculation
  • A set of isotherms with \(C_4H_{10}\) at different temperature, for isosteric enthalpy calculations

[5]:
# Get the isotherms for isosteric enthalpy calculations
isotherms_isosteric_paths = list(Path(json_path / 'enth_isosteric').rglob("*.json"))
isotherms_isosteric = [pgp.isotherm_from_json(filepath) for filepath in isotherms_isosteric_paths]
print('Selected', len(isotherms_isosteric), 'isotherms for isosteric enthalpy calculation')
Selected 3 isotherms for isosteric enthalpy calculation
  • Isotherms used for Whittaker enthalpy calculations

[ ]:
isotherms_enth_whittaker_paths = list(Path(json_path / 'enth_whittaker').rglob("*.aiff"))
isotherms_enth_whittaker = [
    pgp.isotherm_from_aif(filepath) for filepath in isotherms_enth_whittaker_paths
]
This page was generated from docs/examples/inspection.ipynb. To start an interactive version: Binder badge

General isotherm info#

Before we start the characterisation, let's have a cursory look at the isotherms. First, make sure the data is imported by running the previous notebook.

[1]:
# import isotherms
%run import.ipynb
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation

We know that some of the isotherms are measured with nitrogen at 77 kelvin, but don't know what the samples are. If we evaluate an isotherm, its key details are automatically displayed.

[2]:
isotherms_n2_77k
[
    <PointIsotherm 5b36aabf643c8557893f1a82aeedffaa>: 'nitrogen' on 'MCM-41' at 77.355 K,
    <PointIsotherm e0590c7a3dae0ea88d7328c2c1a31fed>: 'nitrogen' on 'NaY' at 77.355 K,
    <PointIsotherm 25abc19b921164e130f13e0126763853>: 'nitrogen' on 'SiO2' at 77.355 K,
    <PointIsotherm b72b8b4b525dc901d2977ad577637462>: 'nitrogen' on 'Takeda 5A' at 77.355 K,
    <PointIsotherm 5133355996d3f32458515fd1cf492813>: 'nitrogen' on 'UiO-66(Zr)' at 77.355 K
]

So we have a mesoporous templated silica, a zeolite, some amorphous silica, a microporous carbon and a common MOF.

What about the isotherms which we'll use for isosteric calculations? Let's see what is the sample and what temperatures they are recorded at. We can use the standard print method on an isotherm for some detailed info.

[3]:
print(isotherms_isosteric[0])
print([isotherm.temperature for isotherm in isotherms_isosteric])
Material: TEST
Adsorbate: n-butane
Temperature: 298.15K
Units:
        Uptake in: mmol/g
        Pressure in: bar
Other properties:
        iso_type: isotherm
        material_batch: TB

[298.15, 323.15, 348.15]

Let's look at the isotherms that were measured in a combination with microcalorimetry. Besides the loading and pressure points, these isotherms also have a differential enthalpy of adsorption measured for each point. We can use the isotherm.print_info function, which also outputs a graph of the isotherm besides its properties.

[4]:
# The second axis range limits are given to the print function
isotherms_calorimetry[1].print_info(y2_range=(0,60))
Material: Takeda 5A
Adsorbate: carbon dioxide
Temperature: 303.0K
Units:
        Uptake in: mmol/g
        Pressure in: bar
Other properties:
        iso_type: Calorimetrie
        lab: MADIREL
        instrument: CV
        material_batch: Test
        activation_temperature: 150.0
        user: ADW

(
    <AxesSubplot:xlabel='Pressure [$bar$]', ylabel='Loading [$mmol\\/g^{-1}$]'>,
    <AxesSubplot:ylabel='$\\Delta_{ads}h$ $(-kJ\\/mol^{-1})$'>
)
_images/examples_inspection_7_2.png

For the isotherms which are to be used for IAST calculations, we'd like to plot them on the same graph, with the name of the adsorbate in the legend. For this we can use the more general pygaps.graphing.plot_iso function.

[5]:
# import the characterisation module
import pygaps.graphing as pgg

pgg.plot_iso(
    isotherms_iast,                         # the isotherms
    branch='ads',                           # only the adsorption branch
    lgd_keys=['material','adsorbate'],      # the isotherm properties making up the legend
)
<AxesSubplot:xlabel='Pressure [$bar$]', ylabel='Loading [$mmol\\/g^{-1}$]'>
_images/examples_inspection_9_1.png

Material characterisation#

This section will have several examples of porous material characterisation that can be performed using pyGAPS. A complete applicability guide and info on each function parameters can be found in the reference manual.

This page was generated from docs/examples/area_calcs.ipynb. To start an interactive version: Binder badge
Specific surface area determination#
BET surface area#

Let's do a calculation of the BET area for these samples. First, make sure the data is imported by running the previous notebook.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation

pyGAPS attempts to calculate the applicable BET region on its own by using the Rouquerol rules. The function is pygaps.characterisation.area_BET, where we set the verbose parameter to obtain a printed output of results and plots.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'MCM-41')
print(isotherm.material)
results = pgc.area_BET(isotherm, verbose=True)
MCM-41
BET area: a = 358.5 m2/g
The BET constant is: C = 129.2
Minimum pressure point is 0.0337 and maximum is 0.286
Statistical monolayer at: n = 0.00368 mol/g
The slope of the BET fit: s = 270
The intercept of the BET fit: i = 2.11

_images/examples_area_calcs_3_1.png
_images/examples_area_calcs_3_2.png

It looks that the correlation is reasonably good. A warning is emitted if this is not the case. We can also restrict the pressure range manually to see what difference it would make, by using the limits parameter.

[3]:
results = pgc.area_BET(isotherm, p_limits=(0.05, 0.2), verbose=True)
BET area: a = 361.7 m2/g
The BET constant is: C = 111.3
Minimum pressure point is 0.0513 and maximum is 0.194
Statistical monolayer at: n = 0.00371 mol/g
The slope of the BET fit: s = 267
The intercept of the BET fit: i = 2.42

_images/examples_area_calcs_5_1.png
_images/examples_area_calcs_5_2.png

Now let's do the analysis on all of the nitrogen samples. We'll assume the framework makes a reasonably accurate choice for the applicable range. The function returns a dictionary with all the calculated parameters, and we'll print the BET area from there.

[4]:
results = []
for isotherm in isotherms_n2_77k:
    results.append((isotherm.material, pgc.area_BET(isotherm)))

[(x, f"{y['area']:.2f}") for (x, y) in results]
[
    (<pygaps.Material 'MCM-41'>, '358.53'),
    (<pygaps.Material 'NaY'>, '699.23'),
    (<pygaps.Material 'SiO2'>, '210.39'),
    (<pygaps.Material 'Takeda 5A'>, '1109.80'),
    (<pygaps.Material 'UiO-66(Zr)'>, '1275.36')
]

We also have isotherms which were measured with \(CO_2\) at room temperature. While there's no guarantee that the BET method is still applicable with this adsorbate and temperature, we can still attempt to perform the calculations.

It just happens that the isotherms were recorded on the same Takeda 5A carbon sample (see \(N_2\) results above). Let's see how the \(CO_2\) BET surface area looks in comparison.

[5]:
isotherm = next(i for i in isotherms_calorimetry if i.material == 'Takeda 5A')
print(isotherm.material)
results = pgc.area_BET(isotherm, verbose=True)
Takeda 5A
BET area: a = 739.1 m2/g
The BET constant is: C = 31.7
Minimum pressure point is 0.0369 and maximum is 0.285
Statistical monolayer at: n = 0.00722 mol/g
The slope of the BET fit: s = 134
The intercept of the BET fit: i = 4.36

_images/examples_area_calcs_9_1.png
_images/examples_area_calcs_9_2.png

The surface area obtained with carbon dioxide is around 740 \(m^2\). Compared to the nitrogen surface area of 1100 \(m^2\), it is much smaller. While the checks implemented did not find anything wrong, this is likely due to interactions between carbon dioxide and the carbon surface leading to the breakdown of the BET theory.

While any kind of adsorbate and temperature (below critical) can be used, result interpretation is left at the discretion of the user. More info can be found in the documentation of the module.

Langmuir surface area#

Another common method of calculating specific surface area relies on fitting the isotherm with a Langmuir model. This model assumes adsorption occurs on active surface/pore sites, in a single layer. We use the pygaps.characterisation.area_langmuir function, which defaults to a region of 0.1-0.9 p/p0 for the fitting (see documentation for details). We pass the verbose parameter to display the results automatically.

[6]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'MCM-41')
print(isotherm.material)
results = pgc.area_langmuir(isotherm, verbose=True)
MCM-41
The correlation is not linear.
Langmuir area: a = 1409 m2/g
Minimum pressure point is 0.0513 and maximum is 0.859
The Langmuir constant is: K = 2.72
Amount Langmuir monolayer is: n = 0.0144 mol/g
The slope of the Langmuir fit: s = 69.2
The intercept of the Langmuir fit: i = 25.4

_images/examples_area_calcs_12_1.png

The correlation is not very good due to condensation in mesopores of MCM-41, which is not predicted by the Langmuir model. Due to this, the area calculated is not realistic. We can manually select a range in the monolayer adsorption regime for a better fit.

[7]:
print(isotherm.material)
results = pgc.area_langmuir(isotherm, p_limits=(0.05, 0.3), verbose=True)
MCM-41
Langmuir area: a = 550.1 m2/g
Minimum pressure point is 0.0513 and maximum is 0.286
The Langmuir constant is: K = 21.5
Amount Langmuir monolayer is: n = 0.00564 mol/g
The slope of the Langmuir fit: s = 177
The intercept of the Langmuir fit: i = 8.24

_images/examples_area_calcs_14_1.png

The fit is now better and the calculated area is also realistic. Comparing it to the BET area obtained previously, we see that it is higher by about 150 m2. Since adsorption is more likely to occur in monolayers until pore condensation, this method is not really suited for MCM-41. In general the Langmuir surface area is not as widely applicable as the BET one.

Now let's do the analysis on all of the nitrogen samples and compare the obtained surface areas with the BET ones.

[8]:
import matplotlib.pyplot as plt

area_langmuir = []
area_langmuir_lim = []
area_bet = []
for isotherm in isotherms_n2_77k:
    area_bet.append(pgc.area_BET(isotherm)['area'])
    area_langmuir.append(pgc.area_langmuir(isotherm)['area'])
    area_langmuir_lim.append(pgc.area_langmuir(isotherm, p_limits=(0.01, 0.3))['area'])

fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 5))
ax1.scatter(area_langmuir, area_bet)
ax2.scatter(area_langmuir_lim, area_bet)

ax1.set_title('BET v. Langmuir area, full range')
ax2.set_title('BET v. Langmuir area, LP range')

ax1.plot([0, 2000], [0, 2000], 'k--')
ax2.plot([0, 2000], [0, 2000], 'k--')

ax1.set_xlim(left=0, right=2000)
ax1.set_ylim(bottom=0, top=2000)
ax2.set_xlim(left=0, right=2000)
ax2.set_ylim(bottom=0, top=2000)

ax1.set_xlabel('Langmuir surface area [m2/g]')
ax1.set_ylabel('BET surface area [m2/g]')
ax2.set_xlabel('Langmuir surface area [m2/g]')
ax2.set_ylabel('BET surface area [m2/g]')

The correlation is not linear.
The correlation is not linear.
The correlation is not linear.
Text(0, 0.5, 'BET surface area [m2/g]')
_images/examples_area_calcs_16_2.png

We can see that some points correspond, while others are not as well correlated. Unless the adsorption isotherm respects the Langmuir model, the calculated surface areas do not match. However, if the Langmuir area is calculated in a low pressure regime, ideally before multilayer adsorption or condensation occurs, the two specific areas are better correlated (right graph).

More info can be found in the documentation of the module.

This page was generated from docs/examples/tplot.ipynb. To start an interactive version: Binder badge
t-plot calculations#

Another common characterisation method is the t-plot method. First, make sure the data is imported by running the previous notebook.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation

Besides an isotherm, this method requires a so-called thickness function, which empirically describes the thickness of adsorbate layers on a non-porous surface as a function of pressure. It can be specified by the user, otherwise the "Harkins and Jura" thickness model is used by default. When the function is called without any other parameters, the framework will attempt to find plateaus in the data and automatically fit them with a straight line.

Let's look again at the our MCM-41 pore-controlled glass.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')
print(isotherm.material)
results = pgc.t_plot(isotherm, verbose=True)
MCM-41
For linear region 1
The slope is 0.009732 and the intercept is 0.0002239, with a correlation coefficient of 1
The adsorbed volume is 0.00778 cm3/g and the area is 338.2 m2/g
For linear region 2
The slope is 0.001568 and the intercept is 0.008244, with a correlation coefficient of 0.9993
The adsorbed volume is 0.286 cm3/g and the area is 54.5 m2/g
_images/examples_tplot_4_1.png

The first line can be attributed to adsorption on the inner pore surface, while the second one is adsorption on the external surface after pore filling. Two values are calculated for each section detected: the adsorbed volume and the specific area. In this case, the area of the first linear region corresponds to the pore area. Compare the specific surface area obtained of 340 \(m^2\) with the 360 \(m^2\) obtained through the BET method previously.

In the second region, the adsorbed volume corresponds to the total pore volume and the area is the external surface area of the sample.

We can get a better result for the surface area by attempting to have the first linear region at a zero intercept.

[3]:
print(isotherm.material)
results = pgc.t_plot(
    isotherm,
    thickness_model='Harkins/Jura',
    t_limits=(0.3,0.44),
    verbose=True
)
MCM-41
For linear region 1
The slope is 0.01003 and the intercept is 0.0001048, with a correlation coefficient of 0.9996
The adsorbed volume is 0.00364 cm3/g and the area is 348.4 m2/g
_images/examples_tplot_6_1.png

A near perfect match with the BET method. Of course, the method is only this accurate in certain cases, see more info in the documentation of the module. Let's do the calculations for all the nitrogen isotherms, using the same assumption that the first linear region is a good indicator of surface area.

[4]:
results = []
for isotherm in isotherms_n2_77k:
    results.append((isotherm.material, pgc.t_plot(isotherm, 'Harkins/Jura')))

[(x, f"{y['results'][0].get('area'):.2f}") for (x,y) in results]
[
    (<pygaps.Material 'MCM-41'>, '338.20'),
    (<pygaps.Material 'NaY'>, '199.77'),
    (<pygaps.Material 'SiO2'>, '249.14'),
    (<pygaps.Material 'Takeda 5A'>, '99.55'),
    (<pygaps.Material 'UiO-66(Zr)'>, '17.77')
]

We can see that, while we get reasonable values for the silica samples, all the rest are quite different. This is due to a number of factors depending on the material: adsorbate-adsorbent interactions having an effect on the thickness of the layer or simply having a different adsorption mechanism. The t-plot requires careful thought to assign meaning to the calculated values.

Since no thickness model can be universal, the framework allows for the thickness model to be substituted with an user-provided function which will be used for the thickness calculation, or even another isotherm, which will be converted into a thickness model.

For example, using a carbon black type model:

[5]:
def carbon_model(relative_p):
    return 0.88*(relative_p**2) + 6.45*relative_p + 2.98

isotherm = next(i for i in isotherms_n2_77k if i.material=='Takeda 5A')
print(isotherm.material)
results = pgc.t_plot(isotherm, thickness_model=carbon_model, verbose=True)
Takeda 5A
For linear region 1
The slope is 0.0006789 and the intercept is 0.0101, with a correlation coefficient of 0.9939
The adsorbed volume is 0.351 cm3/g and the area is 23.59 m2/g
_images/examples_tplot_10_1.png

Isotherms which do not use nitrogen can also be used, but one should be careful that the thickness model is well chosen.

More information about the functions and their use can be found in the manual.

This page was generated from docs/examples/alphas.ipynb. To start an interactive version: Binder badge
The \(\alpha_s\) method#

Very similar to the t-plot method, the \(\alpha_s\) method compares an isotherm on a porous material with one that was taken on a reference non-porous surface. The reference isotherm should be measured over a wide pressure range, to be able to compare loading values. First, make sure the data is imported notebook.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation

Unfortunately, we don't have a reference isotherm in our data. We are instead going to be creative and assume that the adsorption on the silica (\(SiO_2\) sample) is a good representation of an adsorption on a non-porous version of the MCM-41 sample. Let's try:

[2]:
iso_1 = next(i for i in isotherms_n2_77k if i.material == 'MCM-41')
iso_2 = next(i for i in isotherms_n2_77k if i.material == 'SiO2')

print(iso_1.material)
print(iso_2.material)
try:
    results = pgc.alpha_s(iso_1, reference_isotherm=iso_2, verbose=True)
except Exception as e:
    print('ERROR!:', e)

MCM-41
SiO2
ERROR!: A value in x_new is below the interpolation range.

The data in our reference isotherm is on a smaller range than that in the isotherm that we want to calculate! We are going to be creative again and first model the adsorption behaviour using a ModelIsotherm.

[3]:
import pygaps

model = pygaps.ModelIsotherm.from_pointisotherm(iso_2, model='BET', verbose=True)
Attempting to model using BET.
Model BET success, RMSE is 0.474
_images/examples_alphas_5_1.png

With our model fitting the data pretty well, we can now try the \(\alpha_s\) method again.

[4]:
results = pgc.alpha_s(iso_1, model, verbose=True)
For linear region 0
The slope is 0.004462 and the intercept is 0.001096, with a correlation coefficient of 0.9848
The adsorbed volume is 0.0381 cm3/g and the area is 270.1 m2/g
For linear region 1
The slope is 0.0005994 and the intercept is 0.008443, with a correlation coefficient of 0.9656
The adsorbed volume is 0.293 cm3/g and the area is 36.28 m2/g
_images/examples_alphas_7_1.png

The results don't look that bad, considering all our assumptions and modelling. There are other parameters which can be specified for the \(\alpha_s\) function such as:

  • The relative pressure to use as the reducing pressure

  • The known area of the reference material. If this is not specified, the BET method is used to calculate the surface area.

  • As in the t-plot function, the limits for the straight line selection.

Read more about the theory in the documentation of the module and in the manual.

This page was generated from docs/examples/psd.ipynb. To start an interactive version: Binder badge
Pore size distribution#

Often, pore size distributions are a very important part of adsorbent characterisation. The pyGAPS framework includes several common classical methods which are applicable to mesoporous or microporous materials. A DFT-fitting method is also provided together with an internal \(N_2\)/carbon applicable DFT kernel. The user can also specify their own kernel. A complete applicability guide and info on each function parameters can be found in the manual.

First, make sure the data is imported.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Mesoporous pore size distribution#

Let's start by analysing the mesoporous size distribution of some of our nitrogen physisorption samples.

The MCM-41 sample should have a very well defined, singular pore size in the mesopore range, with the pores as open-ended cylinders. We can use a common method, relying on a description of the adsorbate in the pores based on the Kelvin equation and the thickness of the adsorbed layer. These methods are derivatives of the BJH (Barrett, Joyner and Halenda) method.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')
print(isotherm.material)
results = pgc.psd_mesoporous(
    isotherm,
    pore_geometry='cylinder',
    verbose=True,
)
MCM-41
_images/examples_psd_3_1.png

The distribution is what we expected, a single narrow peak. Since we asked for extra verbosity, the function has generated a graph. The graph automatically sets a minimum limit of 1.5 angstrom, where the Kelvin equation methods break down.

The result dictionary returned contains the x and y points of the graph.

Depending on the sample, the distribution can be a well defined or broad, single or multimodal, or, in the case of adsorbents without mesoporoes, not relevant at all. For example, using the Takeda 5A carbon, and specifying a slit pore geometry:

[3]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='Takeda 5A')
print(isotherm.material)
result_dict_meso = pgc.psd_mesoporous(
        isotherm,
        psd_model='pygaps-DH',
        pore_geometry='slit',
        verbose=True,
)
Takeda 5A
_images/examples_psd_5_1.png

Now let's break down the available settings with the mesoporous PSD function.

  • A psd_model parameter to select specific implementations of the methods, such as the BJH method or the DH method.

  • A pore_geometry parameter can be used to specify the known pore geometry of the pore. The Kelvin equation parameters change appropriately.

  • Classical models are commonly applied on the desorption branch of the isotherm. This is also the default, although the user can specify the adsorption branch to be used with the branch parameter.

  • The function used for evaluating the layer thickness can be specified by the thickness_model parameter. Either a named internal model ('Halsey', 'Harkins/Jura', etc.) or a custom user function which takes pressure as an argument is accepted.

  • If the user wants to use a custom Kelvin model, they can do so through the kelvin_model parameters. This must be a name of an internal model ('Kelvin', 'Kelvin-KJS', etc.) or a custom function.

Below we use the adsorption branch and the Halsey thickness curve to look at the MCM-41 pores. We use the Kruck-Jaroniec-Sayari correction of the Kelvin model.

[4]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')
print(isotherm.material)
result_dict = pgc.psd_mesoporous(
    isotherm,
    psd_model='DH',
    pore_geometry='cylinder',
    branch='ads',
    thickness_model='Halsey',
    kelvin_model='Kelvin-KJS',
    verbose=True,
)
MCM-41
_images/examples_psd_7_1.png

Note: If the user wants to customise the standard plots which are displayed, they are available for use in the pygaps.graphing.calc_graphs module

Microporous pore size distribution#

For microporous samples, we can use the psd_microporous function. The available model is an implementation of the Horvath-Kawazoe (HK) method.

The HK model uses a list of parameters which describe the interaction between the adsorbate and the adsorbent. These should be selected on a per case basis by using the adsorbate_model and adsorbent_model keywords. If they are not specified, the function assumes a carbon model for the sample surface and takes the required adsorbate properties (magnetic susceptibility, polarizability, molecular diameter, surface density, liquid density and molar mass) from the isotherm adsorbate. The pore geometry is also assumed to be slit-like.

Let's look at using the function on the carbon sample:

[5]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'Takeda 5A')
print(isotherm.material)
result_dict_micro = pgc.psd_microporous(
    isotherm,
    psd_model='HK',
    verbose=True,
)
Takeda 5A
_images/examples_psd_10_1.png

We see that we could have a peak around 0.7 nm, but could use more adsorption data at low pressure for better resolution. It should be noted that the model breaks down with pores bigger than around 3 nm.

The framework comes with other models for the surface, like as the Saito-Foley derived oxide-ion model. Below is an attempt to use the HK method with these parameters for the UiO-66 sample and some user-specified parameters for the adsorbate interaction. We should not expect the results to be very accurate, due to the different surface properties and heterogeneity of the MOF.

[6]:
adsorbate_params = {
    'molecular_diameter': 0.3,
    'polarizability': 1.76e-3,
    'magnetic_susceptibility': 3.6e-8,
    'surface_density': 6.71e+18,
    'liquid_density': 0.806,
    'adsorbate_molar_mass': 28.0134
}

isotherm = next(i for i in isotherms_n2_77k if i.material == 'UiO-66(Zr)')
print(isotherm.material)
result_dict = pgc.psd_microporous(
    isotherm,
    psd_model='HK',
    material_model='AlSiOxideIon',
    adsorbate_model=adsorbate_params,
    verbose=True,
)

UiO-66(Zr)
_images/examples_psd_12_1.png

Finally, other types of H-K modified models are also available, like the Rege-Yang adapted model (RY), or a Cheng-Yang modification of both H-K (HK-CY) and R-Y (RY-CY) models.

[7]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'Takeda 5A')
print(isotherm.material)
result_dict_micro = pgc.psd_microporous(
    isotherm,
    psd_model='RY',
    verbose=True,
)
Takeda 5A
_images/examples_psd_14_1.png
Kernel fit pore size distribution#

The kernel fitting method is currently the most powerful method for pore size distribution calculations. It requires a DFT kernel, or a collection of previously simulated adsorption isotherms which cover the entire pore range which we want to investigate. The calculation of the DFT kernel is currently not in the scope of this framework.

The user can specify their own kernel, in a CSV format, which will be used for the isotherm fitting on the psd_dft function. A common DFT kernel is included with the framework, which is simulated with nitrogen on a carbon material and slit-like pores in the range of 0.4-10 nanometres.

Let's run the fitting of this internal kernel on the carbon sample:

[8]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='Takeda 5A')
result_dict_dft = {}
result_dict_dft = pgc.psd_dft(
    isotherm,
    branch='ads',
    kernel='DFT-N2-77K-carbon-slit',
    verbose=True,
    p_limits=None,
)
_images/examples_psd_17_0.png
_images/examples_psd_17_1.png

The output is automatically smoothed using a b-spline method. Further (or less) smoothing can be specified by the bspline_order parameter. The higher the order, more smoothing is applied. Specify "0" to return the data as-fitted.

[9]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'Takeda 5A')
result_dict_dft = pgc.psd_dft(
    isotherm,
    bspline_order=5,
    verbose=True,
)
_images/examples_psd_19_0.png
_images/examples_psd_19_1.png
Comparing all the PSD methods#

For comparison purposes, we will compare the pore size distributions obtained through all the methods above. The sample on which all methods are applicable is the Takeda carbon.

We will first plot the data using the existing function plot, then use the graph returned to plot the remaining results.

[11]:
from pygaps.graphing.calc_graphs import psd_plot

ax = psd_plot(
    result_dict_dft['pore_widths'],
    result_dict_dft['pore_distribution'],
    method='comparison',
    labeldiff='DFT',
    labelcum=None,
    left=0.4,
    right=8
)
ax.plot(
    result_dict_micro['pore_widths'],
    result_dict_micro['pore_distribution'],
    label='microporous',
)
ax.plot(
    result_dict_meso['pore_widths'],
    result_dict_meso['pore_distribution'],
    label='mesoporous',
)
ax.legend(loc='best')
<matplotlib.legend.Legend object at 0x0000019655F02760>
_images/examples_psd_22_1.png
This page was generated from docs/examples/dr_da_plots.ipynb. To start an interactive version: Binder badge
Dubinin-Radushkevich and Dubinin-Astakov plots#

The Dubinin-Radushkevich (DR) and Dubinin-Astakov (DA) plots are often used to determine the pore volume and the characteristic adsorption potential of adsorbent materials, in particular those of carbonaceous materials, such as activated carbons, carbon black or carbon nanotubes. It is also sometimes used for solution adsorption isotherms.

In pyGAPS, both the DR and DA plots are available with the dr_plot and da_plot functions. First we import the example isotherms.

[1]:
# import isotherms
%run import.ipynb
%matplotlib inline

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation

Then we'll select the Takeda 5A isotherm to examine. We will perform a DR plot, with increased verbosity to observe the resulting linear fit. The function returns a dictionary with the calculated pore volume and adsorption potential.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'Takeda 5A')
results = pgc.dr_plot(isotherm, verbose=True)
Micropore volume is: 0.484 cm3/g
Effective adsorption potential is : 5.84 kJ/mol
_images/examples_dr_da_plots_3_1.png

We can specify the pressure limits for the DR plot, to select only the points at low pressure for a better fit.

[3]:
results = pgc.dr_plot(isotherm, p_limits=[0, 0.1], verbose=True)
Micropore volume is: 0.448 cm3/g
Effective adsorption potential is : 6 kJ/mol
_images/examples_dr_da_plots_5_1.png

An extension of the DR model is the Dubinin-Astakov (DA) model. In the DA equation, the exponent can vary, and is usually chosen between 1 (for surfaces) and 3 (for micropores). For an explanation of the theory, check the function reference.

We then use the da_plot function and specify the exponent to be 2.3 (larger than the standard DR exponent of 2)

[4]:
results = pgc.da_plot(isotherm, p_limits=[0, 0.1], exp=2.3, verbose=True)

Micropore volume is: 0.422 cm3/g
Effective adsorption potential is : 6.34 kJ/mol
_images/examples_dr_da_plots_7_1.png

The DA plot can also automatically test which exponent gives the best fit between 1 and 3, if the parameter is left blank. The calculated exponent is returned in the result dictionary.

[5]:
result = pgc.da_plot(isotherm, p_limits=[0, 0.1], exp=None, verbose=True)
Exponent is: 3
Micropore volume is: 0.385 cm3/g
Effective adsorption potential is : 6.92 kJ/mol
_images/examples_dr_da_plots_9_1.png

More info about the method can be found in the reference.

This page was generated from docs/examples/enthalpy_sorption.ipynb. To start an interactive version: Binder badge
Sorption enthalpy calculations#

Sorption enthalpy, \(\Delta H_{ads}\), is an indication of the strength of the adsorbate-material interaction and can be estimated through several methods.

Isosteric enthalpy (Clausius-Clapeyron)#

In order to calculate \(\Delta H_{ads}\), at least two isotherms which were taken at different temperatures are required.

First, make sure the data is imported.

[1]:
# import isotherms
%run import.ipynb
%matplotlib inline

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
The file version is None while the parser uses version 1.0. Strange things might happen, so double check your data.
Could not parse parameter material_mass, currently unknown

Let's quickly plot the isotherms to see how they look. We put the temperature of the experiment in the legend by using the lgd_keys keyword.

[2]:
# import the graphing module
import pygaps.graphing as pgg

pgg.plot_iso(
    isotherms_isosteric,
    lgd_keys=['adsorbate', 'temperature'],
)
_images/examples_enthalpy_sorption_5_0.png

The isotherms look good, except perhaps a bit of measurement error in the low pressure region.

The isosteric enthalpy calculation takes the list of the isotherms and returns the results as a dictionary. Using the verbose keyword, we also generate a graph that includes an error bar.

[3]:
result_dict = pgc.isosteric_enthalpy(isotherms_isosteric, verbose=True)
_images/examples_enthalpy_sorption_7_0.png

The inaccuracy in the low pressure region has contributed to the odd enthalpy curve. One other option would be to first fit a model to each isotherm, then use it for the enthalpy determination.

Let's try a Double Site Langmuir model and then re-run the isosteric calculation.

[4]:
from pygaps.modelling import model_iso

models_isosteric = [model_iso(iso, model="dslangmuir") for iso in isotherms_isosteric]
result_dict = pgc.isosteric_enthalpy(models_isosteric, verbose=True)
_images/examples_enthalpy_sorption_9_0.png

More information about the functions and their use can be found in the manual.

Whittaker method#

The Whittaker method uses a Tóth’s modification of Polanyi's potential theory to determine enthalpy of adsorption from a single isotherm fitted with a suitable model. We loaded the example isotherm before, so we can now plot it.

[5]:
pgg.plot_iso(isotherms_enth_whittaker, lgd_keys=['adsorbate', 'temperature'], pressure_unit='bar')

_images/examples_enthalpy_sorption_13_0.png

The code can accept a ModelIsotherm of the required type. Note that this isotherm must be in correct pressure mode (absolute) and units: (Pa).

[6]:
from pygaps.modelling import model_iso
for iso in isotherms_enth_whittaker:
    iso.convert_pressure(mode_to="absolute", unit_to="Pa")
models_whittaker = model_iso(isotherms_enth_whittaker[0], model="Toth")
result_dict = pgc.enthalpy_sorption_whittaker(models_whittaker, verbose=True)
Thermodynamic backend failed with error: Temperature to QT_flash [298 K] must be in range [90.5941 K, 190.564 K]. Attempting to read parameters dictionary...
methane does not have a saturation pressure at 298.0 K. Calculating pseudo-saturation pressure...
_images/examples_enthalpy_sorption_15_1.png

Alternatively, the PointIsotherm and the desired model can be passed as parameters, and fitting is performed automatically before the method is applied.

[7]:
result_dict = pgc.enthalpy_sorption_whittaker(
    isotherms_enth_whittaker[0], model="Toth", verbose=True
)

Attempting to model using Toth.
Model Toth success, RMSE is 0.00493
Thermodynamic backend failed with error: Temperature to QT_flash [298 K] must be in range [90.5941 K, 190.564 K]. Attempting to read parameters dictionary...
methane does not have a saturation pressure at 298.0 K. Calculating pseudo-saturation pressure...
_images/examples_enthalpy_sorption_17_1.png
_images/examples_enthalpy_sorption_17_2.png
This page was generated from docs/examples/initial_henryc.ipynb. To start an interactive version: Binder badge
Henry's constant calculations#

In this notebook we'll calculate the characteristic Henry's constant at zero loading. Unlike fitting the entire isotherm with a Henry model, these methods will attempt to fit a straight line only on the initial part of the isotherm. First, make sure the data is imported.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Slope method#

The slope method of calculating Henry's fits a linear henry model to the isotherm points. If the model does not fit, it goes to progressively lower pressures until it finds a good fit. For data that is very non-linear, this fit might be between p=0 and the first isotherm point.

[2]:
# Slope method
isotherm = next(i for i in isotherms_n2_77k if i.material == 'MCM-41')
h1 = pgc.initial_henry_slope(isotherm, max_adjrms=0.01, logx=True, verbose=True)

isotherm = next(i for i in isotherms_n2_77k if i.material == 'UiO-66(Zr)')
h2 = pgc.initial_henry_slope(isotherm, max_adjrms=0.01, logx=True, verbose=True)

print(h1, h2)
Calculated K = 5.549e+04
Starting points: 42
Selected points: 2
Final adjusted RMSE: 5.32e-13
Calculated K = 6.977e+05
Starting points: 91
Selected points: 7
Final adjusted RMSE: 0.0061
55485.962663510676 697654.2592115065
_images/examples_initial_henryc_3_1.png
_images/examples_initial_henryc_3_2.png
Virial method#

The virial method uses a virial model to fit the data and then obtain the Henry constant from the value of the virial function at n=0. If the data is fitted well by a virial model, the resulting Henry constant will be very accurate.

[3]:
# Virial method
isotherm = next(i for i in isotherms_n2_77k if i.material == 'MCM-41')
h1 = pgc.initial_henry_virial(isotherm, verbose=True)

isotherm = next(i for i in isotherms_n2_77k if i.material == 'UiO-66(Zr)')
h2 = pgc.initial_henry_virial(isotherm, verbose=True)

print(h1, h2)
Attempting to model using Virial
Model Virial success, RMSE is 0.2174
Attempting to model using Virial
Model Virial success, RMSE is 0.494
194297.38100718585 1365637.221525526
_images/examples_initial_henryc_6_1.png
_images/examples_initial_henryc_6_2.png
_images/examples_initial_henryc_6_3.png
_images/examples_initial_henryc_6_4.png

More information about the functions and their use can be found in the manual.

This page was generated from docs/examples/initial_enthalpy.ipynb. To start an interactive version: Binder badge
Initial enthalpy calculations and enthalpy modelling#

Experimentally, the enthalpy of adsorption can be obtained either indirectly, through the isosteric enthalpy method, or directly, using adsorption microcalorimetry. Once an enthalpy curve is calculated, a useful performance indicator is the enthalpy of adsorption at zero loading, corresponding to the initial interactions of the probe with the surface. pyGAPS contains two methods to determine the initial enthalpy of adsorption starting from an enthalpy curve.

First, make sure the data is imported by running the import notebook.

[1]:
# import isotherms
%run import.ipynb

# import the characterisation module
import pygaps.characterisation as pgc
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Initial point method#

The point method of determining enthalpy of adsorption is the simplest method. It just returns the first measured point in the enthalpy curve.

Depending on the data, the first point method may or may not be representative of the actual value.

[2]:
import matplotlib.pyplot as plt

# Initial point method
isotherm = next(i for i in isotherms_calorimetry if i.material == 'HKUST-1(Cu)')
res = pgc.initial_enthalpy_point(isotherm, 'enthalpy', verbose=True)
plt.show()

isotherm = next(i for i in isotherms_calorimetry if i.material == 'Takeda 5A')
res = pgc.initial_enthalpy_point(isotherm, 'enthalpy', verbose=True)
plt.show()

The initial enthalpy of adsorption is:
        E = 28.8
_images/examples_initial_enthalpy_3_1.png
The initial enthalpy of adsorption is:
        E = 34.7
_images/examples_initial_enthalpy_3_3.png
Compound model method#

This method attempts to model the enthalpy curve by the superposition of several contributions. It is slower, as it runs a constrained minimisation algorithm with several initial starting guesses, then selects the optimal one.

[3]:
# Modelling method
isotherm = next(i for i in isotherms_calorimetry if i.material == 'HKUST-1(Cu)')
res = pgc.initial_enthalpy_comp(isotherm, 'enthalpy', verbose=True)
plt.show()

isotherm = next(i for i in isotherms_calorimetry if i.material == 'Takeda 5A')
res = pgc.initial_enthalpy_comp(isotherm, 'enthalpy', verbose=True)
plt.show()
Bounds:
        const = (19.849417219261177, 37.63022921697883)
        preexp = (0, 150), exp = (0, inf), exploc = (0, 0.5)
        prepowa = (0, 50), powa = (1, 20)
        prepowr = (-50, 0), powr = (1, 20)


Minimizing routine number 1
Initial guess:
        const = 28.739823218120005
        preexp = 0.0, exp = 0.0, exploc = 0.0
        prepowa = 0.0, powa = 1.0
        prepowr = 0.0, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.14458327076416183
            Iterations: 81
            Function evaluations: 738
            Gradient evaluations: 81


Minimizing routine number 2
Initial guess:
        const = 14.369911609060003
        preexp = 0.048145386979996374, exp = 0.0, exploc = 0.0
        prepowa = 0.0, powa = 1.0
        prepowr = -8.554071327020004, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.14458327307850274
            Iterations: 92
            Function evaluations: 833
            Gradient evaluations: 92


Minimizing routine number 3
Initial guess:
        const = 28.739823218120005
        preexp = 0.07221808046999456, exp = 10.0, exploc = 0.1
        prepowa = 0.01, powa = 3.0
        prepowr = 0.0, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.14458327055352374
            Iterations: 87
            Function evaluations: 792
            Gradient evaluations: 87


Minimizing routine number 4
Initial guess:
        const = 28.739823218120005
        preexp = 0.0, exp = 0.0, exploc = 0.1
        prepowa = 0.0, powa = 3.0
        prepowr = -0.01, powr = 3.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.14458327064989074
            Iterations: 112
            Function evaluations: 1019
            Gradient evaluations: 112


Final best fit 0.14458327064989074.


The initial enthalpy of adsorption is:
        E = 28.5
The constant contribution is
        19.8
The exponential contribution is
        8.71 * exp(15.1 * n)with the limit at 0.407
The guest-guest attractive contribution is
        47.7 * n^2.6
The guest-guest repulsive contribution is
        -50 * n^5.48
_images/examples_initial_enthalpy_6_1.png
Bounds:
        const = (23.797607891739087, 32.54769036367995)
        preexp = (0, 150), exp = (0, inf), exploc = (0, 0.5)
        prepowa = (0, 50), powa = (1, 20)
        prepowr = (-50, 0), powr = (1, 20)


Minimizing routine number 1
Initial guess:
        const = 28.17264912770952
        preexp = 0.0, exp = 0.0, exploc = 0.0
        prepowa = 0.0, powa = 1.0
        prepowr = 0.0, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.09541800223250922
            Iterations: 9
            Function evaluations: 81
            Gradient evaluations: 9


Minimizing routine number 2
Initial guess:
        const = 14.08632456385476
        preexp = 6.557036324590477, exp = 0.0, exploc = 0.0
        prepowa = 2.9277314033904815, powa = 1.0
        prepowr = 0.0, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.007235460364955422
            Iterations: 75
            Function evaluations: 678
            Gradient evaluations: 75


Minimizing routine number 3
Initial guess:
        const = 28.17264912770952
        preexp = 9.835554486885716, exp = 10.0, exploc = 0.1
        prepowa = 0.01, powa = 3.0
        prepowr = 0.0, powr = 1.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.007235460317026574
            Iterations: 62
            Function evaluations: 558
            Gradient evaluations: 62


Minimizing routine number 4
Initial guess:
        const = 28.17264912770952
        preexp = 0.0, exp = 0.0, exploc = 0.1
        prepowa = 0.0, powa = 3.0
        prepowr = -0.01, powr = 3.0
Optimization terminated successfully    (Exit mode 0)
            Current function value: 0.007334815292145825
            Iterations: 62
            Function evaluations: 561
            Gradient evaluations: 62


Final best fit 0.007334815292145825.


The initial enthalpy of adsorption is:
        E = 37.1
The constant contribution is
        26.5
The exponential contribution is
        21.2 * exp(12.3 * n)with the limit at 5.53e-18
The guest-guest attractive contribution is
        50 * n^18.1
The guest-guest repulsive contribution is
        -45.6 * n^18.1
_images/examples_initial_enthalpy_6_3.png
This page was generated from docs/examples/modelling.ipynb. To start an interactive version: Binder badge

Isotherm model fitting#

In this notebook we'll attempt to fit isotherms using the included models. First, make sure the data is imported by running the import notebook.

[1]:
%matplotlib inline
# import isotherms
%run import.ipynb

# Then the modelling module
import pygaps.modelling as pgm
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Selecting models#

Instead of recreating isotherms, we'll fit PointIsotherms with the model_iso function. Let's select one of the isotherms and attempt to model it with the Double Site Langmuir model. It is worth noting that, if the branch is not selected, the model will automatically select the adsorption branch.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='UiO-66(Zr)')
model_iso = pgm.model_iso(isotherm, model='DSLangmuir', verbose=True)
print(model_iso.model)
Attempting to model using DSLangmuir.
Model DSLangmuir success, RMSE is 0.0235
DSLangmuir isotherm model.
RMSE = 0.02352
Model parameters:
        n_m1 = 4.736
        K1 = 243.3
        n_m2 = 9.218
        K2 = 5.912e+04
Model applicable range:
        Pressure range: 6.04e-07 - 0.96
        Loading range: 0.257 - 14.7

_images/examples_modelling_4_1.png

The original model is therefore reasonably good, even if there are likely microporous filling steps at low pressure which are not fully captured. It's important to note that the ModelIsotherm has almost all the properties of a PointIsotherm and can be used for all calculations. For example:

[3]:
import pygaps.characterisation as pgc
res = pgc.area_langmuir(model_iso, verbose=True)
Langmuir area: a = 1361 m2/g
Minimum pressure point is 0.0482 and maximum is 0.851
The Langmuir constant is: K = 738
Amount Langmuir monolayer is: n = 0.014 mol/g
The slope of the Langmuir fit: s = 71.7
The intercept of the Langmuir fit: i = 0.0971

_images/examples_modelling_6_1.png

Let's now apply the same model to another isotherm.

[4]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'SiO2')
try:
    model = pgm.model_iso(
        isotherm,
        model='DSLangmuir',
        verbose=True,
    )
except Exception as e:
    print(e)
Attempting to model using DSLangmuir.
Fitting routine for DSLangmuir failed with error:
        The maximum number of function evaluations is exceeded.
Try a different starting point in the nonlinear optimization
by passing a dictionary of parameter guesses, param_guess, to the constructor.
Default starting guess for parameters:
[ 8.584125   18.63017335  8.584125   27.94526003]

We can increase the number of minimisation iterations manually, by specifying an optimisation_params dictionary which will be passed to the relevant Scipy routine. However, the model chosen may not fit the data, no matter how much we attempt to minimise the function, as seen below.

[5]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'SiO2')
try:
    model = pgm.model_iso(
        isotherm,
        model='DSLangmuir',
        verbose=True,
        optimization_params=dict(max_nfev=1e3),
    )
except Exception as e:
    print(e)
Attempting to model using DSLangmuir.
Fitting routine for DSLangmuir failed with error:
        The maximum number of function evaluations is exceeded.
Try a different starting point in the nonlinear optimization
by passing a dictionary of parameter guesses, param_guess, to the constructor.
Default starting guess for parameters:
[ 8.584125   18.63017335  8.584125   27.94526003]

Guessing models#

We also have the option of guessing a model instead. This option will calculate model fits with a selection of the available models, and select the one with the smallest root mean square. Let's try this on the previous isotherm.

[6]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='SiO2')

model = pgm.model_iso(isotherm, model='guess', verbose=True)
Attempting to model using Henry.
Model Henry success, RMSE is 0.102
Attempting to model using Langmuir.
Modelling using Langmuir failed.

Fitting routine for Langmuir failed with error:
        The maximum number of function evaluations is exceeded.
Try a different starting point in the nonlinear optimization
by passing a dictionary of parameter guesses, param_guess, to the constructor.
Default starting guess for parameters:
[46.57543338 17.16825   ]

Attempting to model using DSLangmuir.
Modelling using DSLangmuir failed.

Fitting routine for DSLangmuir failed with error:
        The maximum number of function evaluations is exceeded.
Try a different starting point in the nonlinear optimization
by passing a dictionary of parameter guesses, param_guess, to the constructor.
Default starting guess for parameters:
[ 8.584125   18.63017335  8.584125   27.94526003]

Attempting to model using DR.
Model DR success, RMSE is 0.134
Attempting to model using Freundlich.
Model Freundlich success, RMSE is 0.0977
Attempting to model using Quadratic.
Model Quadratic success, RMSE is 0.181
Attempting to model using BET.
Model BET success, RMSE is 0.0311
Attempting to model using TemkinApprox.
Model TemkinApprox success, RMSE is 0.0963
Attempting to model using Toth.
Model Toth success, RMSE is 0.102
Attempting to model using JensenSeaton.
Model JensenSeaton success, RMSE is 0.102
Best model fit is BET.
_images/examples_modelling_12_1.png

We can see that most models failed or have a poor fit, but the BET model has been correctly identified as the best fitting one.

Other options#

We can also attempt to model the desorption branch of an isotherm, and provide a manual list of models to attempt to guess, including specialised models which are not usually included in the guessing routine.

[7]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='Takeda 5A')

model = pgm.model_iso(isotherm, model=['GAB', 'BET', 'Langmuir'], branch='des', verbose=True)
Attempting to model using GAB.
Model GAB success, RMSE is 0.0569
Attempting to model using BET.
Model BET success, RMSE is 0.0569
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.119
Best model fit is BET.
_images/examples_modelling_16_1.png

We can manually set bounds on fitted parameters by using a param_bounds dictionary, passed to the ModelIsotherm.

[8]:
isotherm = next(i for i in isotherms_n2_77k if i.material == 'UiO-66(Zr)')

model = pgm.model_iso(
    isotherm,
    model='Langmuir',
    param_bounds={
        "n_m": [0., 14],
        "K": [0., 100],
    },
    verbose=True,
)
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.247
_images/examples_modelling_18_1.png

Just because a the minimisation has successfully produced a model that does NOT mean that the model is accurate. For example, trying to model the MCM-41 sample with a Langmuir model does not throw any errors but it is obvious that the model is not representative of the mesoporous condensation in the pores.

[9]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')

model = pgm.model_iso(isotherm, model="DSLangmuir", verbose=True)
Attempting to model using DSLangmuir.
Model DSLangmuir success, RMSE is 0.0507
_images/examples_modelling_20_1.png
Creating ModelIsotherms#

ModelIsotherms do not need to be created from a PointIsotherm. They can also be created from raw data, or from pre-generated models. To create one from scratch:

[10]:
import pygaps as pg
model_iso = pg.ModelIsotherm(
    material='carbon',
    adsorbate='N2',
    temperature=77,
    pressure=[1,2,3,4],
    loading=[1,2,3,4],
    model='Henry',
)
model_iso.plot()
WARNING: 'pressure_mode' was not specified, assumed as 'absolute'
WARNING: 'pressure_unit' was not specified, assumed as 'bar'
WARNING: 'material_basis' was not specified, assumed as 'mass'
WARNING: 'material_unit' was not specified, assumed as 'g'
WARNING: 'loading_basis' was not specified, assumed as 'molar'
WARNING: 'loading_unit' was not specified, assumed as 'mmol'
WARNING: 'temperature_unit' was not specified, assumed as 'K'
_images/examples_modelling_22_1.png

Or if a model is to be created from pre-defined parameters, one can do:

[11]:
model = pgm.get_isotherm_model(
    'Langmuir',
    parameters={
        "K": 20,
        "n_m": 2
    },
    pressure_range=(0.01, 2),
)
model_iso = pg.ModelIsotherm(
    material='carbon',
    adsorbate='N2',
    temperature=77,
    model=model,
)
model_iso.plot()

WARNING: 'pressure_mode' was not specified, assumed as 'absolute'
WARNING: 'pressure_unit' was not specified, assumed as 'bar'
WARNING: 'material_basis' was not specified, assumed as 'mass'
WARNING: 'material_unit' was not specified, assumed as 'g'
WARNING: 'loading_basis' was not specified, assumed as 'molar'
WARNING: 'loading_unit' was not specified, assumed as 'mmol'
WARNING: 'temperature_unit' was not specified, assumed as 'K'
_images/examples_modelling_24_1.png

More info can be found in the manual section.

This page was generated from docs/examples/iast.ipynb. To start an interactive version: Binder badge

IAST examples#

The IAST method is used to predict the composition of the adsorbed phase in a multicomponent adsorption system, starting from pure component isotherms. First, make sure the data is imported by running the import notebook.

[1]:
# import isotherms
%run import.ipynb

# import the iast module
import pygaps
import pygaps.iast as pgi

import matplotlib.pyplot as plt
import numpy
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Using models#

The IAST calculation is often performed by fitting a model to the isotherm rather than on the isotherms themselves, as spreading pressure can be computed efficiently when using most common models. Let's first fit the Langmuir model to both isotherms.

[2]:
isotherms_iast_models = []

isotherm = next(i for i in isotherms_iast if i.material=='MOF-5(Zn)')
print('Isotherm sample:', isotherm.material)

for isotherm in isotherms_iast:
    model = pygaps.ModelIsotherm.from_pointisotherm(isotherm, model='Langmuir', verbose=True)
    isotherms_iast_models.append(model)
Isotherm sample: MOF-5(Zn)
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.901
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.272
_images/examples_iast_3_1.png
_images/examples_iast_3_2.png

Now we can perform the IAST calculation with the resulting models. We specify the partial pressures of each component in the gaseous phase to obtain the composition of the adsorbed phase.

[3]:
gas_fraction = [0.5, 0.5]
total_pressure = 10
pgi.iast_point_fraction(isotherms_iast_models, gas_fraction, total_pressure, verbose=True)
2 components.
        Partial pressure component 0 = 5
        Partial pressure component 1 = 5
Component 0
        p = 5
        p^0 = 5.71
        Loading = 11.01
        x = 0.8757
        Spreading pressure = 18.18
Component 1
        p = 5
        p^0 = 40.21
        Loading = 1.563
        x = 0.1243
        Spreading pressure = 18.18
array([11.0056369 ,  1.56267705])

Alternatively, if we are interested in a binary system, we can use the extension functions iast_binary_svp and iast_binary_vle to obtain how the selectivity changes based on pressure in a constant composition or, respectively, how the gas phase-adsorbed phase changes with gas composition, at constant pressure.

These functions perform the IAST calculation at every point in the range passed and can plot the results. If interested in the selectivity for one component in an equimolar mixture over a pressure range:

[4]:
mole_fractions = [0.5, 0.5]
pressure_range = numpy.linspace(0.01, 20, 30)

result_dict = pgi.iast_binary_svp(
    isotherms_iast_models,
    mole_fractions,
    pressure_range,
    verbose=True,
)
_images/examples_iast_7_0.png

Or if interested on a adsorbed - gas phase equilibrium line:

[5]:
total_pressure = 2
result_dict = pgi.iast_binary_vle(
    isotherms_iast_models,
    total_pressure,
    verbose=True,
)
_images/examples_iast_9_0.png
Using isotherms directly - interpolation#

The isotherms themselves can be used directly. However, instead of spreading pressure being calculated from the model, it will be approximated through interpolation and numerical quadrature integration.

[6]:
gas_fraction = [0.5, 0.5]
total_pressure = 10
pgi.iast_point_fraction(
    isotherms_iast_models,
    gas_fraction,
    total_pressure,
    verbose=True,
)
2 components.
        Partial pressure component 0 = 5
        Partial pressure component 1 = 5
Component 0
        p = 5
        p^0 = 5.71
        Loading = 11.01
        x = 0.8757
        Spreading pressure = 18.18
Component 1
        p = 5
        p^0 = 40.21
        Loading = 1.563
        x = 0.1243
        Spreading pressure = 18.18
array([11.0056369 ,  1.56267705])

The binary mixture functions can also accept PointIsotherm objects.

[7]:
mole_fraction = [0.5, 0.5]
pressure_range = numpy.linspace(0.01, 20, 30)

result_dict = pgi.iast_binary_svp(
    isotherms_iast,
    mole_fraction,
    pressure_range,
    verbose=True,
)
_images/examples_iast_14_0.png
[8]:
result_dict = pgi.iast_binary_vle(
    isotherms_iast,
    total_pressure=2,
    verbose=True,
)
_images/examples_iast_15_0.png

More info about the method can be found in the manual.

This page was generated from docs/examples/parsing.ipynb. To start an interactive version: Binder badge

Parsing examples#

Some examples on parsing to and from supported formats. More info about all parsing methods can be found in the manual section.

Declare paths#

First, let's do all the necessary imports and generate the paths that we'll use for file import and export.

[1]:
%matplotlib inline

from pathlib import Path
import pygaps.parsing as pgp

# Get directory paths
base_path = Path.cwd() / 'data' / 'parsing'

# Find files
aif_file_paths = list((base_path / 'aif').rglob('*.aif'))
json_file_paths = list((base_path / 'json').rglob('*.json'))
xl_file_paths = list((base_path / 'excel').rglob('*.xls'))
csv_file_paths = list((base_path / 'csv').rglob('*.csv'))
Manufacturer import#

Many report files from various adsorption device manufacturers can be imported directly using pyGAPS. Here are some examples.

[2]:
cfld = base_path / "commercial"
dvssms = pgp.isotherm_from_commercial(cfld / "smsdvs" / "13X water 30c.xlsx", 'smsdvs', 'xlsx')
micromeritics = pgp.isotherm_from_commercial(cfld / "mic" / "Sample_C.xls", 'mic', 'xl')
belsorp_dat = pgp.isotherm_from_commercial(cfld / "bel" / "DUT-13-CH4-190K.DAT", 'bel', 'dat')
threeP_xl = pgp.isotherm_from_commercial(cfld / "3p" / "MOF_N2_77K.xlsx", '3p', 'xl')
quantachrome = pgp.isotherm_from_commercial(
    cfld / "qnt" / "DUT-6_N2_77K (Raw Analysis Data).txt", 'qnt', 'txt-raw'
)

Specified adsorbate is not in internal list (or name cannot be resolved to an existing one). Thermodynamic backend disabled for this gas/vapour.
No data collected for pressure_saturation in file c:\Users\pauli\git\pyGAPS\docs\examples\data\parsing\commercial\mic\Sample_C.xls.
AIF Parsing#
AIF Import#

Adsorption information files are fully supported in pyGAPS, both for import and exports. Isotherms can be imported from an .aif as:

[3]:
# Import all
isotherms = [pgp.isotherm_from_aif(path) for path in aif_file_paths]

# Display an example file
print(next(isotherms))
Material: DMOF
Adsorbate: ethane
Temperature: 298.15K
Units:
        Uptake in: cm3(STP)/g
        Pressure in: kPa
Other properties:
        user: single gas
        date: 2019-08-19T00:00:00
        instrument: BEL VC-05
        material_mass: 0.817
        material_batch: [Zn2(tm-bdc)2(dabco)]
        material_mass_unit: g

AIF Export#

Similarly, an isotherm can be exported as an AIF file or a string, depending on whether a path is passed. For this purpose use either the module pygaps.isotherm_to_aif() function or the convenience class function to_aif().

[4]:
# module function
for isotherm, path in zip(isotherms, aif_file_paths):
    pgp.isotherm_to_aif(isotherm, path)

# save to file with convenience function
isotherms[0].to_aif('isotherm.aif')

# string
isotherm_string = isotherms[0].to_aif()
JSON Parsing#
JSON Import#

Isotherms can be imported either from a json file or from a json string. The same function is used in both cases.

[5]:
# Import them
isotherms = [pgp.isotherm_from_json(path) for path in json_file_paths]

# Display an example file
print(next(isotherms))
Material: TEST
Adsorbate: n-butane
Temperature: 298.15K
Units:
        Uptake in: g/g
        Pressure in: bar
Other properties:
        iso_type: isotherm
        material_batch: TB

JSON Export#

Exporting to JSON can be done to a file or a string, depending on whether a path is passed. For this purpose use either the module pygaps.isotherm_to_json() function or the convenience class function to_json().

[6]:
# module function
for isotherm, path in zip(isotherms, json_file_paths):
    pgp.isotherm_to_json(isotherm, path, indent=4)

# save to file with convenience function
isotherms[0].to_json('isotherm.json')

# string
isotherm_string = isotherms[0].to_json()
Excel Parsing#

Excel does not have to be installed on the system in use.

Excel Import#
[7]:
# Import them
isotherms = [pgp.isotherm_from_xl(path) for path in xl_file_paths]

# Display an example file
print(next(isotherms))
Material: MCM-41
Adsorbate: nitrogen
Temperature: 77.0K
Units:
        Uptake in: mmol/g
        Pressure in: bar
Other properties:
        comment: None
        date: None
        lab: MADIREL
        instrument: Triflex
        project: None
        activation_temperature: 150.0
        user: PI
        iso_type: Isotherme
        material_batch: Test

[8]:
isotherms[1].plot()
_images/examples_parsing_14_0.png
Excel Export#
[9]:
# Export each isotherm in turn
for isotherm, path in zip(isotherms, xl_file_paths):
    pgp.isotherm_to_xl(isotherm, path)

# save to file with convenience function
isotherms[0].to_xl('isotherm.xls')
CSV Parsing#
CSV Import#

Like JSON, isotherms can be imported either from a CSV file or from a CSV string. The same function is used in both cases.

[10]:
# Import them
isotherms = [pgp.isotherm_from_csv(path) for path in csv_file_paths]

# Display an example file
print(next(isotherms))
Material: HKUST-1(Cu)
Adsorbate: carbon dioxide
Temperature: 303.0K
Units:
        Uptake in: mmol/g
        Pressure in: bar
Other properties:
        material_batch: Test
        iso_type: Calorimetrie
        user: ADW
        machine: CV
        date: 21/05/2010 00:00
        activation_temperature: 150.0
        lab: MADIREL

CSV Export#
[11]:
# Export each isotherm in turn
for isotherm, path in zip(isotherms, csv_file_paths):
    pgp.isotherm_to_csv(isotherm, path)

# save to file with convenience function
isotherms[0].to_csv('isotherm.csv')

# string representation
isotherm_string = isotherms[0].to_csv()
This page was generated from docs/examples/database.ipynb. To start an interactive version: Binder badge

Database examples#

Premise#

Let's assume we want to upload a newly created isotherm in the internal database. This isotherm is measured on the novel adsorbent Carbon X1, with nitrogen at 77 K.

Imports#

First we need to do the required imports

[1]:
import pygaps
import pygaps.parsing as pgp
Upload#

We happen to have the isotherm conveniently stored as a json file, so we load it into memory and inspect it.

[2]:
from pathlib import Path

json_path = Path.cwd() / 'data' / 'carbon_x1_n2.json'
with open(json_path) as text_file:
    isotherm = pgp.isotherm_from_json(text_file.read())

isotherm.print_info()
Material: Carbon X1
Adsorbate: nitrogen
Temperature: 77.355K
Units:
        Uptake in: mmol/g
        Pressure in: bar
Other properties:
        material_batch: X1
        iso_type: physisorption
        user: PI
        instrument: homemade1
        activation_temperature: 150.0
        lab: local
        treatment: acid activated

<AxesSubplot:xlabel='Pressure [$bar$]', ylabel='Loading [$mmol\\/g^{-1}$]'>
_images/examples_database_5_2.png

Now we can do the database upload.

[3]:
isotherm.to_db()
Material uploaded: 'Carbon X1'
Isotherm uploaded: '29c44c6f12c0f4735b6de977bdcef5c5'

The output points out that two things happened:

  • The isotherm material was first uploaded, using the material.name as a database ID.

  • Then the isotherm itself was uploaded, using its isotherm.iso_id as the unique ID.

The adsorbate (nitrogen) is already in the database, so it does need to be stored.

Now if we try to run the upload again we get an error.

[4]:
try:
    isotherm.to_db()
except Exception as e:
    print(e)
Error inserting isotherm "29c44c6f12c0f4735b6de977bdcef5c5" base properties. Ensure material "Carbon X1", and adsorbate "nitrogen" exist in the database. Original error:
 UNIQUE constraint failed: isotherms.id

There was a FOREIGN KEY error: this isotherm is already in the database!

To retrieve the newly uploaded isotherm we can use the isotherms_from_db() function. By default, it gets all existing isotherms, although it can be configured to only return those matching a filter.

[5]:
# Getting all isotherms: 1 found
isos = pgp.isotherms_from_db()
# Filtering isotherms with an adsorbate: 0 found
isos = pgp.isotherms_from_db(criteria={'adsorbate' : 'neon'})
# Filtering isotherms on a material: 1 found
isos = pgp.isotherms_from_db(criteria={'material' : 'Carbon X1'})

# Check if the isotherm is the same
print(isotherm == isos[0])
Selected 1 isotherms
Selected 0 isotherms
Selected 1 isotherms
True

Any kind of isotherm can be stored, including ModelIsotherms and BaseIsotherms.

If we want to remove the isotherm from the database, we can just use a delete function:

[6]:
# This will remove the isotherm from the internal database
pgp.isotherm_delete_db(isotherm)
Isotherm deleted: '29c44c6f12c0f4735b6de977bdcef5c5'

The corresponding isotherm Material and Adsorbate have not been deleted, just the Isotherm!

Uploading Materials and Adsorbates#

Besides Isotherms, individually created Materials and Adsorbates can similarly be stored. If we attempt to create and upload a material which has some more metadata about the Carbon X1 sample we used previously.

[7]:
novel_material = pygaps.Material(
    name='Carbon X1',
    contact='PI',
    source='local',
    treatment='etched',
)
try:
    pgp.material_to_db(novel_material)
except Exception as e:
    print(e)
UNIQUE constraint failed: materials.name

We get a foreign key error, since the material.name is still in the database from the initial isotherm upload!

Instead, we overwrite it, using a function parameter:

[8]:
mats = pgp.materials_from_db()
print(mats[0])
Selected 1 materials
Carbon X1
[9]:
pgp.material_to_db(novel_material, overwrite=True)
Material properties type uploaded 'contact'
Material properties type uploaded 'source'
Material properties type uploaded 'treatment'
Material uploaded: 'Carbon X1'

Several things happened behind the scenes, including an upload of the new metadata types. Now the material has been updated. We can check this easily.

[10]:
mats = pgp.materials_from_db()
print(mats[0] == novel_material)

# The material was also cached internally
print(novel_material in pygaps.MATERIAL_LIST)
Selected 1 materials
True
True

We can also delete this material.

[11]:
pgp.material_delete_db(novel_material)
Material deleted: 'Carbon X1'

The same is true for Adsorbates by using the adsorbate_to_db/adsorbates_from_db/adsorbate_delete_db functions.

Using a separate database#

Up until now, we have been using the default database which comes with pyGAPS. This may not be ideal for long term isotherm storage or making changes, as every new pyGAPS version will replace the default database.

To see the location of the default database:

[12]:
pygaps.DATABASE
WindowsPath('c:/users/pauli/git/pygaps/src/pygaps/data/default.db')

However you can easily create your own, then use it instead. To do this pyGAPS provides an utility function.

[13]:
from pygaps.utilities.sqlite_db_creator import db_create

db_create("./own_database.db")
Experiment type uploaded 'isotherm'
Experiment type uploaded 'pointisotherm'
Experiment type uploaded 'modelisotherm'

This new database is identical to the default pyGAPS database, and includes all adsorbate data. To use it for any storage, one can pass its path to the database interaction functions.

[14]:
pgp.material_to_db(novel_material, db_path="./own_database.db")
pgp.isotherm_to_db(isotherm, db_path="./own_database.db")
Material properties type uploaded 'contact'
Material properties type uploaded 'source'
Material properties type uploaded 'treatment'
Material uploaded: 'Carbon X1'
Isotherm uploaded: '29c44c6f12c0f4735b6de977bdcef5c5'
This page was generated from docs/examples/plotting.ipynb. To start an interactive version: Binder badge

Graphing examples#

This notebook contains some examples of how to generate isotherm graphs in pyGAPS. In general, we use matplotlib as a backend, and all resulting graphs can be customized as standard matplotlib figures/axes. However, some implicit formatting is applied and some utilities are provided to make it quicker to plot.

Import isotherms#

First import the example data by running the import notebook

[1]:
%matplotlib inline
%run import.ipynb
import matplotlib.pyplot as plt
Selected 5 isotherms with nitrogen at 77K
Selected 2 room temperature calorimetry isotherms
Selected 2 isotherms for IAST calculation
Selected 3 isotherms for isosteric enthalpy calculation
Isotherm display#

To generate a quick plot of an isotherm, call the plot() function. The parameters to this function are the same as pygaps.plot_iso.

[2]:
isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')
ax = isotherm.plot()
_images/examples_plotting_3_0.png
Isotherm plotting and comparison#

For more complex plots of multiple isotherms, the pygaps.plot_iso function is provided. Several examples of isotherm plotting are presented here:

  • A logarithmic isotherm graph comparing the adsorption branch of two isotherms up to 1 bar (x_range=(None, 1)). The isotherms are measured on the same material and batch, but at different temperatures, so we want this information to be visible in the legend (lgd_keys=[...]). We also want the loading to be displayed in cm3 STP (loading_unit="cm3(STP)") and to select the colours manually (color=[...]).

[3]:
import pygaps.graphing as pgg

ax = pgg.plot_iso(
    isotherms_isosteric,
    branch = 'ads',
    logx = True,
    x_range=(None,1),
    lgd_keys=['temperature'],
    loading_unit='cm3(STP)',
    color=['b', 'r', 'g']
)
_images/examples_plotting_5_0.png
  • A black and white (color=False) full scale graph of both adsorption and desorption branches of an isotherm (branch = 'all'), saving it to the local directory for a publication (save_path=path). The result file is found here. We also display the isotherm points using X markers (marker=['x']) and set the figure title (fig_title='Novel Behaviour').

[4]:
import pygaps.graphing as pgg
from pathlib import Path

path = Path.cwd() / 'novel.png'

isotherm = next(i for i in isotherms_n2_77k if i.material=='MCM-41')

ax = pgg.plot_iso(
    isotherm,
    branch = 'all',
    color=False,
    save_path=path,
    marker=['x'],
)
_images/examples_plotting_7_0.png
  • A graph which plots the both the loading and enthalpy as a function of pressure on the left and the enthalpy as a function of loading on the right, for a microcalorimetry experiment. To do this, we separately generate the axes and pass them in to the plot_iso function (ax=ax1). We want the legend to appear inside the graph (lgd_pos='inner') and, to limit the range of enthalpy displayed to 40 kJ (either y2_range or y1_range, depending on where it is displayed). Finally, we want to manually control the size of the pressure and enthalpy markers (y1_line_style=dict(markersize=0)).

[5]:
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10,5))

pgg.plot_iso(
    isotherms_calorimetry[1],
    ax=ax1,
    x_data='pressure',
    y1_data='loading',
    y2_data='enthalpy',
    lgd_pos='lower right',
    y2_range=(0,40),
    y1_line_style=dict(markersize=0),
    y2_line_style=dict(markersize=3),
)
pgg.plot_iso(
    isotherms_calorimetry[1],
    ax=ax2,
    x_data='loading',
    y1_data='enthalpy',
    y1_range=(0,40),
    lgd_pos='best',
    marker=['^'],
    y1_line_style=dict(linewidth=0)
)
<AxesSubplot:xlabel='Loading [$mmol\\/g^{-1}$]', ylabel='$\\Delta_{ads}h$ $(-kJ\\/mol^{-1})$'>
_images/examples_plotting_9_1.png
  • A comparison graph of all the nitrogen isotherms, with both branches shown but without adding the desorption branch to the label (branch='all-nol'). We want each isotherm to use a different marker (marker=len(isotherms)) and to not display the desorption branch component of the legend (only lgd_keys=['material']).

[6]:
ax = pgg.plot_iso(
    isotherms_n2_77k,
    branch='all',
    lgd_keys=['material'],
    marker=len(isotherms_n2_77k)
)
ax.set_title("Regular isotherms colour")
Text(0.5, 1.0, 'Regular isotherms colour')
_images/examples_plotting_11_1.png
  • A black and white version of the same graph (color=False), but with absolute pressure in bar.

[7]:
ax = pgg.plot_iso(
    isotherms_n2_77k,
    branch='all',
    color=False,
    lgd_keys=['material'],
    pressure_mode='absolute',
    pressure_unit='bar',
   )
ax.set_title("Black and white")
Text(0.5, 1.0, 'Black and white')
_images/examples_plotting_13_1.png
  • Only some ranges selected for display from all the isotherms (x_range=(0.2, 0.6) and y1_range=(3, 10)).

[8]:
ax = pgg.plot_iso(
    isotherms_n2_77k,
    branch='all',
    x_range=(0.2, 0.6),
    y1_range=(3, 10),
    lgd_keys=['material']
)
_images/examples_plotting_15_0.png
  • The isosteric pressure isotherms, in relative pressure mode and loading in cm3(STP). No markers are displayed (marker=False).

[9]:
ax = pgg.plot_iso(
    isotherms_isosteric,
    branch='ads',
    pressure_mode='relative',
    loading_unit='cm3(STP)',
    lgd_keys=['adsorbate', 'temperature'],
    marker=False
)
ax.set_title("Different pressure mode or units")
Text(0.5, 1.0, 'Different pressure mode or units')
_images/examples_plotting_17_1.png
  • Only desorption branch of some isotherms (branch='des'), displaying the user who recorded the isotherms in the graph legend.

[10]:
ax = pgg.plot_iso(
    isotherms_n2_77k,
    branch='des',
    lgd_keys=['material', 'user'],
    lgd_pos='out bottom',
)
ax.set_title("Only desorption branch")
Text(0.5, 1.0, 'Only desorption branch')
_images/examples_plotting_19_1.png

Command Line Interface#

Some pyGAPS functionality can also be used from the command line. Once installed, an entrypoint is automatically generated under the name pygaps.

pygaps -h

Full reference below:

Simple command-line interface for pyGAPS.

The main() function is defined as the entrypoint in setuptools' console_scripts.

pygaps.cli.cli.main()[source]#

The main entrypoint for the cli.

Currently the CLI can read any pyGAPS format (JSON, CSV, Excel) and then:

  • print isotherm to output (default if no argument is passed)

  • plot the isotherm using a Matplotlib window (-p/--plot)

  • run basic automated characterization tests (-ch/--characterize a_bet for the BET area for example)

  • attempt to model the isotherm using a requested model or guess the best fitting model (-md/--model guess) and save the resulting isotherm model using the -o/--outfile path.

  • convert the isotherm to any unit/basis (-cv/--convert pressure_mode=absolute,pressure_unit=bar) and save the resulting isotherm model using the -o/--outfile path.

Reference#

Below is documentation from all pyGAPS modules and functions. It has information on various inputs and outputs of the program, as well as extensive descriptions of the theory behind the methods and models that can be applied. It is recommended that the user always double checks this section to understand the inner workings of various functions.

Core classes#

Isotherms#
Base Isotherm#

Contains the Isotherm base class.

class pygaps.core.baseisotherm.BaseIsotherm(material: str | dict | Material | None = None, adsorbate: str | Adsorbate | None = None, temperature: float | str | None = None, **properties: dict)[source]#

Class which contains the general data for an isotherm, real or model.

The isotherm class is the parent class that both PointIsotherm and ModelIsotherm inherit. It is designed to contain the information about an isotherm (such as material, adsorbate, data units etc.) but without any of the data itself.

Think of this class as a extended python dictionary.

Parameters:
  • material (str) -- Name of the material on which the isotherm is measured.

  • adsorbate (str) -- Isotherm adsorbate.

  • temperature (float) -- Isotherm temperature.

Other Parameters:
  • pressure_mode (str, optional) -- The pressure mode, either 'absolute' pressure or 'relative' ('relative%') in the form of p/p0.

  • pressure_unit (str, optional) -- Unit of pressure, if applicable.

  • loading_basis (str, optional) -- Whether the adsorbed amount is in terms of either 'volume_gas' 'volume_liquid', 'molar', 'mass', or a fraction/percent basis.

  • loading_unit (str, optional) -- Unit in which the loading basis is expressed.

  • material_basis (str, optional) -- Whether the underlying material is in terms of 'per volume' 'per molar amount' or 'per mass' of material.

  • material_unit (str, optional) -- Unit in which the material basis is expressed.

Notes

The class is also used to prevent duplication of code within the child classes, by calling the common inherited function before any other specific implementation additions.

The minimum arguments required to instantiate the class are material, temperature', ``adsorbate.

property iso_id: str#

Return an unique identifier of the isotherm.

property material: Material#

Return underlying material.

property adsorbate: Adsorbate#

Return underlying adsorbate.

property temperature: float#

Return underlying temperature, always in kelvin.

property units: dict#

Return a dictionary of all isotherm units

to_dict() dict[source]#

Returns a dictionary of the isotherm class Is the same dictionary that was used to create it.

Returns:

dict -- Dictionary of all parameters.

to_json(path=None, **kwargs) None | str[source]#

Convert the isotherm to a JSON representation.

Parameters:
  • path -- File path or object. If not specified, the result is returned as a string.

  • kwargs -- Custom arguments to be passed to "json.dump", like indent.

Returns:

None or str -- If path is None, returns the resulting json as a string. Otherwise returns None.

to_csv(path=None, separator=',', **kwargs) None | str[source]#

Convert the isotherm to a CSV representation.

Parameters:
  • path -- File path or object. If not specified, the result is returned as a string.

  • separator (str, optional) -- Separator used int the csv file. Defaults to '',''.

Returns:

None or str -- If path is None, returns the resulting csv as a string. Otherwise returns None.

to_xl(path, **kwargs)[source]#

Save the isotherm as an Excel file.

Parameters:

path -- Path where to save Excel file.

to_aif(path=None, **kwargs) None | str[source]#

Convert the isotherm to a AIF representation.

Parameters:

path -- File path or object. If not specified, the result is returned as a string.

Returns:

None or str -- If path is None, returns the resulting AIF as a string. Otherwise returns None.

to_db(db_path: str | None = None, verbose: bool = True, autoinsert_material: bool = True, autoinsert_adsorbate: bool = True, **kwargs)[source]#

Upload the isotherm to an sqlite database.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • autoinsert_material (bool, True) -- Whether to automatically insert an isotherm material if it is not found in the database.

  • autoinsert_adsorbate (bool, True) -- Whether to automatically insert an isotherm adsorbate if it is not found in the database.

  • verbose (bool) -- Extra information printed to console.

convert_temperature(unit_to: str, verbose: bool = False)[source]#

Convert isotherm temperature from one unit to another.

Parameters:
  • unit_to (str) -- The unit into which the internal temperature should be converted to.

  • verbose (bool) -- Print out steps taken.

Point Isotherm#

This module contains the main class that describes an isotherm through discrete points.

class pygaps.core.pointisotherm.PointIsotherm(pressure: List[float] | None = None, loading: List[float] | None = None, isotherm_data: DataFrame | None = None, pressure_key: str | None = None, loading_key: str | None = None, branch: str | List[bool] = 'guess', **other_properties)[source]#

Class which contains the points from an adsorption isotherm.

This class is designed to be a complete description of a discrete isotherm. It extends the BaseIsotherm class, which contains all the description of the isotherm parameters, but also holds the datapoints recorded during an experiment or simulation.

The minimum arguments required to instantiate the class, besides those required for the parent BaseIsotherm, is the actual data, specified either as pressure + loading arrays or as isotherm_data (a pandas.DataFrame) + keys for the columns of the dataframe which have the loading and the pressure data.

Parameters:
  • pressure (list) -- Create an isotherm directly from an array. Values for pressure. If the isotherm_data dataframe is specified, these values are ignored.

  • loading (list) -- Create an isotherm directly from an array. Values for loading. If the isotherm_data dataframe is specified, these values are ignored.

  • isotherm_data (pandas.DataFrame) -- Pure-component adsorption isotherm data.

  • pressure_key (str) -- The title of the pressure data in the DataFrame provided.

  • loading_key (str) -- The title of the loading data in the DataFrame provided.

  • branch (['guess', ads', 'des', iterable], optional) -- The branch of the isotherm. The code will automatically attempt to guess if there's an adsorption and desorption branch. The user can instead tell the framework that all points are part of an adsorption ('ads') or desorption ('des') curve. Alternatively, an iterable can be passed which contains detailed info for each data point if adsorption points ('False') or desorption points ('True'). eg: [False, False, True, True...] or as a column of the isotherm_data.

  • material (str) -- Name of the material on which the isotherm is measured.

  • adsorbate (str) -- Isotherm adsorbate.

  • temperature (float) -- Isotherm temperature.

Other Parameters:
  • pressure_mode (str, optional) -- The pressure mode, either 'absolute' pressure or 'relative' ('relative%') in the form of p/p0.

  • pressure_unit (str, optional) -- Unit of pressure, if applicable.

  • loading_basis (str, optional) -- Whether the adsorbed amount is in terms of either 'volume_gas' 'volume_liquid', 'molar', 'mass', or a fraction/percent basis.

  • loading_unit (str, optional) -- Unit in which the loading basis is expressed.

  • material_basis (str, optional) -- Whether the underlying material is in terms of 'per volume' 'per molar amount' or 'per mass' of material.

  • material_unit (str, optional) -- Unit in which the material basis is expressed.

Notes

This class assumes that the datapoints do not contain noise. Detection of adsorption/desorption branches will not work if data is noisy.

classmethod from_isotherm(isotherm: BaseIsotherm, pressure: List[float] | None = None, loading: List[float] | None = None, isotherm_data: DataFrame | None = None, pressure_key: str | None = None, loading_key: str | None = None)[source]#

Construct a point isotherm using a parent isotherm as the template for all the parameters.

Parameters:
  • isotherm (Isotherm) -- An instance of the Isotherm parent class.

  • pressure (list) -- Create an isotherm directly from an array. Values for pressure. If the isotherm_data dataframe is specified, these values are ignored.

  • loading (list) -- Create an isotherm directly from an array. Values for loading. If the isotherm_data dataframe is specified, these values are ignored.

  • isotherm_data (pandas.DataFrame) -- Pure-component adsorption isotherm data.

  • loading_key (str) -- Column of the pandas DataFrame where the loading is stored.

  • pressure_key (str) -- Column of the pandas DataFrame where the pressure is stored.

classmethod from_modelisotherm(modelisotherm, pressure_points: List[float] | None = None, loading_points: List[float] | None = None)[source]#

Construct a PointIsotherm from a ModelIsothem class.

This class method allows for the model to be converted into a list of points calculated by using the model in the isotherm.

Parameters:
  • modelisotherm (ModelIsotherm) -- The isotherm containing the model.

  • pressure_points (None or List or PointIsotherm) -- How the pressure points should be chosen for the resulting PointIsotherm.

    • If None, the PointIsotherm returned has a fixed number of equidistant points

    • If an array, the PointIsotherm returned has points at each of the values of the array

    • If a PointIsotherm is passed, the values will be calculated at each of the pressure points in the passed isotherm. This is useful for comparing a model overlap with the real isotherm.

convert(pressure_mode: str | None = None, pressure_unit: str | None = None, loading_basis: str | None = None, loading_unit: str | None = None, material_basis: str | None = None, material_unit: str | None = None, verbose: bool = False)[source]#

Convenience function for permanently converting any isotherm mode/basis/units.

Parameters:
  • pressure_mode ({'absolute', 'relative', 'relative%'}) -- The mode in which the isotherm should be converted.

  • pressure_unit (str) -- The unit into which the internal pressure should be converted to. Only makes sense if converting to absolute pressure.

  • loading_basis ({'mass', 'molar', 'volume_gas', 'volume_liquid', 'percent', 'fraction'}) -- The basis in which the isotherm should be converted.

  • loading_unit (str) -- The unit into which the internal loading should be converted to.

  • material_basis ({'mass', 'molar', 'volume'}) -- The basis in which the isotherm should be converted.

  • material_unit (str) -- The unit into which the material should be converted to.

  • verbose (bool) -- Print out steps taken.

convert_pressure(mode_to: str | None = None, unit_to: str | None = None, verbose: bool = False)[source]#

Convert isotherm pressure from one unit to another and the pressure mode from absolute to relative.

Only applicable in the case of isotherms taken below critical point of adsorbate.

Parameters:
  • mode_to ({'absolute', 'relative', 'relative%'}) -- The mode in which the isotherm should be converted.

  • unit_to (str) -- The unit into which the internal pressure should be converted to. Only makes sense if converting to absolute pressure.

  • verbose (bool) -- Print out steps taken.

convert_loading(basis_to: str | None = None, unit_to: str | None = None, verbose: bool = False)[source]#

Convert isotherm loading from one unit to another and the basis of the isotherm loading to be either 'mass', 'molar' or 'percent'/'fraction'.

Parameters:
  • basis_to ({'mass', 'molar', 'volume_gas', 'volume_liquid', 'percent', 'fraction'}) -- The basis in which the isotherm should be converted.

  • unit_to (str) -- The unit into which the internal loading should be converted to.

  • verbose (bool) -- Print out steps taken.

convert_material(basis_to: str | None = None, unit_to: str | None = None, verbose: bool = False)[source]#

Convert the material of the isotherm from one unit to another and the basis of the isotherm loading to be either 'per mass' or 'per volume' or 'per mole' of material.

Only applicable to materials that have been loaded in memory with a 'density' or 'molar mass' property respectively.

Parameters:
  • basis ({'mass', 'molar', 'volume'}) -- The basis in which the isotherm should be converted.

  • unit_to (str) -- The unit into which the material should be converted to.

  • verbose (bool) -- Print out steps taken.

print_info(**plot_iso_args)[source]#

Print a short summary of all the isotherm parameters and a graph.

Parameters:

show (bool, optional) -- Specifies if the graph is shown automatically or not.

Other Parameters:

plot_iso_args (dict) -- options to be passed to pygaps.plot_iso()

Returns:

axes (matplotlib.axes.Axes or numpy.ndarray of them)

plot(**plot_iso_args)[source]#

Plot the isotherm using pygaps.plot_iso().

Parameters:

show (bool, optional) -- Specifies if the graph is shown automatically or not.

Other Parameters:

plot_iso_args (dict) -- options to be passed to pygaps.plot_iso()

Returns:

axes (matplotlib.axes.Axes or numpy.ndarray of them)

data(branch: str | None = None) DataFrame[source]#

Return underlying isotherm data.

Parameters:

branch ({None, 'ads', 'des'}) -- The branch of the isotherm to return. If None, returns entire dataset.

Returns:

DataFrame -- The pandas DataFrame containing all isotherm data.

pressure(branch: str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, limits: Tuple[float, float] | None = None, indexed: bool = False) ndarray | Series[source]#

Return pressure points as an array.

Parameters:
  • branch ({None, 'ads', 'des'}) -- The branch of the pressure to return. If None, returns entire dataset.

  • pressure_unit (str, optional) -- Unit in which the pressure should be returned. If None it defaults to which pressure unit the isotherm is currently in.

  • pressure_mode ({None, 'absolute', 'relative', 'relative%'}) -- The mode in which to return the pressure, if possible. If None, returns mode the isotherm is currently in.

  • limits ([float, float], optional) -- Minimum and maximum pressure limits. Put None or -+np.inf for no limit.

  • indexed (bool, optional) -- If this is specified to true, then the function returns an indexed pandas.Series instead of an array.

Returns:

array or Series -- The pressure slice corresponding to the parameters passed.

loading(branch: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None, limits: Tuple[float, float] | None = None, indexed: bool = False) ndarray | Series[source]#

Return loading points as an array.

Parameters:
  • branch ({None, 'ads', 'des'}) -- The branch of the loading to return. If None, returns entire dataset.

  • loading_unit (str, optional) -- Unit in which the loading should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • loading_basis ({None, 'mass', 'volume_gas', 'volume_liquid', 'molar'}) -- The basis on which to return the loading, if possible. If None, returns on the basis the isotherm is currently in.

  • material_unit (str, optional) -- Unit in which the material should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • material_basis ({None, 'mass', 'volume', 'molar'}) -- The basis on which to return the material, if possible. If None, returns on the basis the isotherm is currently in.

  • limits ([float, float], optional) -- Minimum and maximum loading limits. Put None or -+np.inf for no limit.

  • indexed (bool, optional) -- If this is specified to true, then the function returns an indexed pandas.Series instead of an array.

Returns:

Array or Series -- The loading slice corresponding to the parameters passed.

property other_keys#

Return column names of any supplementary data points.

other_data(key: str, branch: str | None = None, limits: Tuple[float, float] | None = None, indexed: bool = False) ndarray | Series[source]#

Return supplementary data points as an array.

Parameters:
  • key (str) -- Key in the isotherm DataFrame containing the data to select.

  • branch ({None, 'ads', 'des'}) -- The branch of the data to return. If None, returns entire dataset.

  • limits ([float, float], optional) -- Minimum and maximum data limits. Put None or -+np.inf for no limit.

  • indexed (bool, optional) -- If this is specified to true, then the function returns an indexed pandas.Series instead of an array.

Returns:

array or Series -- The data slice corresponding to the parameters passed.

has_branch(branch: str) bool[source]#

Check if the isotherm has an specific branch.

Parameters:

branch ({None, 'ads', 'des'}) -- The branch of the data to check for.

Returns:

bool -- Whether the data exists or not.

pressure_at(loading: List[float], branch: str = 'ads', interpolation_type: str = 'linear', interp_fill: float | Tuple[float, float] | str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None) ndarray[source]#

Interpolate isotherm to compute pressure at any loading given.

Parameters:
  • loading (float) -- Loading at which to compute pressure.

  • branch ({'ads', 'des'}) -- The branch of the use for calculation. Defaults to adsorption.

  • interpolation_type (str) -- The type of scipy.interp1d used: linear, nearest, zero, slinear, quadratic, cubic. It defaults to linear.

  • interp_fill (array-like or (array-like, array_like) or “extrapolate”, optional) -- Parameter to determine what to do outside data bounds. Passed to the scipy.interpolate.interp1d function as fill_value. If blank, interpolation will not predict outside the bounds of data.

  • pressure_unit (str) -- Unit the pressure is returned in. If None, it defaults to internal isotherm units.

  • pressure_mode (str) -- The mode the pressure is returned in. If None, it defaults to internal isotherm mode.

  • loading_unit (str) -- Unit the loading is specified in. If None, it defaults to internal isotherm units.

  • loading_basis ({None, 'mass', 'molar', 'volume_gas', 'volume_liquid'}) -- The basis the loading is specified in. If None, assumes the basis the isotherm is currently in.

  • material_unit (str, optional) -- Unit in which the material is passed in. If None it defaults to which loading unit the isotherm is currently in

  • material_basis (str) -- The basis the loading is passed in. If None, it defaults to internal isotherm basis.

Returns:

float -- Predicted pressure at loading specified.

loading_at(pressure: List[float], branch: str = 'ads', interpolation_type: str = 'linear', interp_fill: float | Tuple[float, float] | str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None) ndarray[source]#

Interpolate isotherm to compute loading at any pressure given.

Parameters:
  • pressure (float or array) -- Pressure at which to compute loading.

  • branch ({'ads', 'des'}) -- The branch the interpolation takes into account.

  • interpolation_type (str) -- The type of scipy.interp1d used: linear, nearest, zero, slinear, quadratic, cubic. It defaults to linear.

  • interp_fill (array-like or (array-like, array_like) or “extrapolate”, optional) -- Parameter to determine what to do outside data bounds. Passed to the scipy.interpolate.interp1d function as fill_value. If blank, interpolation will not predict outside the bounds of data.

  • pressure_unit (str) -- Unit the pressure is specified in. If None, it defaults to internal isotherm units.

  • pressure_mode (str) -- The mode the pressure is passed in. If None, it defaults to internal isotherm mode.

  • loading_unit (str, optional) -- Unit in which the loading should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • loading_basis ({None, 'mass', 'molar', 'volume_gas', 'volume_liquid'}) -- The basis on which to return the loading, if possible. If None, returns on the basis the isotherm is currently in.

  • material_unit (str, optional) -- Material unit in which the data should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • material_basis ({None, 'mass', 'volume', 'molar'}) -- Material basis on which to return the data, if possible. If None, returns on the basis the isotherm is currently in.

Returns:

float or array -- Predicted loading at pressure P.

spreading_pressure_at(pressure: List[float], branch: str = 'ads', pressure_unit: str | None = None, pressure_mode: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None, interp_fill: float | Tuple[float, float] | str | None = None) ndarray[source]#

Calculate reduced spreading pressure at a bulk adsorbate pressure P.

Use numerical quadrature on isotherm data points to compute the reduced spreading pressure via the integral:

\[\Pi(p) = \int_0^p \frac{q(\hat{p})}{ \hat{p}} d\hat{p}.\]

In this integral, the isotherm \(q(\hat{p})\) is represented by a linear interpolation of the data.

For in-detail explanations, check reference [1].

Parameters:
  • pressure (float) -- Pressure (in corresponding units as data in instantiation).

  • branch ({'ads', 'des'}) -- The branch of the use for calculation. Defaults to adsorption.

  • loading_unit (str) -- Unit the loading is specified in. If None, it defaults to internal isotherm units.

  • pressure_unit (str) -- Unit the pressure is returned in. If None, it defaults to internal isotherm units.

  • material_basis (str) -- The basis the loading is passed in. If None, it defaults to internal isotherm basis.

  • pressure_mode (str) -- The mode the pressure is returned in. If None, it defaults to internal isotherm mode.

  • interp_fill (array-like or (array-like, array_like) or “extrapolate”, optional) -- Parameter to determine what to do outside data bounds. Passed to the scipy.interpolate.interp1d function as fill_value. If blank, interpolation will not predict outside the bounds of data.

Returns:

float -- Spreading pressure, \(\Pi\).

References

Model Isotherm#

Class representing a model of and isotherm.

class pygaps.core.modelisotherm.ModelIsotherm(pressure: List[float] | None = None, loading: List[float] | None = None, isotherm_data: DataFrame | None = None, pressure_key: str | None = None, loading_key: str | None = None, branch: str = 'ads', model: str | List[str] | Any | None = None, param_guess: dict | None = None, param_bounds: dict | None = None, optimization_params: dict | None = None, verbose: bool = False, **other_properties)[source]#

Class to characterize pure-component isotherm data with an analytical model. Data fitting is done during instantiation.

A ModelIsotherm class is instantiated by passing it the pure-component adsorption isotherm in the form of a Pandas DataFrame.

Parameters:
  • pressure (list) -- Create an isotherm directly from an array. Values for pressure. If the isotherm_data dataframe is specified, these values are ignored.

  • loading (list) -- Create an isotherm directly from an array. Values for loading. If the isotherm_data dataframe is specified, these values are ignored.

  • isotherm_data (DataFrame) -- Pure-component adsorption isotherm data.

  • pressure_key (str) -- Column of the pandas DataFrame where the pressure is stored.

  • loading_key (str) -- Column of the pandas DataFrame where the loading is stored.

  • model (str or Model class) -- The model to be used to describe the isotherm.

  • param_guess (dict) -- Starting guess for model parameters in the data fitting routine.

  • param_bounds (dict) -- Bounds for model parameters in the data fitting routine (applicable to some models).

  • branch (['ads', 'des'], optional) -- The branch on which the model isotherm is based on. It is assumed to be the adsorption branch, as it is the most commonly modelled part, although may set to desorption as well.

  • material (str) -- Name of the material on which the isotherm is measured.

  • adsorbate (str) -- Isotherm adsorbate.

  • temperature (float) -- Isotherm temperature.

Other Parameters:
  • optimization_params (dict) -- Dictionary to be passed to the minimization function to use in fitting model to data. See here.

  • pressure_mode (str, optional) -- The pressure mode, either 'absolute' pressure or 'relative' ('relative%') in the form of p/p0.

  • pressure_unit (str, optional) -- Unit of pressure, if applicable.

  • loading_basis (str, optional) -- Whether the adsorbed amount is in terms of either 'volume_gas' 'volume_liquid', 'molar', 'mass', or a fraction/percent basis.

  • loading_unit (str, optional) -- Unit in which the loading basis is expressed.

  • material_basis (str, optional) -- Whether the underlying material is in terms of 'per volume' 'per molar amount' or 'per mass' of material.

  • material_unit (str, optional) -- Unit in which the material basis is expressed.

Notes

Models supported are found in :mod:modelling. Here, \(L\) is the adsorbate uptake and \(P\) is pressure (fugacity technically).

classmethod from_isotherm(isotherm: BaseIsotherm, pressure: List[float] | None = None, loading: List[float] | None = None, isotherm_data: DataFrame | None = None, pressure_key: str | None = None, loading_key: str | None = None, branch: str = 'ads', model: str | List[str] | Any | None = None, param_guess: dict | None = None, param_bounds: dict | None = None, optimization_params: dict | None = None, verbose: bool = False)[source]#

Construct a ModelIsotherm using a parent isotherm as the template for all the parameters.

Parameters:
  • isotherm (BaseIsotherm) -- An instance of the BaseIsotherm parent class.

  • pressure (list) -- Create an isotherm directly from an array. Values for pressure. If the isotherm_data dataframe is specified, these values are ignored.

  • loading (list) -- Create an isotherm directly from an array. Values for loading. If the isotherm_data dataframe is specified, these values are ignored.

  • isotherm_data (DataFrame) -- Pure-component adsorption isotherm data.

  • pressure_key (str) -- Column of the pandas DataFrame where the pressure is stored.

  • loading_key (str) -- Column of the pandas DataFrame where the loading is stored.

  • branch (['ads', 'des'], optional) -- The branch on which the model isotherm is based on. It is assumed to be the adsorption branch, as it is the most commonly modelled.

  • model (str) -- The model to be used to describe the isotherm.

  • param_guess (dict) -- Starting guess for model parameters in the data fitting routine.

  • param_bounds (dict) -- Bounds for model parameters in the data fitting routine.

  • optimization_params (dict) -- Dictionary to be passed to the minimization function to use in fitting model to data. See here. Defaults to "Nelder-Mead".

  • verbose (bool) -- Prints out extra information about steps taken.

classmethod from_pointisotherm(isotherm, branch: str = 'ads', model: str | List[str] | Any | None = None, param_guess: dict | None = None, param_bounds: dict | None = None, optimization_params: dict | None = None, verbose: bool = False)[source]#

Constructs a ModelIsotherm using data from a PointIsotherm and all its parameters.

Parameters:
  • isotherm (PointIsotherm) -- An instance of the PointIsotherm parent class to model.

  • branch ([None, 'ads', 'des'], optional) -- Branch of isotherm to model. Defaults to adsorption branch.

  • model (str, list, 'guess') -- The model to be used to describe the isotherm. Give a single model name ("Langmuir") to fit it. Give a list of many model names to try them all and return the best fit ([`Henry, Langmuir]`). Specify "guess" to try all available models.

  • param_guess (dict, optional) -- Starting guess for model parameters in the data fitting routine.

  • param_bounds (dict) -- Bounds for model parameters in the data fitting routine.

  • optimization_params (dict, optional) -- Dictionary to be passed to the minimization function to use in fitting model to data. See here.

  • verbose (bool) -- Prints out extra information about steps taken.

classmethod guess(pressure: List[float] | None = None, loading: List[float] | None = None, isotherm_data: DataFrame | None = None, pressure_key: str | None = None, loading_key: str | None = None, branch: str = 'ads', models='guess', optimization_params: dict | None = None, verbose: bool = False, **other_properties)[source]#

Attempt to model the data using supplied list of model names, then return the one with the best RMS fit.

May take a long time depending on the number of datapoints.

Parameters:
  • pressure (list) -- Create an isotherm directly from an array. Values for pressure. If the isotherm_data dataframe is specified, these values are ignored.

  • loading (list) -- Create an isotherm directly from an array. Values for loading. If the isotherm_data dataframe is specified, these values are ignored.

  • isotherm_data (DataFrame) -- Pure-component adsorption isotherm data.

  • pressure_key (str) -- Column of the pandas DataFrame where the pressure is stored.

  • loading_key (str) -- Column of the pandas DataFrame where the loading is stored.

  • models ('guess', list of model names) -- Attempt to guess which model best fits the isotherm data from the model name list supplied. If set to 'guess' A calculation of all models available will be performed, therefore it will take a longer time.

  • optimization_params (dict) -- Dictionary to be passed to the minimization function to use in fitting model to data. See here.

  • branch (['ads', 'des'], optional) -- The branch on which the model isotherm is based on. It is assumed to be the adsorption branch, as it is the most commonly modelled part, although may set to desorption as well.

  • verbose (bool, optional) -- Prints out extra information about steps taken.

  • other_properties -- Any other parameters of the isotherm which should be stored internally.

print_info(**plot_iso_args)[source]#

Print a short summary of the isotherm parameters and a graph.

Parameters:

show (bool, optional) -- Specifies if the graph is shown automatically or not.

Other Parameters:

plot_iso_args (dict) -- options to be passed to pygaps.plot_iso()

Returns:

axes (matplotlib.axes.Axes or numpy.ndarray of them)

plot(**plot_iso_args)[source]#

Plot the isotherm using pygaps.plot_iso().

Parameters:

show (bool, optional) -- Specifies if the graph is shown automatically or not.

Other Parameters:

plot_iso_args (dict) -- options to be passed to pygaps.plot_iso()

Returns:

axes (matplotlib.axes.Axes or numpy.ndarray of them)

has_branch(branch: str) bool[source]#

Check if the isotherm has an specific branch.

Parameters:

branch ({None, 'ads', 'des'}) -- The branch of the data to check for.

Returns:

bool -- Whether the data exists or not.

pressure(points: int = 60, branch: str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, limits: Tuple[float, float] | None = None, indexed: bool = False)[source]#

Return a numpy.linspace generated array with a fixed number of equidistant points within the pressure range the model was created.

Parameters:
  • points (int) -- The number of points to get.

  • branch ({None, 'ads', 'des'}) -- The branch of the pressure to return. If None, returns the branch the isotherm is modelled on.

  • pressure_unit (str, optional) -- Unit in which the pressure should be returned. If None it defaults to which pressure unit the isotherm is currently in.

  • pressure_mode ({None, 'absolute', 'relative', 'relative%'}) -- The mode in which to return the pressure, if possible. If None, returns mode the isotherm is currently in.

  • limits ([float, float], optional) -- Minimum and maximum pressure limits. Put None or -+np.inf for no limit.

  • indexed (bool, optional) -- If this is specified to true, then the function returns an indexed pandas.Series with the columns requested instead of an array.

Returns:

numpy.array or pandas.Series -- Pressure points in the model pressure range.

loading(points: int = 60, branch: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None, limits: Tuple[float, float] | None = None, indexed: bool = False)[source]#

Return the loading calculated at equidistant pressure points within the pressure range the model was created.

Parameters:
  • points (int) -- The number of points to get.

  • branch ({None, 'ads', 'des'}) -- The branch of the loading to return. If None, returns entire dataset.

  • loading_unit (str, optional) -- Unit in which the loading should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • loading_basis ({None, 'mass', 'volume_gas', 'volume_liquid'}) -- The basis on which to return the loading, if possible. If None, returns on the basis the isotherm is currently in.

  • material_unit (str, optional) -- Unit in which the material should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • material_basis ({None, 'mass', 'volume'}) -- The basis on which to return the material, if possible. If None, returns on the basis the isotherm is currently in.

  • limits ([float, float], optional) -- Minimum and maximum loading limits. Put None or -+np.inf for no limit.

  • indexed (bool, optional) -- If this is specified to true, then the function returns an indexed pandas.Series with the columns requested instead of an array.

Returns:

numpy.array or pandas.Series -- Loading calculated at points the model pressure range.

property other_keys#

Return column names of any supplementary data points.

pressure_at(loading: float | List[float], branch: str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None)[source]#

Compute pressure at loading L, given stored model parameters.

Depending on the model, may be calculated directly or through a numerical minimisation.

Parameters:
  • loading (float or array) -- Loading at which to compute pressure.

  • branch ({None, 'ads', 'des'}) -- The branch the calculation is based on.

  • pressure_unit (str) -- Unit the pressure is returned in. If None, it defaults to internal isotherm units.

  • pressure_mode (str) -- The mode the pressure is returned in. If None, it defaults to internal isotherm mode.

  • loading_unit (str) -- Unit the loading is specified in. If None, it defaults to internal isotherm units.

  • loading_basis ({None, 'mass', 'volume_gas', 'volume_liquid'}) -- The basis the loading is specified in. If None, assumes the basis the isotherm is currently in.

  • material_unit (str, optional) -- Unit in which the material is passed in. If None it defaults to which loading unit the isotherm is currently in

  • material_basis (str) -- The basis the loading is passed in. If None, it defaults to internal isotherm basis.

Returns:

float or array -- Predicted pressure at loading L using fitted model parameters.

loading_at(pressure: float | List[float], branch: str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None, loading_unit: str | None = None, loading_basis: str | None = None, material_unit: str | None = None, material_basis: str | None = None)[source]#

Compute loading at pressure P, given stored model parameters.

Depending on the model, may be calculated directly or through a numerical minimisation.

Parameters:
  • pressure (float or array) -- Pressure at which to compute loading.

  • branch ({None, 'ads', 'des'}) -- The branch the calculation is based on.

  • pressure_unit (str) -- Unit the pressure is specified in. If None, it defaults to internal isotherm units.

  • pressure_mode (str) -- The mode the pressure is passed in. If None, it defaults to internal isotherm mode.

  • loading_unit (str, optional) -- Unit in which the loading should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • loading_basis ({None, 'mass', 'volume_gas', 'volume_liquid'}) -- The basis on which to return the loading, if possible. If None, returns on the basis the isotherm is currently in.

  • material_unit (str, optional) -- Unit in which the material should be returned. If None it defaults to which loading unit the isotherm is currently in.

  • material_basis ({None, 'mass', 'volume'}) -- The basis on which to return the material, if possible. If None, returns on the basis the isotherm is currently in.

Returns:

float or array -- Predicted loading at pressure P using fitted model parameters.

spreading_pressure_at(pressure: float | List[float], branch: str | None = None, pressure_unit: str | None = None, pressure_mode: str | None = None)[source]#

Calculate reduced spreading pressure at a bulk gas pressure P.

The reduced spreading pressure is an integral involving the isotherm \(L(P)\):

\[\Pi(p) = \int_0^p \frac{L(\hat{p})}{ \hat{p}} d\hat{p},\]

which is computed analytically or numerically, depending on the model used.

Parameters:
  • pressure (float) -- Pressure (in corresponding units as data in instantiation)

  • branch ({'ads', 'des'}) -- The branch of the use for calculation. Defaults to adsorption.

  • pressure_unit (str) -- Unit the pressure is returned in. If None, it defaults to internal isotherm units.

  • pressure_mode (str) -- The mode the pressure is returned in. If None, it defaults to internal isotherm mode.

Returns:

float -- Spreading pressure, \(\Pi\).

Adsorbate#

Contains the adsorbate class.

class pygaps.core.adsorbate.Adsorbate(name: str, store: bool = False, **properties)[source]#

An unified class descriptor for an adsorbate.

Its purpose is to expose properties such as adsorbate name, and formula, as well as physical properties, such as molar mass vapour pressure, etc.

The properties can be either calculated through a wrapper over CoolProp or supplied in the initial adsorbate creation. All parameters passed are saved in a self.parameters dictionary.

Parameters:

name (str) -- The name which should be used for this adsorbate.

Other Parameters:
  • alias (list[str]) -- Other names the same adsorbate might take. Example: name=propanol, alias=['1-propanol']. pyGAPS disregards capitalisation (Propanol = propanol = PROPANOL).

  • formula (str) -- A chemical formula for the adsorbate in LaTeX form: He/N_{2}/C_{2}H_{4} etc.

  • backend_name (str) -- Used for integration with CoolProp/REFPROP. For a list of names look at the CoolProp list of fluids

  • molar_mass (float) -- Custom value for molar mass (otherwise obtained through CoolProp).

  • p_triple (float) -- Custom value for triple point pressure (otherwise obtained through CoolProp).

  • t_triple (float) -- Custom value for triple point temperature (otherwise obtained through CoolProp).

  • p_critical (float) -- Custom value for critical point pressure (otherwise obtained through CoolProp).

  • t_critical (float) -- Custom value for critical point temperature (otherwise obtained through CoolProp).

  • saturation_pressure (float) -- Custom value for saturation pressure (otherwise obtained through CoolProp).

  • surface_tension (float) -- Custom value for surface tension (otherwise obtained through CoolProp).

  • liquid_density (float) -- Custom value for liquid density (otherwise obtained through CoolProp).

  • liquid_molar_density (float) -- Custom value for liquid molar density (otherwise obtained through CoolProp).

  • gas_density (float) -- Custom value for gas density (otherwise obtained through CoolProp).

  • gas_molar_density (float) -- Custom value for gas molar density (otherwise obtained through CoolProp).

  • enthalpy_vaporisation (float) -- Custom value for enthalpy of vaporisation/liquefaction (otherwise obtained through CoolProp).

  • enthalpy_liquefaction (float) -- Custom value for enthalpy of vaporisation/liquefaction (otherwise obtained through CoolProp).

Notes

The members of the properties dictionary are left at the discretion of the user, to keep the class extensible. There are, however, some unique properties which are used by calculations in other modules listed in the other parameters section above.

These properties can be either calculated by CoolProp (if the adsorbate exists in CoolProp/REFPROP) or taken from the parameters dictionary. They are best accessed using the associated function.

Calculated:

my_adsorbate.surface_tension(77)

Value from dictionary:

my_adsorbate.surface_tension(77, calculate=False)

If available, the underlying CoolProp state object (http://www.coolprop.org/coolprop/LowLevelAPI.html) can be accessed directly through the backend variable. For example, to get the CoolProp-calculated critical pressure:

adsorbate.backend.p_critical()
properties#

Adsorbate properties

print_info()[source]#

Print a short summary of all the adsorbate parameters.

classmethod find(name: str)[source]#

Get the specified adsorbate from the master list.

Parameters:

name (str) -- The name of the adsorbate to search

Returns:

Adsorbate -- Instance of class

Raises:

ParameterError -- If it does not exist in list.

property backend#

Return the CoolProp state associated with the fluid.

property formula: str#

Return the adsorbate formula.

to_dict() dict[source]#

Return a dictionary of the adsorbate class.

Is the same dictionary that was used to create it.

Returns:

dict -- dictionary of all parameters

get_prop(prop: str)[source]#

Return a property from the 'properties' dictionary.

Parameters:

prop (str) -- property name desired

Returns:

str/float -- Value of property in the properties dict

Raises:

ParameterError -- If the the property does not exist in the class dictionary.

property backend_name: str#

Get the CoolProp interaction name of the adsorbate.

Returns:

str -- Value of backend_name in the properties dict

Raises:

ParameterError -- If the the property does not exist in the class dictionary.

molar_mass(calculate: bool = True) float[source]#

Return the molar mass of the adsorbate.

Parameters:

calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Molar mass in g/mol.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

p_triple(calculate: bool = True) float[source]#

Return the triple point pressure, in Pa.

Parameters:

calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Triple point pressure in Pa.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

t_triple(calculate: bool = True) float[source]#

Return the triple point temperature, in K.

Parameters:

calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Triple point temperature in K.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

p_critical(calculate: bool = True) float[source]#

Return the critical point pressure, in Pa.

Parameters:

calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Critical point pressure in Pa.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

t_critical(calculate: bool = True) float[source]#

Return the critical point temperature, in K.

Parameters:

calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Critical point temperature in K.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

pressure_saturation(temp: float, unit: str | None = None, calculate: bool = True) float[source]#

Get the saturation pressure at a particular temperature, in desired unit (default Pa).

Alias for 'saturation_pressure'

Parameters:
  • temp (float) -- Temperature at which the pressure is desired in K.

  • unit (str) -- Unit in which to return the saturation pressure. If not specifies defaults to Pascal.

  • calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Pressure in unit requested.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

saturation_pressure(temp: float, unit: str | None = None, calculate: bool = True) float[source]#

Get the saturation pressure at a particular temperature, in desired unit (default Pa).

Parameters:
  • temp (float) -- Temperature at which the pressure is desired in K.

  • unit (str) -- Unit in which to return the saturation pressure. If not specifies defaults to Pascal.

  • calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Pressure in unit requested.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

surface_tension(temp: float, calculate: bool = True) float[source]#

Get the surface tension at a particular temperature, in mN/m.

Parameters:
  • temp (float) -- Temperature at which the surface_tension is desired in K.

  • calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Surface tension in mN/m.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

liquid_density(temp: float, calculate: bool = True) float[source]#

Get the liquid density at a particular temperature, in g/cm3.

Parameters:
  • temp (float) -- Temperature at which the liquid density is desired in K.

  • calculate (bool, optional.) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Liquid density in g/cm3.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

liquid_molar_density(temp: float, calculate: bool = True) float[source]#

Get the liquid molar density at a particular temperature, in mol/cm3.

Parameters:
  • temp (float) -- Temperature at which the liquid density is desired in K.

  • calculate (bool, optional.) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Molar liquid density in mol/cm3.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

gas_density(temp: float, calculate: bool = True) float[source]#

Get the gas molar density at a particular temperature, in g/cm3.

Parameters:
  • temp (float) -- Temperature at which the gas density is desired in K.

  • calculate (bool, optional.) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Gas density in g/cm3.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

gas_molar_density(temp: float, calculate: bool = True) float[source]#

Get the gas density at a particular temperature, in mol/cm3.

Parameters:
  • temp (float) -- Temperature at which the gas density is desired in K.

  • calculate (bool, optional.) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Molar gas density in mol/cm3.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

enthalpy_vaporisation(temp: float | None = None, press: float | None = None, calculate: bool = True) float[source]#

Get the enthalpy of vaporisation at a particular temperature, in kJ/mol.

Parameters:
  • temp (float) -- Temperature at which the enthalpy of vaporisation is desired, in K.

  • calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Enthalpy of vaporisation in kJ/mol.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

enthalpy_liquefaction(temp: float | None = None, press: float | None = None, calculate: bool = True) float[source]#

Get the enthalpy of liquefaction at a particular temperature, in kJ/mol.

Parameters:
  • temp (float) -- Temperature at which the enthalpy of liquefaction is desired, in K.

  • calculate (bool, optional) -- Whether to calculate the property or look it up in the properties dictionary, default - True.

Returns:

float -- Enthalpy of liquefaction in kJ/mol.

Raises:
  • ParameterError -- If the calculation is not requested and the property does not exist in the class dictionary.

  • CalculationError -- If it cannot be calculated, due to a physical reason.

Material#

Contains the material class.

class pygaps.core.material.Material(name: str, store: bool = False, **properties)[source]#

An unified descriptor for an adsorbent material.

Parameters:

name (str) -- The name of the material.

Other Parameters:
  • density (float) -- Material density.

  • molar_mass (float) -- Material molar mass.

Notes

The members of the properties are left at the discretion of the user. There are, however, some unique properties which can be set.

print_info()[source]#

Print a short summary of all the material parameters.

classmethod find(name: str)[source]#

Get the specified material from the master list.

Parameters:

name (str) -- The name of the material to search.

Returns:

Material -- Instance of class.

Raises:

ParameterError -- If it does not exist in list.

to_dict() dict[source]#

Return a dictionary of the material class.

Is the same dictionary that was used to create it.

Returns:

dict -- Dictionary of all parameters.

property density: float#

Material density, in g/cm3 (optional).

property molar_mass: float#

Material molar mass, in g/mol (optional).

get_prop(prop: str)[source]#

Return a property from the internal dictionary.

Parameters:

prop (str) -- Property name desired.

Returns:

str/float -- Value of property in the properties dict.

Raises:

ParameterError -- If it does not exist.

Material characterisation#

BET surface area#

This module contains BET area calculations.

pygaps.characterisation.area_bet.area_BET(isotherm: PointIsotherm | ModelIsotherm, branch: str = 'ads', p_limits: tuple[float, float] = None, verbose: bool = False)[source]#

Calculate BET area from an isotherm.

The optional p_limits parameter allows to specify the upper and lower pressure limits to calculate the BET area, otherwise the limits will be automatically selected based on the Rouquerol rules.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- The isotherm of which to calculate the BET surface area.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • p_limits (tuple[float, float], optional) -- Pressure range in which to perform the calculation.

  • verbose (bool, optional) -- Prints extra information and plots graphs of the calculation.

Returns:

dict -- A dictionary of results with the following components. The basis of these results will be derived from the basis of the isotherm (per mass, per volume, or per mole of adsorbent):

  • area (float) : calculated BET surface area, in m2/unit of adsorbent

  • c_const (float) : the C constant in the BET equation, unitless

  • n_monolayer (float) : the amount adsorbed at statistical monolayer, in mmol

  • p_monolayer (float) : the pressure at which statistical monolayer is chosen, relative

  • bet_slope (float) : slope of the BET plot

  • bet_intercept (float) : intercept of the BET plot

  • corr_coef (float) : correlation coefficient of the linear region in the BET plot

Raises:

Notes

Description

The BET method [1] is one of the first standardised methods to calculate the surface area of a porous material. It is generally applied on isotherms obtained through N2 adsorption at 77K, although other adsorbates (Ar, Kr) have been used.

It assumes that the adsorption happens on the surface of the material in incremental layers according to the BET theory. Even if the adsorbent is mesoporous, the initial amount adsorbed (usually between 0.05 - 0.4 \(p/p_0\)) can be modelled through the BET equation:

\[\frac{p/p_0}{n_{ads} (1-p/p_0)} = \frac{1}{n_{m} C} + \frac{C - 1}{n_{m} C}(p/p_0)\]

If we plot the isotherm points as \(\frac{p/p_0}{n_{ads}(1-p/p_0)}\) versus \(p/p_0\), a linear region can usually be found. The slope and intercept of this line can then be used to calculate \(n_{m}\), the amount adsorbed at the statistical monolayer, as well as \(C\), the BET constant.

\[ \begin{align}\begin{aligned}n_{m} = \frac{1}{s+i}\\C = \frac{s}{i} + 1\end{aligned}\end{align} \]

The surface area can then be calculated by using the moles adsorbed at the statistical monolayer. If the specific area taken by one of the adsorbate molecules on the surface is known, it is inserted in the following equation together with Avogadro's number:

\[a_{BET} = n_m A_N \sigma\]

Limitations

While a standard for surface area determinations, the BET area should be used with care, as there are many assumptions made in the calculation. To augment the validity of the BET method, Rouquerol [2] proposed several checks to ensure that the BET region selected is valid:

  • The BET constant (\(C\)) obtained should be positive.

  • In the corresponding Rouquerol plot where \(n_{ads}(1-p/p_0)\) is plotted with respect to \(p/p_0\), the points chosen for BET analysis should be strictly increasing.

  • The loading at the statistical monolayer should be situated within the limits of the BET region.

This module implements all these checks.

Regardless, the BET surface area should still be interpreted carefully. The following assumptions are implicitly made in this approach:

  • Adsorption takes place on the pore surface. Microporous materials which have pores in similar size as the molecule adsorbed cannot posses a realistic surface area.

  • The cross-sectional area of the molecule on the surface cannot be guaranteed For example, nitrogen has been known to adopt different orientations on the surface of some materials due to inter-molecular forces, which effectively lowers its cross-sectional area.

  • No account is made for heterogeneous adsorbate-adsorbent interaction in the BET theory.

References

pygaps.characterisation.area_bet.area_BET_raw(pressure: list[float], loading: list[float], cross_section: float, p_limits: tuple[float, float] = None)[source]#

Calculate BET-determined surface area.

This is a 'bare-bones' function to calculate BET surface area 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 (list[float]) -- Pressures, relative.

  • loading (list[float]) -- Loadings, in mol/basis.

  • cross_section (float) -- Adsorbed cross-section of the molecule of the adsorbate, in nm.

  • p_limits (tuple[float, float], optional) -- Pressure range in which to perform the calculation.

Returns:

  • area (float) -- Calculated BET surface area.

  • c_const (float) -- C constant from the BET equation.

  • n_monolayer (float) -- Adsorbed quantity in the statistical monolayer.

  • p_monolayer (float) -- Pressure at the statistical monolayer.

  • slope (float) -- Calculated slope of the BET plot.

  • intercept (float) -- Calculated intercept of the BET plot.

  • minimum (float) -- Minimum point taken for the linear region.

  • maximum (float) -- Maximum point taken for the linear region.

  • corr_coef (float) -- Correlation coefficient of the straight line in the BET plot.

Langmuir surface area#

This module contains Langmuir area calculations.

pygaps.characterisation.area_lang.area_langmuir(isotherm: PointIsotherm | ModelIsotherm, branch: str = 'ads', p_limits: tuple[float, float] = None, verbose: bool = False)[source]#

Calculate the Langmuir area of an isotherm.

The optional p_limits parameter allows to specify the upper and lower pressure limits to calculate the Langmuir area, otherwise the limits will be automatically set to 5-90% of isotherm pressure range.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- The isotherm of which to calculate the Langmuir surface area.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • p_limits (tuple[float, float], optional) -- Pressure range in which to perform the calculation.

  • verbose (bool, optional) -- Prints extra information and plots graphs of the calculation.

Returns:

dict -- A dictionary of results with the following components. The basis of these results will be derived from the basis of the isotherm (per mass, per volume, or per mole of adsorbent):

  • area (float) : calculated Langmuir surface area, in m2/unit of material

  • langmuir_const (float) : the constant in the Langmuir fit

  • n_monolayer (float) : the amount adsorbed at the monolayer

  • langmuir_slope (float) : slope of the Langmuir plot

  • langmuir_intercept (float) : intercept of the Langmuir plot

  • corr_coef (float) : correlation coefficient of the linear region in the Langmuir plot

Raises:

Notes

Description

The Langmuir theory [1], proposed at the start of the 20th century, states that adsorption happens on individual active sites on a surface in a single layer. It is derived based on several assumptions.

  • All sites are equivalent and have the same chance of being occupied.

  • Each adsorbate molecule can occupy one adsorption site.

  • There are no interactions between adsorbed molecules.

  • The rates of adsorption and desorption are proportional to the number of sites currently free and currently occupied, respectively.

  • Adsorption is complete when all sites are filled.

The Langmuir equation is then:

\[n = n_m\frac{KP}{1+KP}\]

The equation can be rearranged as:

\[\frac{P}{n} = \frac{1}{K n_m} + \frac{P}{n_m}\]

Assuming the data can be fitted with a Langmuir model, by plotting \(\frac{P}{n}\) against pressure, a line will be obtained. The slope and intercept of this line can then be used to calculate \(n_{m}\), the amount adsorbed at the monolayer, as well as K, the Langmuir constant.

\[ \begin{align}\begin{aligned}n_m = \frac{1}{s}\\K = \frac{1}{i * n_m}\end{aligned}\end{align} \]

The surface area can then be calculated by using the moles adsorbed at the monolayer. If the specific area taken by one of the adsorbate molecules on the surface is known, it is inserted in the following equation together with Avogadro's number:

\[a(Langmuir) = n_m A_N \sigma\]

Limitations

The Langmuir method for determining surface area assumes that only one single layer is adsorbed on the surface of the material. As most adsorption processes (except chemisorption) don't follow this behaviour, it is important to regard the Langmuir surface area as an estimate.

References

pygaps.characterisation.area_lang.area_langmuir_raw(pressure: list[float], loading: list[float], cross_section: float, p_limits: tuple[float, float] = None)[source]#

Calculate Langmuir-determined surface area.

This is a 'bare-bones' function to calculate Langmuir surface area 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 (list[float]) -- Pressures, relative.

  • loading (list[float]) -- Loadings, in mol/basis.

  • cross_section (float) -- Adsorbed cross-section of the molecule of the adsorbate, in nm.

  • p_limits (tuple[float, float], optional) -- Pressure range in which to perform the calculation.

Returns:

  • langmuir_area (float) -- Calculated Langmuir surface area.

  • langmuir_const (float) -- K constant from the Langmuir equation.

  • n_monolayer (float) -- Adsorbed quantity in the monolayer.

  • slope (float) -- Calculated slope of the Langmuir plot.

  • intercept (float) -- Calculated intercept of the Langmuir plot.

  • minimum (float) -- Minimum point taken for the linear region.

  • maximum (float) -- Maximum point taken for the linear region.

  • corr_coef (float) -- Correlation coefficient of the straight line in the Langmuir plot.

t-plot#
Module doc#

This module contains the t-plot calculation.

pygaps.characterisation.t_plots.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)[source]#

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:

Notes

Description

The t-plot method [1] 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:

\[A = \frac{s M_m}{\rho_{l}}\]

where \(\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:

\[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

pygaps.characterisation.t_plots.t_plot_raw(loading: list[float], pressure: list[float], thickness_model: Callable[[float], float], liquid_density: float, adsorbate_molar_mass: float, t_limits: tuple[float, float] = None)[source]#

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.

Available thickness models#

See Available thickness models.

Alpha-s plot#

This module contains the alpha-s calculation.

pygaps.characterisation.alphas_plots.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)[source]#

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:

Notes

Description

In order to extend the t-plot analysis with other adsorbents and non-standard thickness curves, the \(\alpha_s\) method was devised [1]. 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 \(\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

\[\alpha_s = \frac{n_a}{n_{0.4}}\]

The analysis then proceeds as in the t-plot method.

The slope of the linear section (\(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.

\[A = \frac{s A_{ref}}{(n_{ref})_{0.4}}\]

If the region selected is after a vertical deviation, the intercept (\(i\)) of the line will no longer pass through the origin. This intercept be used to calculate the pore volume through the following equation:

\[V_{ads} = \frac{i M_m}{\rho_{l}}\]

Limitations

The reference isotherm chosen for the \(\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

pygaps.characterisation.alphas_plots.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)[source]#

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.

Dubinin-Radushkevich and Dubinin-Astakov plots#

Dubinin-Radushkevich equation and related plots.

pygaps.characterisation.dr_da_plots.dr_plot(isotherm: PointIsotherm | ModelIsotherm, branch: str = 'ads', p_limits: tuple[float, float] = None, verbose: bool = False)[source]#

Calculate pore volume and effective adsorption potential through a Dubinin-Radushkevich (DR) plot.

The optional p_limits parameter allows to specify the upper and lower pressure limits for the calculation, otherwise the entire range will be used.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- The isotherm to use for the DR plot.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • p_limits ([float, float], optional) -- Pressure range in which to perform the calculation.

  • verbose (bool, optional) -- Prints extra information and plots the resulting fit graph.

Returns:

dict -- Dictionary of results with the following parameters:

  • pore_volume (float) : calculated total micropore volume, cm3/material unit

  • adsorption_potential (float) : calculated adsorption potential, in kJ/mol

Raises:

Notes

The Dubinin-Radushkevich equation [1] is an extension of the potential theory of Polanyi, which asserts that molecules near a surface are subjected to a potential field. The adsorbate at the surface is in a liquid state and its local pressure is conversely equal to the vapour pressure at the adsorption temperature.

Pore filling progresses as a function of total adsorbed volume \(V_{t}\).

\[V_{ads} = V_{t} \exp\Big[\Big(\frac{\Delta G}{\varepsilon}\Big)^{2}\Big]\]

Here \(\Delta G\) is the change in Gibbs free energy \(\Delta G = - RT \ln(p_0/p)\) and \(\varepsilon\) is a characteristic energy of adsorption. Substituting:

\[V_{ads} = V_{t} - e^{-\Big(\frac{RT}{\varepsilon}\Big)^2 \ln^2{\frac{p_0}{p}}} = V_{t} - e^{-D \ln^2{\frac{p_0}{p}}}\]

If an experimental isotherm is consistent with the DR model, the equation can be used to obtain the total pore volume and energy of adsorption. The DR equation is linearised:

\[\ln{V_{ads}} = \ln{V_{t}} - \Big(\frac{RT}{\varepsilon}\Big)^2 \ln^2{\Big[\frac{p_0}{p}}\Big]\]

Isotherm loading is converted to volume adsorbed by assuming that the density of the adsorbed phase is equal to bulk liquid density at the isotherm temperature. Afterwards \(\ln{V_{ads}}\) is plotted against \(\ln^2{p_0/p}\), and fitted with a best-fit line. The intercept of this line can be used to calculate the total pore volume, while the slope is proportional to the characteristic energy of adsorption \(\varepsilon\).

References

pygaps.characterisation.dr_da_plots.da_plot(isotherm, exp: float = None, branch: str = 'ads', p_limits: tuple[float, float] = None, verbose: bool = False)[source]#

Calculate pore volume and effective adsorption potential through a Dubinin-Astakov (DA) plot. Optionally find a best exponent fit to the DA line.

The function accepts a pyGAPS isotherm, with an ability to select the pressure limits for point selection.

Parameters:
  • isotherm (PointIsotherm) -- The isotherm to use for the DA plot.

  • exp (float, optional) -- The exponent to use in the DA equation. If not specified a best fit exponent will be calculated between 1 and 3.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • p_limits ([float, float], optional) -- Pressure range in which to perform the calculation.

  • verbose (bool, optional) -- Prints extra information and plots the resulting fit graph.

Returns:

dict -- Dictionary of results with the following parameters:

  • pore_volume (float) : calculated total micropore volume, cm3/material unit

  • adsorption_potential (float) : calculated adsorption potential, in kJ/mol

  • exponent (float) : the exponent, only if not specified, unitless

Notes

The Dubinin-Astakov equation [2] is an expanded form of the Dubinin-Radushkevich model. It is an extension of the potential theory of Polanyi, which asserts that molecules near a surface are subjected to a potential field. The adsorbate at the surface is in a liquid state and its local pressure is conversely equal to the vapour pressure at the adsorption temperature.

Pore filling progresses as a function of total adsorbed volume \(V_{t}\).

\[V_{ads} = V_{t} \exp\Big[\Big(\frac{\Delta G}{\varepsilon}\Big)^{n}\Big]\]

Here \(\Delta G\) is the change in Gibbs free energy \(\Delta G = - RT \ln(p_0/p)\) and \(\varepsilon\) is a characteristic energy of adsorption. The exponent \(n\) is a fitting coefficient, often taken between 1 (described as surface adsorption) and 3 (micropore adsorption). The exponent can also be related to surface heterogeneity. Substituting:

\[V_{ads} = V_{t} - e^{-\Big(\frac{RT}{\varepsilon}\Big)^n \ln^n{\frac{p_0}{p}}} = V_{t} - e^{-D \ln^n{\frac{p_0}{p}}}\]

If an experimental isotherm is consistent with the DA model, the equation can be used to obtain the total pore volume and energy of adsorption. The DA equation is first linearised:

\[\ln{V_{ads}} = \ln{V_{t}} - \Big(\frac{RT}{\varepsilon}\Big)^n \ln^n{\Big[\frac{p_0}{p}}\Big]\]

Isotherm loading is converted to volume adsorbed by assuming that the density of the adsorbed phase is equal to bulk liquid density at the isotherm temperature. Afterwards \(\ln{V_{ads}}\) is plotted against \(\ln^n{p_0/p}\), and fitted with a best-fit line. The intercept of this line can be used to calculate the total pore volume, while the slope is proportional to the characteristic energy of adsorption \(\varepsilon\).

References

pygaps.characterisation.dr_da_plots.da_plot_raw(pressure: list, loading: list, iso_temp: float, molar_mass: float, liquid_density: float, exp: float = None, p_limits: tuple[float, float] = None)[source]#

Calculate a DA fit, a 'bare-bones' function.

Designed as a low-level alternative to the main function. For advanced use.

Parameters:
  • pressure (array) -- Pressure, relative.

  • loading (array) -- Loading, in mol/basis.

  • iso_temp (float) -- Isotherm temperature, in K

  • molar_mass (float) -- Molar mass of adsorbate, in g/mol.

  • liquid_density (float) -- Mass liquid density of the adsorbed phase, in g/cm3.

  • exp (float, None) -- Exponent used in the DA equation. Pass None to automatically calculate.

  • p_limits ([float, float], optional) -- Pressure range in which to perform the calculation.

Returns:

  • microp_volume (float) -- Calculated DA pore volume.

  • potential (float) -- Effective DA adsorption potential.

  • exp (float) -- The exponent (useful if fitting was desired).

  • slope (float) -- Slope of the fitted DA line.

  • intercept (float) -- Intercept of the DA line.

  • minimum (float) -- Minimum point taken.

  • maximum (float) -- Maximum point taken.

  • corr_coef (float) -- Correlation coefficient of the fit line.

pygaps.characterisation.dr_da_plots.log_v_adj(loading, molar_mass, liquid_density)[source]#

Log of volumetric uptake.

pygaps.characterisation.dr_da_plots.log_p_exp(pressure, exp)[source]#

Log of p_0/p raised to the DA exponent.

Mesoporous pore size distribution#
Module doc#

Methods of calculating a pore size distribution for pores in the mesopore range (2-100 nm), based on the Kelvin equation and pore condensation/evaporation.

pygaps.characterisation.psd_meso.psd_mesoporous(isotherm: PointIsotherm | ModelIsotherm, psd_model: str = 'pygaps-DH', pore_geometry: str = 'cylinder', meniscus_geometry: str = None, branch: str = 'des', thickness_model: str | PointIsotherm | ModelIsotherm | t.Callable[[float], float] = 'Harkins/Jura', kelvin_model: str | t.Callable[[float], float] = 'Kelvin', p_limits: tuple[float, float] = None, verbose: bool = False)[source]#

Calculate the mesopore size distribution using a Kelvin-type model.

Expected pore geometry must be specified as pore_geometry. If the meniscus (adsorbed/gas interface) geometry is known, it can be equally specified, otherwise it will be inferred from the pore geometry.

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.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- Isotherm for which the pore size distribution will be calculated.

  • psd_model (str) -- The pore size distribution model to use.

  • pore_geometry (str) -- The geometry of the meniscus (adsorbed/gas interface) in the pores.

  • meniscus_geometry (str, optional) -- The geometry of the adsorbent pores.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to desorption.

  • thickness_model (str, callable, optional) -- The thickness model to use for PSD, It defaults to Harkins/Jura.

  • kelvin_model (str, callable, optional) -- A custom user kelvin model. It should be a callable that only takes relative pressure as an argument.

  • p_limits (tuple[float, float]) -- Pressure range in which to calculate PSD, defaults to (0.1, 0.99).

  • verbose (bool) -- Prints out extra information on the calculation and graphs the results.

Returns:

dict -- A dictionary of results of the form:

  • pore_widths (ndarray) : the widths (or diameter) of the pores, nm

  • pore_volumes (ndarray) : pore volume for each pore width, cm3/material

  • pore_volume_cumulative (ndarray) : cumulative sum of pore volumes, in cm3/material

  • pore_distribution (ndarray) : contribution of each pore width to the overall pore distribution, cm3/material/nm

  • pore_areas (ndarray) : specific area for each pore width, m2/material

  • pore_area_total (float) : total specific area, m2/material

  • limits (tuple[int, int]) : indices of selection limits

Raises:

Notes

Calculates the pore size distribution using a 'classical' model which attempts to describe the adsorption in a pore as a combination of a statistical thickness and a condensation/evaporation behaviour related to adsorbate surface tension. It is based on solving the following equation:

\[\Delta V_n = V_{k,n} + V_{t,n}\]

Which states that the volume adsorbed or desorbed during a pressure step, \(\Delta V_n\), can be described as a sum of the volume involved in capillary condensation / evaporation (\(\Delta V_{k,n}\)), and the volume corresponding to the increase / decrease of the adsorbed layer thickness in the surface of all non-filled pores (\(\Delta V_{t,n}\)).

Expressions are derived for the pore volume as a function of pore geometry, the shape of the liquid-gas interface (meniscus), relationship between pore width and critical condensation width (\(R_{p}\)), rearranging the equation to:

\[V_{p,n} = (\Delta V_n - V_{t,n}) R_p\]

Currently, the methods provided are:

  • the pygaps-DH model, a custom expanded DH model for multiple pore geometries

  • the original BJH or Barrett, Joyner and Halenda method

  • the original DH or Dollimore-Heal method

According to Rouquerol [1], in adopting this approach, it is assumed that:

  • The Kelvin equation is applicable over the pore range (mesopores). In pores which are below a certain size (around 2.5 nm), the granularity of the liquid-vapour interface becomes too large the method to hold.

  • The meniscus curvature is controlled be the pore size and shape. Ideal shapes for the curvature are assumed.

  • The pores are rigid and of well defined shape. They are considered open-ended and non-intersecting.

  • The filling/emptying of each pore does not depend on its location, i.e. no diffusion or blocking effects.

  • Adsorption on the pore walls is not different from surface adsorption.

Caution

A common mantra of data processing is: garbage in = garbage out. Only use methods when you are aware of their limitations and shortcomings.

References

See also

pygaps.characterisation.psd_meso.psd_pygapsdh

low-level pygaps-DH method

pygaps.characterisation.psd_meso.psd_bjh

low-level BJH or Barrett, Joyner and Halenda method

pygaps.characterisation.psd_meso.psd_dollimore_heal

low-level DH or Dollimore-Heal method

pygaps.characterisation.psd_meso.psd_pygapsdh(volume_adsorbed: list[float], relative_pressure: list[float], pore_geometry: str, thickness_model: Callable, condensation_model: Callable)[source]#

Calculate a pore size distribution using an expanded Dollimore-Heal method. This function should not be used with isotherms (use instead pygaps.characterisation.psd_meso.psd_mesoporous()).

Parameters:
  • volume_adsorbed (array) -- Volume adsorbed of "liquid" phase in cm3/material.

  • relative_pressure (array) -- Relative pressure.

  • pore_geometry (str) -- The geometry of the pore, eg. 'sphere', 'cylinder' or 'slit'.

  • thickness_model (callable) -- Function which returns the thickness of the adsorbed layer at a pressure p, in nm.

  • condensation_model (callable) -- Function which returns the critical kelvin radius at a pressure p, in nm.

Returns:

dict -- A dictionary of results of the form:

  • pore_widths (ndarray) : the widths (or diameter) of the pores, nm

  • pore_volumes (ndarray) : pore volume for each pore width, cm3/material

  • pore_areas (ndarray) : specific area for each pore width, m2/material

  • pore_distribution (ndarray) : volumetric pore distribution, cm3/material/nm

Notes

This method is an extended DH method which is applicable to multiple pore geometries (slit, cylinder, spherical).

The calculation is performed in terms of the characteristic dimension of the pore geometry: width for slits, diameter for cylinders and spheres. The characteristic dimension is calculated in terms of the Kelvin radius and adsorbed layer thickness:

\[w_p = 2 * r_p = (r_k + t)\]

The Kelvin radius and layer thickness are calculated based on the functions provided, no assessment of the meniscus shape or thickness is performed here.

The basic equation for all geometries is as follows:

\[V_p = \Big[\Delta V_n - \Delta t_n \sum_{i=1}^{n-1} \Big(\frac{\bar{w}_{p,i} - 2 t_{n}}{\bar{w}_{p,i}}\Big)^{l-1} \frac{2 l V_{p,i}}{w_{p,i}}\Big] \Big(\frac{\bar{w}_p}{\bar{w}_p - 2 t_n}\Big)^l\]
Where :
  • \(V_p\) is the volume of pores of size \(\bar{r}_p\)

  • \(\Delta V_n\) is the adsorbed volume change between two points

  • \(\bar{r}_p\) is the average pore radius calculated as a sum of the kelvin radius and layer thickness of the pores at pressure p between two measurement points

  • \(\bar{r}_k\) is the average kelvin radius between two pressure points

  • \(t_n\) is the layer thickness at point n

  • \(\bar{t}_n\) is the average layer thickness between two pressure points

  • \(\Delta t_n\) is the change in layer thickness between two pressure points

  • \(l\) is the characteristic dimension of the system

In order to account for different geometries, factors are calculated in terms of a characteristic number of the system:

  • In a slit pore, the relationship between pore volume and pore surface is \(A_p = 2 V_p / w_p\). The pore area stays the same throughout any changes in layer thickness, therefore no correction factor is applied. Finally, the relationship between the volume of the kelvin capillary and the total pore width is \(\frac{\bar{w}_{p,i}}{\bar{w}_{p,i} - 2 t_{n,i}}\).

  • In a cylindrical pore, the relationship between pore volume and pore surface is \(A_p = 4 V_p / w_p\). The ratio between average pore area at a point and total pore area can be expressed by using \(\frac{\bar{w}_{p,i} - 2 t_{n,i}}{\bar{w}_{p,i}}\). Finally, the relationship between the inner Kelvin capillary and the total pore diameter is \(\frac{\bar{w}_{p,i}}{\bar{w}_{p,i} - 2 t_{n,i}}^2\).

  • In a spherical pore, the relationship between pore volume and pore surface is \(A_p = 6 V_p / w_p\). The ratio between average pore area at a point and total pore area can be expressed by using \(\frac{\bar{w}_{p,i} - 2 t_{n,i}}{\bar{w}_{p,i}}^2\). Finally, the relationship between the inner Kelvin sphere and the total pore diameter is \(\frac{\bar{w}_{p,i}}{\bar{w}_{p,i} - 2 t_{n,i}}^3\).

References

pygaps.characterisation.psd_meso.psd_bjh(volume_adsorbed: list[float], relative_pressure: list[float], pore_geometry: str, thickness_model: Callable, condensation_model: Callable)[source]#

Calculate a pore size distribution using the original BJH method. This function should not be used with isotherms (use instead pygaps.characterisation.psd_meso.psd_mesoporous()).

Parameters:
  • volume_adsorbed (array) -- Volume adsorbed of "liquid" phase in cm3/material.

  • relative_pressure (array) -- Relative pressure.

  • pore_geometry (str) -- The geometry of the pore, eg. 'sphere', 'cylinder' or 'slit'.

  • thickness_model (callable) -- Function which returns the thickness of the adsorbed layer at a pressure p, in nm.

  • condensation_model (callable) -- Function which returns the critical kelvin radius at a pressure p, in nm.

Returns:

dict -- A dictionary of results of the form:

  • pore_widths (ndarray) : the widths (or diameter) of the pores, nm

  • pore_volumes (ndarray) : pore volume for each pore width, cm3/material

  • pore_areas (ndarray) : specific area for each pore width, m2/material

  • pore_distribution (ndarray) : volumetric pore distribution, cm3/material/nm

Notes

The BJH or Barrett, Joyner and Halenda [2] method for calculation of pore size distribution is based on a classical description of the adsorbate behaviour in the adsorbent pores. Under this method, the adsorbate is adsorbing on the pore walls forming a layer of known thickness, therefore decreasing the apparent pore volume until condensation takes place, filling the entire pore. The two variables, layer thickness and critical pore width where condensation takes place can be respectively modelled by a thickness model (such as Halsey, Harkins & Jura, etc.) and a model for condensation/evaporation based on a form of the Kelvin equation.

\[1/2 w_p = r_p = r_k + t\]

The original model used the desorption curve as a basis for calculating pore size distribution. Between two points of the curve, the volume desorbed can be described as the volume contribution from pore evaporation and the volume from layer thickness decrease as per the equation above. The computation is done cumulatively, starting from the filled pores and calculating for each point the volume adsorbed in a pore from the following equation:

\[V_p = \Big[\Delta V_n - \Delta t_n \sum_{i=1}^{n-1} \Big(\frac{\bar{r}_{p,i} - t_{n}}{\bar{r}_{p,i}}\Big) A_{p,i}\Big] \Big(\frac{\bar{r}_p}{\bar{r}_k + \Delta t_n}\Big)^2\]
Where :
  • \(V_p\) is the volume of pores of size \(\bar{r}_p\)

  • \(\Delta V_n\) is the adsorbed volume change between two points

  • \(\bar{r}_p\) is the average pore radius calculated as a sum of the kelvin radius and layer thickness of the pores at pressure p between two measurement points

  • \(\bar{r}_k\) is the average kelvin radius between two measurement points

  • \(t_n\) is the layer thickness at point n

  • \(\bar{t}_n\) is the average layer thickness between two measurement points

  • \(\Delta t_n\) is the change in layer thickness between two measurement points

Then, by plotting \(V_p / (2 \Delta r_p)\) versus the width of the pores calculated for each point, the pore size distribution can be obtained.

The code in this function is an accurate implementation of the original BJH method.

References

pygaps.characterisation.psd_meso.psd_dollimore_heal(volume_adsorbed: list[float], relative_pressure: list[float], pore_geometry: str, thickness_model: Callable, condensation_model: Callable)[source]#

Calculate a pore size distribution using the original Dollimore-Heal method. This function should not be used with isotherms (use instead pygaps.characterisation.psd_meso.psd_mesoporous()).

Parameters:
  • volume_adsorbed (array) -- Volume adsorbed of "liquid" phase in cm3/material.

  • relative_pressure (array) -- Relative pressure.

  • pore_geometry (str) -- The geometry of the pore, eg. 'sphere', 'cylinder' or 'slit'.

  • thickness_model (callable) -- Function which returns the thickness of the adsorbed layer at a pressure p, in nm.

  • condensation_model (callable) -- Function which returns the critical kelvin radius at a pressure p, in nm.

Returns:

dict -- A dictionary of results of the form:

  • pore_widths (ndarray) : the widths (or diameter) of the pores, nm

  • pore_volumes (ndarray) : pore volume for each pore width, cm3/material

  • pore_areas (ndarray) : specific area for each pore width, m2/material

  • pore_distribution (ndarray) : volumetric pore distribution, cm3/material/nm

Notes

The DH or Dollimore-Heal method [3] of calculation of pore size distribution is an extension of the BJH method.

Like the BJH method, it is based on a classical description of the adsorbate behaviour in the adsorbent pores. Under this method, the adsorbate is adsorbing on the pore walls in a predictable way, and decreasing the apparent pore radius until condensation takes place, filling the entire pore. The two components, layer thickness (t) and radius (r_k) where condensation takes place can be modelled by a thickness model (such as Halsey, Harkins & Jura, etc.) and a critical radius model for condensation/evaporation, based on a form of the Kelvin equation.

\[1/2 w_p = r_p = r_k + t\]

The original model used the desorption curve as a basis for calculating pore size distribution. Between two points of the curve, the volume desorbed can be described as the volume contribution from pore evaporation and the volume from layer thickness decrease as per the equation above. The computation is done cumulatively, starting from the filled pores and calculating for each point the volume adsorbed in a pore from the following equation:

\[ \begin{align}\begin{aligned}V_p = \Big(\Delta V_n - \Delta V_m\Big)\Big(\frac{\bar{r}_p}{\bar{r}_p - t_n}\Big)^2\\V_p = \Big[\Delta V_n - \Delta t_n \sum_{i=1}^{n-1} A_{p,i} + \Delta t_n \bar{t}_n \sum_{i=1}^{n-1} 2 \pi L_{p,i} \Big]\Big(\frac{\bar{r}_p}{\bar{r}_p - t_n}\Big)^2\end{aligned}\end{align} \]
Where :
  • \(\bar{r}_p\) is the average pore radius calculated as a sum of the kelvin radius and layer thickness of the pores at pressure p between two measurement points

  • \(V_p\) is the volume of pores of size \(\bar{r}_p\)

  • \(\Delta V_n\) is the adsorbed volume change between two points

  • \(\bar{t}_n\) is the average layer thickness between two measurement points

  • \(\Delta t_n\) is the change in layer thickness between two measurement points

Then, by plotting \(V_p/(2*\Delta r_p)\) versus the width of the pores calculated for each point, the pore size distribution can be obtained.

The code in this function is an accurate implementation of the original DH method.

References

Available Kelvin models#

Module contains functions to calculate the critical evaporation/condensation pore radius the mesopore range, as a function of pressure.

pygaps.characterisation.models_kelvin.get_meniscus_geometry(branch: str, pore_geometry: str)[source]#

Determine the meniscus geometry.

Parameters:
  • branch ({'ads', 'des'}) -- Branch of the isotherm used.

  • geometry ({'slit', 'cylinder', 'halfopen-cylinder', 'sphere'}) -- Geometry of the pore.

Returns:

str -- Geometry of the meniscus in the pore.

pygaps.characterisation.models_kelvin.kelvin_radius(pressure: list[float], meniscus_geometry: str, temperature: float, liquid_density: float, adsorbate_molar_mass: float, adsorbate_surface_tension: float)[source]#

Calculate the kelvin radius of the pore, using the standard form of the kelvin equation.

Parameters:
  • pressure -- Relative pressure (p/p0), unitless.

  • meniscus_geometry (str) -- Geometry of the interface of the vapour and liquid phase. WARNING: it is not the same as the pore geometry.

  • temperature (float) -- Temperature in kelvin.

  • liquid_density (float) -- Density of the adsorbed phase, assuming it can be approximated as a liquid g/cm3.

  • adsorbate_molar_mass (float) -- Molar area of the adsorbate, g/mol.

  • adsorbate_surface_tension (float) -- Surface tension of the adsorbate, in mN/m.

Returns:

float -- Kelvin radius(nm).

Notes

Description

The standard kelvin equation for determining critical pore radius for condensation or evaporation.

\[\ln\Big(\frac{p}{p_0}\Big) = -\frac{2 \cos\theta M_m \gamma}{r_K\rho_l RT}\]

Limitations

The Kelvin equation assumes that adsorption in a pore is not different than adsorption on a standard surface. Therefore, no interactions with the adsorbent is accounted for.

Furthermore, the geometry of the pore itself is considered to be invariant accross the entire adsorbate.

See also

pygaps.characterisation.models_kelvin.kelvin_radius_kjs

KJS corrected Kelvin function

pygaps.characterisation.models_kelvin.kelvin_radius_kjs(pressure: list[float], meniscus_geometry: str, temperature: float, liquid_density: float, adsorbate_molar_mass: float, adsorbate_surface_tension: float)[source]#

Calculate the kelvin radius of the pore, using the Kruck-Jaroniec-Sayari correction.

Parameters:
  • pressure -- Relative pressure (p/p0), unitless.

  • meniscus_geometry (str) -- Geometry of the interface of the vapour and liquid phase. WARNING: it is not the same as the pore geometry.

  • temperature (float) -- Temperature in kelvin.

  • liquid_density (float) -- Density of the adsorbed phase, assuming it can be approximated as a liquid g/cm3.

  • adsorbate_molar_mass (float) -- Molar area of the adsorbate, g/mol.

  • adsorbate_surface_tension (float) -- Surface tension of the adsorbate, in mN/m.

Returns:

float -- Kelvin radius(nm).

Notes

Description

The KJS correction to the kelvin equation equation is modified with a constant term of 0.3 nm. The authors arrived at this constant by using the adsorption branch of the isotherm on several MCM-41 materials calibrated with XRD data.

\[\ln\Big(\frac{p}{p_0}\Big) = -\frac{2 \cos\theta M_m \gamma}{r_K\rho_l RT} + 0.3\]

Limitations

Besides the standard limitations of the Kelvin equation, the KJS correction is empirical in nature.

References

See also

pygaps.characterisation.models_kelvin.kelvin_radius

standard Kelvin function

pygaps.characterisation.models_kelvin.get_kelvin_model(model: str | Callable, **model_args)[source]#

Return a function calculating an kelvin-based critical radius.

The model parameter is a string which names the Kelvin model to be used. Alternatively, a user can implement their own model, as a function which returns the critical radius the adsorbed layer. In that case, instead of a string, pass a callable function as the model parameter.

Parameters:
  • model (str or callable) -- Name of the kelvin model to use or function that returns a critical radius.

  • model_args (dict) -- any arguments needed for the model

Returns:

callable -- A callable that takes pressure in and returns a critical kelvin radius at that point.

Raises:

ParameterError -- When string is not in the dictionary of models.

Available thickness models#

See Available thickness models.

Microporous pore size distribution#
Module doc#

This module contains 'classical' methods of calculating a pore size distribution for pores in the micropore range (<2 nm). These are derived from the Horvath-Kawazoe models.

pygaps.characterisation.psd_micro.psd_microporous(isotherm: PointIsotherm | ModelIsotherm, psd_model: str = 'HK', pore_geometry: str = 'slit', branch: str = 'ads', material_model: str | dict[str, float] = 'Carbon(HK)', adsorbate_model: str | dict[str, float] = None, p_limits: tuple[float, float] = None, verbose: bool = False) dict[str, list[float]][source]#

Calculate the microporous size distribution using a Horvath-Kawazoe type model.

Expected pore geometry must be specified as pore_geometry.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- Isotherm for which the pore size distribution will be calculated.

  • psd_model (str) -- Pore size distribution model to use. Available are 'HK' (original Horvath-Kawazoe), 'RY' (Rege-Yang correction) or the Cheng-Yang modification to the two models ('HK-CY', 'RY-CY').

  • pore_geometry (str) -- The geometry of the adsorbent pores.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • material_model (str, dict) -- The material model to use for PSD, It defaults to 'Carbon(HK)', the original Horvath-Kawazoe activated carbon parameters.

  • adsorbate_model (str, dict) -- The adsorbate properties to use for PSD, If empty, properties are automatically searched from internal database for the Adsorbate.

  • p_limits (tuple[float, float]) -- Pressure range in which to calculate PSD, defaults to [0, 0.2].

  • verbose (bool) -- Print out extra information on the calculation and graphs the results.

Returns:

dict -- A dictionary with the pore widths and the pore distributions, of the form:

  • pore_widths (array) : the widths of the pores

  • pore_distribution (array) : contribution of each pore width to the overall pore distribution

Raises:

Notes

Calculates the pore size distribution using a "classical" model, which describes adsorption in micropores as a sequential instant filling of increasingly wider pores. The pressure of filling for each pore is determined by relating the global adsorption potential, \(RT \ln(p/p_0)\), with the energetic potential of individual adsorbate molecules in a pore of a particular geometry \(\Phi\). Calculation of the latter is based on the Lennard-Jones 6-12 intermolecular potential, incorporating both guest-host and guest-guest dispersion contributions through the Kirkwood-Muller formalism. The function is then solved numerically. These methods are necessarily approximations, as besides using a semi-empirical mathematical model, they are also heavily dependent on the material and adsorbate properties (polarizability and susceptibility) used to derive dispersion coefficients.

There are two main approaches which pyGAPS implements, chosen by passing the psd_model parameter:

  • The "HK", or the original Horvath-Kawazoe method [1].

  • The "RY", or the modified Rege-Yang method [4].

Detailed explanations for both methods can be found in psd_horvath_kawazoe() and psd_horvath_kawazoe_ry(), respectively. Additionally for both models, the Cheng-Yang correction [3] can be applied by appending "-CY", such as psd_model="HK-CY" or "RY-CY". This correction attempts to change the expression for the thermodynamic potential from a Henry-type to a Langmuir-type isotherm. While this new expression does not remain consistent at high pressures, it may better represent the isotherm curvature at low pressure [4].

\[\Phi = RT\ln(p/p_0) + RT (1 + \frac{\ln(1-\theta)}{\theta})\]

Currently, three geometries are supported for each model: slit-like pores, cylindrical pores and spherical pores, as described in the related papers [1] [2] [3] [4].

Caution

A common mantra of data processing is: garbage in = garbage out. Only use methods when you are aware of their limitations and shortcomings.

References

See also

pygaps.characterisation.psd_micro.psd_horvath_kawazoe

low level HK (Horvath-Kawazoe) method

pygaps.characterisation.psd_micro.psd_horvath_kawazoe_ry

low level RY (Rege-Yang) method

pygaps.characterisation.psd_micro.psd_horvath_kawazoe(pressure: list[float], loading: list[float], temperature: float, pore_geometry: str, adsorbate_properties: dict[str, float], material_properties: dict[str, float], use_cy: bool = False)[source]#

Calculate the pore size distribution using the Horvath-Kawazoe method. This function should not be used with isotherms (use instead pygaps.characterisation.psd_micro.psd_microporous()).

Parameters:
  • pressure (list[float]) -- Relative pressure.

  • loading (list[float]) -- Adsorbed amount in mmol/g.

  • temperature (float) -- Temperature of the experiment, in K.

  • pore_geometry (str) -- The geometry of the pore, eg. 'sphere', 'cylinder' or 'slit'.

  • adsorbate_properties (dict) --

    Properties for the adsorbate in the form of:

    adsorbate_properties = {
        'molecular_diameter': 0,           # nm
        'polarizability': 0,               # nm3
        'magnetic_susceptibility': 0,      # nm3
        'surface_density': 0,              # molecules/m2
        'liquid_density': 0,               # g/cm3
        'adsorbate_molar_mass': 0,         # g/mol
    }
    
  • material_properties (dict) -- Properties for the adsorbate in the same form as 'adsorbate_properties'. A list of common models can be found in .characterisation.models_hk.

  • use_cy (bool:) -- Whether to use the Cheng-Yang nonlinear Langmuir term.

Returns:

  • pore widths (array) -- The widths of the pores.

  • pore_dist (array) -- The distributions for each width.

  • pore_vol_cum (array) -- Cumulative pore volume.

Notes

Description

The H-K method [5] attempts to describe adsorption within pores by calculation of the average potential energy for a pore and equating it to the change in free energy upon adsorption. The method starts by assuming the following relationship between the two:

\[\Phi = RT \ln(p/p_0) = U_0 + P_a\]

Here \(U_0\) is the potential describing the surface to adsorbent interactions and \(P_a\) is the potential describing the adsorbate-adsorbate interactions. This relationship is derived from the equation of the free energy of adsorption at constant temperature where the adsorption entropy term \(T \Delta S^{tr}(\theta)\) is assumed to be negligible. \(R\), \(T\), and \(p\) are the gas constant, temperature and pressure, respectively. The expression for the guest-host and host-host interaction in the pore is then modelled on the basis of the Lennard-Jones 12-6 potential. For two molecules 1 and 2:

\[\epsilon_{12}(z) = 4 \epsilon^{*}_{12} \Big[(\frac{\sigma}{z})^{12} - (\frac{\sigma}{z})^{6}\Big]\]

Where \(z\) is intermolecular distance, \(\epsilon^{*}\) is the depth of the potential well and \(\sigma\) is the zero-interaction energy distance. The two molecules can be identical, or different species.

The distance at zero-interaction energy, commonly defined as the "rest internuclear distance", is a function of the diameter of the molecules involved, and is calculated as \(\sigma = (2/5)^{1/6} d_0\). If the two molecules are different, \(d_0\) is the average of the diameter of the two, \(d_0=(d_g + d_h)/2\) such as between the guest and host molecules. In the case of multiple surface atom types (as for zeolites), representative averages are used.

The depth of the potential well is obtained using the Kirkwood-Muller formalism, which relates molecular polarizability \(\alpha\) and magnetic susceptibility \(\varkappa\) to the specific dispersion constant. For guest-host (\(A_{gh}\)) and guest-guest (\(A_{gg}\)) interactions they are calculated through:

\[\begin{split}A_{gh} = \frac{6mc^2\alpha_g\alpha_h}{\alpha_g/\varkappa_g + \alpha_h/\varkappa_h} \\ A_{gg} = \frac{3}{2} m_e c ^2 \alpha_g\varkappa_g\end{split}\]

In the above formulas, \(m_e\) is the mass of an electron and \(c\) is the speed of light in a vacuum. This potential equation (\(\epsilon\)) is then applied to the specific geometry of the pore (e.g. potential of an adsorbate molecule between two infinite surface slits). Individual molecular contributions as obtained through these expressions are multiplied by average surface densities for the guest (\(n_g\)) and the host (\(n_h\)) and then scaled to moles by using Avogadro's number \(N_A\). By integrating over the specific pore dimension (width, radius) an average potential for a specific pore size is obtained.

Slit pore

The original model was derived for a slit-like pore, with each pore modelled as two parallel infinite planes between which adsorption took place. [5] The effective width of the pore is related to the characterisic length by, \(W = L - d_h\) and the following relationship is derived:

\[\begin{split}RT\ln(p/p_0) = & N_A\frac{n_h A_{gh} + n_g A_{gh} }{\sigma^{4}(L-2d_0)} \\ & \times \Big[ \Big(\frac{\sigma^{10}}{9 d_0^9}\Big) - \Big(\frac{\sigma^{4}}{3 d_0^3}\Big) - \Big(\frac{\sigma^{10}}{9(L-d_0)^{9}}\Big) + \Big(\frac{\sigma^{4}}{3(L - d_0)^{3}}\Big) \Big]\end{split}\]

Cylindrical pore

Using the same procedure, a cylindrical model was proposed by Saito and Foley [6] using pore radius \(L\) as the representative length (therefore pore width \(W = 2L - d_h\)), and involves a summation of probe-wall interactions for sequential axial rings of the cylinder up to infinity.

\[\begin{split}RT\ln(p/p_0) = & \frac{3}{4}\pi N_A \frac{n_h A_{gh} + n_g A_{gg} }{d_0^{4}} \\ & \times \sum^{\infty}_{k = 0} \frac{1}{k+1} \Big( 1 - \frac{d_0}{L} \Big)^{2k} \Big[ \frac{21}{32} \alpha_k \Big(\frac{d_0}{L}\Big)^{10} - \beta_k \Big(\frac{d_0}{L}\Big)^{4} \Big]\end{split}\]

Where the constants \(\alpha_k\) and \(\beta\) are recursively calculated from \(\alpha_0 = \beta_0 = 1\):

\[\alpha_k = \Big( \frac{-4.5-k}{k} \Big)^2 \alpha_{k-1} \ \text{and} \ \beta_k = \Big( \frac{-1.5-k}{k} \Big)^2 \beta_{k-1}\]

Spherical pore

Similarly, Cheng and Yang [7] introduced an extension for spherical pores by considering the interactions with a spherical cavity. This model similarly uses the sphere radius \(L\) as the representative length (therefore effective pore width \(W = 2L - d_h\)) It should be noted that realistic spherical pores would not have any communication with the adsorbent exterior.

\[\begin{split}RT\ln(p/p_0) = & N_A 6 \Big( n_1 \frac{A_{gh}}{4 d_0^6} + n_2 \frac{A_{gg}}{4 d_g^6} \Big) \frac{L^3}{(L-d_0)^{3}} \\ & \times \Big[ \Big( \frac{d_0}{L} \Big)^{12} \Big( \frac{T_9}{90} - \frac{T_8}{80} \Big) - \Big( \frac{d_0}{L} \Big)^{6} \Big( \frac{T_3}{12} - \frac{T_2}{8} \Big) \Big]\end{split}\]

Here, \(T_x\) stands for a function of the type:

\[T_x = \Big[1 + (-1)^{x} \frac{L-d_0}{L} \Big]^{-x} - \Big[1 - (-1)^{x} \frac{L-d_0}{L} \Big]^{-x}\]

While the population densities for guest and host \(n_1\) and \(n_2\) are calculated from the plane values as \(n_0 = 4\pi L^2 n_h\) and \(n_i = 4\pi (L - d_0)^2 n_g\).

Limitations

The main assumptions made by using the H-K method are:

  • It does not have a description of capillary condensation. This means that the pore size distribution can only be considered accurate up to a maximum of 5 nm.

  • The surface is made up of a single layer of atoms. Furthermore, since the HK method is reliant on knowing the properties of the surface atoms as well as the adsorbate molecules the material should ideally be homogenous.

  • Only dispersive forces are accounted for. If the adsorbate-adsorbent interactions have other contributions, such as charged interactions, the Lennard-Jones potential function will not be an accurate description of pore environment.

  • Each pore is uniform and of infinite length. Materials with varying pore shapes or highly interconnected networks may not give realistic results.

References

pygaps.characterisation.psd_micro.psd_horvath_kawazoe_ry(pressure: list[float], loading: list[float], temperature: float, pore_geometry: str, adsorbate_properties: dict[str, float], material_properties: dict[str, float], use_cy: bool = False)[source]#

Calculate the microporous size distribution using a Rege-Yang (R-Y) type model. This function should not be used with isotherms (use instead pygaps.characterisation.psd_micro.psd_microporous()).

Parameters:
  • pressure (list[float]) -- Relative pressure.

  • loading (list[float]) -- Adsorbed amount in mmol/g.

  • temperature (float) -- Temperature of the experiment, in K.

  • pore_geometry (str) -- The geometry of the pore, eg. 'sphere', 'cylinder' or 'slit'.

  • adsorbate_properties (dict) --

    Properties for the adsorbate in the form of:

    adsorbate_properties = {
        'molecular_diameter': 0,           # nm
        'polarizability': 0,               # nm3
        'magnetic_susceptibility': 0,      # nm3
        'surface_density': 0,              # molecules/m2
        'liquid_density': 0,               # g/cm3
        'adsorbate_molar_mass': 0,         # g/mol
    }
    
  • material_properties (dict) -- Properties for the adsorbate in the same form as 'adsorbate_properties'. A list of common models can be found in .characterisation.models_hk.

  • use_cy (bool:) -- Whether to use the Cheng-Yang nonlinear Langmuir term.

Returns:

  • pore widths (array) -- The widths of the pores.

  • pore_dist (array) -- The distributions for each width.

  • pore_vol_cum (array) -- Cumulative pore volume.

Notes

This approach attempts to address two main shortcomings of the H-K method, (see details here psd_horvath_kawazoe_ry()) namely its odd summation of contributions from the adsorbate-surface and adsorbate-adsorbate contributions and the assumption of a continuous distributions of guest molecules inside a pore.

Rege and Yang [8] propose a more granular model, where molecules occupy fixed positions according to a minimum energy potential. Depending on the size of the pore in relation to the guest, pores are categorised based on the number of adsorbed layers \(M\), with molecules adsorbed inside described on a layer-by-layer basis. In a similar assumption to the BET theory, a molecule would experience a surface-guest potential only if adjacent to the pore wall, with subsequent layers interacting through pure guest-guest interactions. While they do not assign a weighted distribution to the guest position (i.e. according to Boltzmann's law) and thus disregard thermal motion, this model is theoretically a more accurate representation of how spherical molecules would pack in the pore. The potential equations were derived for slit, cylindrical and spherical pores.

Slit pore

For a slit geometry, the number of layers in a pore of width \(L\) is calculated as a function of guest molecule and host surface atom diameter as \(M = (L - d_h)/d_g\). If the number of adsorbed layers is between 1 and 2, the guest molecule will see only the two pore walls, and its potential will be:

\[\epsilon_{hgh} = \frac{n_h A_{gh}}{2\sigma^{4}} \Big[ \Big(\frac{\sigma}{d_0}\Big)^{10} - \Big(\frac{\sigma}{d_0}\Big)^{4} - \Big(\frac{\sigma}{L - d_0}\Big)^{10} + \Big(\frac{\sigma}{L - d_0}\Big)^{4} \Big]\]

If the number of layers is larger than two, there will be two types of guest molecular potentials, namely (i) the first layer which interacts on one side with the host surface and a layer of guests on the other and (ii) a middle-type layer which interacts with two other guest layers. Internuclear distance at zero energy for two guest molecules is introduced as \(\sigma_g = (2/5)^{1/6} d_g\). The functions describing the potentials of the two types of potential \(\epsilon_{hgg}\) and \(\epsilon_{ggg}\) are then:

\[\epsilon_{hgg} = \frac{n_h A_{gh}}{2\sigma^{4}} \Big[ \Big(\frac{\sigma}{d_0}\Big)^{10} - \Big(\frac{\sigma}{d_0}\Big)^{4} \Big] + \frac{n_g A_{gg}}{2\sigma_g^{4}} \Big[ \Big(\frac{\sigma_g}{d_g}\Big)^{10} - \Big(\frac{\sigma_g}{d_g}\Big)^{4} \Big]\]
\[\epsilon_{ggg} = 2 \times \frac{n_g A_{gg}}{2\sigma_g^{4}} \Big[ \Big(\frac{\sigma_g}{d_g}\Big)^{10} - \Big(\frac{\sigma_g}{d_g}\Big)^{4} \Big]\]

The average potential for a pore with more than two layers is a weighted combination of the two types of layers \(\bar{\epsilon} = [2 \epsilon_{hgg} + (M-2)\epsilon_{ggg}] / M\), while while for a single layer it is equal to \(\bar{\epsilon} = \epsilon_{hgh}\). With a potential formula for both types of pores, the change in free energy can be calculated similarly to the original H-K method: \(RT\ln(p/p_0) = N_A \bar{\epsilon}\).

Cylindrical pore

In a cylindrical pore, the number of concentric layers of guest molecules which can be arranged in a cross-section of radius \(L\) is mathematically represented as:

\[M = \text{int}\Big[ \frac{(2L - d_h)/d_g - 1}{2} \Big] + 1\]

Here, \(int\) truncates to an integer number rounded down. Molecules can then either be part of the first layer, interacting with the surface, or in subsequent layers, interacting with adsorbate layers, with their number for each layer estimated using its diameter. In this particular geometry, an assumption is made that only outer-facing layers contribute to the interaction energy. The potentials corresponding to the two situations are then determined as:

\[\begin{split}\epsilon_{hg} = \frac{3}{4}\pi \frac{n_h A_{gh}}{d_0^{4}} \times \Big[ \frac{21}{32} a_1^{10} \sum^{\infty}_{k = 0} \alpha_k b_1^{2k} - a_1^{4} \sum^{\infty}_{k = 0} \beta_k b_1^{2k} \Big] \\\end{split}\]
\[\epsilon_{gg} = \frac{3}{4}\pi \frac{n_g A_{gg}}{d_g^{4}} \times \Big[ \frac{21}{32} a_i^{10} \sum^{\infty}_{k = 0} \alpha_k b_i^{2k} - a_i^{4} \sum^{\infty}_{k = 0} \beta_k b_i^{2k} \Big]\]

Where:

\[a_1 = d_0 / L \ \text{and} \ b_1 = (L - d_0) / L\]
\[a_i = \frac{d_g}{L - d_0 - (i - 2) d_g} \ \text{and} \ b_i = \frac{L - d_0 - (i - 1) d_g}{L - d_0 - (i - 2) d_g}\]

With the symbols having the same connotation as those in the original H-K cylindrical model. The number of molecules accommodated in each concentric layer is calculated as:

\[n_i = \frac{\pi}{\sin^{-1} \Big[\frac{d_g}{2(L - d_0 - (i - 1) d_g)}\Big]}\]

The average potential for a pore is then a weighted average defined as \(\bar{\epsilon} = \sum^{M}_{i = 1} n_i \epsilon_i / \sum^{M}_{i = 1} n_i\) and then equated to change in free energy by multiplication with Avogadro's number.

Spherical pore

In a spherical pore of radius \(L\), the number of layers that can be accommodated \(M\) is assumed identical to that in a cylindrical pore of similar radius. The equations describing the potential for the initial and subsequent layers are then given as:

\[\epsilon_1 = 2 \frac{n_0 A_{gh}}{4 d_0^6} \Big[ \frac{a_1^{12}}{10 b_1} \Big( \frac{1}{(1-b_1)^{10}} - \frac{1}{(1+b_1)^{10}} \Big) - \frac{a_1^{6}}{4 b_1} \Big( \frac{1}{(1-b_1)^{4}} - \frac{1}{(1+b_1)^{4}} \Big) \Big]\]
\[\epsilon_i = 2 \frac{n_{i-1} A_{gg}}{4 d_g^6} \Big[ \frac{a_i^{12}}{10 b_i} \Big( \frac{1}{(1-b_i)^{10}} - \frac{1}{(1+b_i)^{10}} \Big) - \frac{a_i^{6}}{4 b_i} \Big( \frac{1}{(1-b_i)^{4}} - \frac{1}{(1+b_i)^{4}} \Big) \Big]\]

The number of molecules each layer interacts with (\(n\)) is calculated based on known surface density and a spherical geometry correction. For the first layer \(n_0 = 4\pi L^2 n_h\) and for subsequent layers \(n_i = 4\pi (L - d_0 - (i-1) d_g)^2 n_g\). The constants \(a\) and \(b\) are calculated as for a cylindrical geometry, as in the case with the average potential \(\bar{\epsilon}\).

References

Available HK models#

Dictionaries or generators which provide properties for use in the Horvath-Kawazoe method.

pygaps.characterisation.models_hk.HK_KEYS = {'magnetic_susceptibility': 'nm3', 'molecular_diameter': 'nm', 'polarizability': 'nm3', 'surface_density': 'molecules/m2'}#

List of parameters for an HK model

pygaps.characterisation.models_hk.PROPERTIES_CARBON = {'magnetic_susceptibility': 1.35e-07, 'molecular_diameter': 0.34, 'polarizability': 0.00102, 'surface_density': 3.845e+19}#

List of parameters for the carbon model

pygaps.characterisation.models_hk.PROPERTIES_AlSi_OXIDE_ION = {'magnetic_susceptibility': 1.3e-08, 'molecular_diameter': 0.276, 'polarizability': 0.0025, 'surface_density': 1.315e+19}#

List of parameters for the AlSi-Oxide model

pygaps.characterisation.models_hk.PROPERTIES_AlPh_OXIDE_ION = {'magnetic_susceptibility': 1.3e-08, 'molecular_diameter': 0.26, 'polarizability': 0.0025, 'surface_density': 1e+19}#

List of parameters for the AlPh-Oxide model

pygaps.characterisation.models_hk.get_hk_model(model: str | dict)[source]#

Get the adsorbent model for HK PSD.

The model parameter is a string which names the parameters which should be returned. Alternatively, a user can implement their own adsorbent model, by passing a dict.

Parameters:

model (str, dict) -- Name of the model to use or a dict with the parameters.

Returns:

dict -- A dict with parameters for the HK model.

Raises:

ParameterError -- When string is not in the dictionary of models.

Kernel fit pore size distribution#
DFT kernel fit#

Module contains methods of calculating a pore size distribution starting from a DFT kernel. Please note that calculation of DFT/NLDFT/QSDFT kernels is outside the scope of this program.

pygaps.characterisation.psd_kernel.psd_dft(isotherm: PointIsotherm | ModelIsotherm, kernel: str = 'DFT-N2-77K-carbon-slit', branch: str = 'ads', p_limits: tuple[float, float] = None, kernel_units: dict = None, bspline_order: int = 2, verbose: bool = False)[source]#

Calculate the pore size distribution using a DFT kernel from an Isotherm.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- The isotherm for which the pore size distribution will be calculated.

  • kernel (str) -- The name of the kernel, or the path where it can be found.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • p_limits ([float, float]) -- Pressure range in which to calculate PSD, defaults to entire isotherm.

  • bspline_order (int) -- The smoothing order of the b-splines fit to the data. If set to 0, data will be returned as-is.

  • kernel_units (dict) -- A dictionary specifying kernel basis and units, contains loading_basis, loading_unit, material_basis, material_unit, pressure_mode and "pressure_unit". Defaults to mmol/g vs. relative pressure.

  • verbose (bool) -- Prints out extra information on the calculation and graphs the results.

Raises:
Returns:

dict -- A dictionary with the pore widths and the pore distributions, of the form:

  • pore_widths (array) : the widths of the pores

  • pore_distribution (array) : contribution of each pore width to the overall pore distribution

Notes

Density Functional Theory (DFT) along with its extensions (NLDFT, QSDFT, etc) have emerged as the most powerful methods of describing adsorption on surfaces and in pores [1], [2]. The theory allows the prediction of molecular behaviour solely on the basis of quantum mechanical considerations, and does not require other properties besides the electron structure and positions of the atoms involved. Calculations of large systems of atoms, such as those involved in adsorption in pores is a computationally intensive task, with modern computing power significantly improving the scales on which the modelling can be applied.

The theory can be used to model adsorption in a simplified pore of a particular width which yields the the adsorption isotherm on a material comprised solely on pores of that width. The calculation is then repeated on pores of different sizes, to obtain a 'kernel' or a collection of ideal isotherms on a distribution of pores. The generation of this kernel should be judicious, as both the adsorbent and the adsorbate must be modelled accurately. As it is a field in of in itself, the DFT calculations themselves are outside the scope of this program.

Using the kernel, the isotherm obtained through experimental means can be modelled. The contributions of each kernel isotherm to the overall data is determined through a minimisation function. The contributions and their corresponding pore size form the pore size distribution of the material.

The program accepts kernel files in a CSV format with the following structure:

Pressure(bar)

Pore size 1(nm)

Pore size 2(nm)

...

Pore size y(nm)

p1

l11

l21

...

ly1

p2

l12

l22

...

ly2

p3

l13

l23

...

ly3

...

...

...

...

...

px

l1x

l2x

...

lyz

The kernel should have sufficient points for a good interpolation as well as have a range of pressures that is wide enough to cover possible experimental values.

Limitations

The accuracy of predicting pore size through DFT kernels is only as good as the kernel itself, which should be tailored to the adsorbate, adsorbent and experimental conditions used.

The isotherm used to calculate the pore size distribution should have enough datapoints and pressure range in order to cover adsorption in the entire range of pores.

See also

pygaps.characterisation.psd_kernel.psd_dft_kernel_fit

backend function for DFT kernel fitting

References

pygaps.characterisation.psd_kernel.psd_dft_kernel_fit(pressure: list[float], loading: list[float], kernel_path: str, bspline_order: int = 2)[source]#

Fit a DFT kernel on experimental adsorption data.

Parameters:
  • loading (array) -- Adsorbed amount in mmol/g.

  • pressure (array) -- Relative pressure.

  • kernel_path (str) -- The location of the kernel to use.

  • bspline_order (int) -- The smoothing order of the b-splines fit to the data. If set to 0, data will be returned as-is.

Returns:

  • pore widths (array) -- The widths of the pores.

  • pore_dist (array) -- The distributions for each width (dV/dw).

  • pore_load_cum (array) -- Cumulative pore loading.

Notes

The function will take the data in the form of pressure and loading. It will then load the kernel either from disk or from memory and define a minimsation function as the sum of squared differences of the sum of all individual kernel isotherm loadings multiplied by their contribution as per the following function:

\[f(x) = \sum_{p=p_0}^{p=p_x} (n_{p,exp} - \sum_{w=w_0}^{w=w_y} n_{p, kernel} X_w )^2\]

The function is then minimised using the scipy.optimise.minimise module, with the constraint that the contribution of each kernel isotherm cannot be negative.

Available thickness models#

Functions calculating the thickness of an adsorbed layer as a function of pressure.

pygaps.characterisation.models_thickness.thickness_halsey(pressure: float) float[source]#

Halsey thickness curve.

Applicable for nitrogen at 77K on materials with weakly interacting surfaces.

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

pygaps.characterisation.models_thickness.thickness_harkins_jura(pressure: float) float[source]#

Harkins and Jura thickness curve.

Applicable for nitrogen at 77K on materials with weakly interacting surfaces.

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

pygaps.characterisation.models_thickness.thickness_zero(pressure: float) float[source]#

A zero-thickness curve applicable for non-wetting adsorbates.

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

pygaps.characterisation.models_thickness.convert_to_thickness(loading, monolayer)[source]#

Conversion to a thickness is done by obtaining the number of adsorbed layers through dividing amount adsorbed by the amount adsorbed in a monolayer (as obtained by BET), then multiplying by the average thickness of a single layer. Mathematically:

\[t [nm] =\]

rac{n}{n_m} * 0.354

pygaps.characterisation.models_thickness.load_std_isotherm(name: str) Callable[source]#

Load a standard isotherm, convert the loading to thickness, then fit an interpolator and then store it in memory.

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

Notes

pygaps.characterisation.models_thickness.SiO2_JKO(pressure: float) float[source]#

Applicable for nitrogen at 77K on silica surfaces down to low pressures. [1]

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

References

pygaps.characterisation.models_thickness.CB_KJG(pressure: float) float[source]#

Applicable for nitrogen at 77K on non-graphitized carbon materials down to low pressures. [2]

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

References

pygaps.characterisation.models_thickness.aerosil_MCM(pressure: float) float[source]#

Hybrid curve using aerosil data at low pressure and MCM data at higher pressure Taken from: https://doi.org/10.1021/la5026679, based on https://doi.org/10.1016/j.micromeso.2010.10.006 and https://doi.org/10.1021/la0105477

Applicable for nitrogen at 77K, used for mesoporous zeolites. Be aware that this method still needs an additional empirical correction to get a more accurate micro/mosopore volume.

Parameters:

pressure (float) -- Relative pressure.

Returns:

float -- Thickness of layer in nm.

pygaps.characterisation.models_thickness.get_thickness_model(model: str | Callable) Callable[source]#

Return a function calculating an adsorbate thickness.

The 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 experimental 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 model parameter.

Parameters:

model (str or callable) -- Name of the thickness model to use.

Returns:

callable -- A callable that takes a pressure in and returns a thickness at that point.

Raises:

ParameterError -- When string is not in the dictionary of models.

Enthalpy of sorption#

Methods for determining isosteric enthalpy of adsorption.

Clausius-Clapeyron Method#

Module calculating the isosteric enthalpy for isotherms at different temperatures.

pygaps.characterisation.isosteric_enth.isosteric_enthalpy(isotherms: list[PointIsotherm | ModelIsotherm], loading_points: list = None, branch: str = 'ads', verbose: bool = False)[source]#

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:

\[\Big( \frac{\partial \ln P}{\partial T} \Big)_{n_a} = -\frac{\Delta H_{ads}}{R T^2}\]

Where \(\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:

\[\Delta H_{ads} = - R \frac{\partial \ln P}{\partial 1 / T}\]

and plotting the values of \(\ln P\) against \(1 / T\) we should obtain a straight line with a slope of \(- \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.

pygaps.characterisation.isosteric_enth.isosteric_enthalpy_raw(pressures: list, temperatures: list)[source]#

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.

Whittaker Method#

Module implementing the Whittaker method for isosteric enthalpy calculations.

pygaps.characterisation.enth_sorp_whittaker.enthalpy_sorption_whittaker(isotherm: BaseIsotherm, model: str = 'Toth', loading: list | None = None, verbose: bool = False)[source]#

Calculate the isosteric heat of adsorption, Delta H_{st} using a single isotherm via the Whittaker method. Pass either a ModelIsotherm of a suitable model (Toth or Langmuir) or the model itself. Parameters of the model fit are then used to determine \(\Delta H_{st}\).

Parameters:
  • isotherm (BaseIsotherm) -- The PointIsotherm or ModelIsotherm to be used. If ModelIsotherm, units must be in Pascal

  • model (str) -- The model to use to fit the PointIsotherm, must be either 'Langmuir' or 'Toth'.

  • loading (list[float]) -- The loadings for which to calculate the isosteric heat of adsorption.

  • 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:

  • enthalpy_sorption (array) : the isosteric enthalpy of adsorption in kJ/mol

  • loading (array) : the loading for each point of the isosteric enthalpy, in mmol/g

Raises:

ParameterError -- When incorrect type of model isotherm is used.

Notes

The Whittaker method, [1] sometimes known as a modified Tóth potential uses variables derived from fitting of a model isotherm (Langmuir or Toth) to derive the isosteric enthalpy of adsorption \(\Delta H_{st}\). The general form of the equation is;

\[\Delta H_{st} = \Delta \lambda + \H_{vap} + RT\]

Where \(\Delta \lambda\) is the adsorption potential, and \(\H_{vap}\) is the latent heat of the liquid-vapour change at equilibrium pressure.

For loadings below the triple point pressure, \(\H_{vap}\) is meaningless. In this case, \(\H_{vap}\) is estimated as that at the triple point.

Whittaker determined \(\Delta \lambda\) as:

\[\Delta \lambda = RT \ln{\left[\left(\frac{p^0}{b^{1/t}}\right)\left(\frac{\Theta^{t}}{1-\Theta^{t}}\right) \right]}\]

Where \(p^0\) is the saturation pressure, \(\Theta\) is the fractional coverage, and \(b\) is derived from the equilibrium constant, \(K\) as \(b = \frac{1}{K^t}\). In the case that the adsorptive is above is supercritical, the pseudo saturation pressure is used; \(p^0 = p_c \left(\frac{T}{T_c}\right)^2\).

The exponent \(t\) is only relevant to the Toth version of the method, as for the Langmuir model it reduces to 1. Thus, \(\Delta \lambda\) becomes

\[\Delta \lambda = RT \ln{ \left( \frac{p^0}{b} \right) }\]

References

pygaps.characterisation.enth_sorp_whittaker.enthalpy_sorption_whittaker_raw(p: float | None = None, p_sat: float | None = None, K: float | None = None, n: float | None = None, n_m: float | None = None)[source]#

not using yet, placeholder for function that may be helpful for DSLangmuir version.

Initial Henry constant#

Module calculating the initial henry constant.

pygaps.characterisation.initial_henry.initial_henry_slope(isotherm: PointIsotherm | ModelIsotherm, branch: str = 'ads', max_adjrms: int = 0.02, p_limits: tuple[float, float] = None, l_limits: tuple[float, float] = None, verbose: bool = False, **plot_parameters)[source]#

Calculate a henry constant based on the initial slope.

Parameters:
  • isotherm (PointIsotherm, ModelIsotherm) -- Isotherm to use for the calculation.

  • branch ({'ads', 'des'}, optional) -- Branch of the isotherm to use. It defaults to adsorption.

  • max_adjrms (float, optional) -- Maximum adjusted root mean square between the linear fit and isotherm data.

  • p_limits ([float, float]) -- Minimum and maximum pressure to take for the fitting routine.

  • l_limits ([float, float]) -- Minimum and maximum loading to take for the fitting routine.

  • verbose (bool, optional) -- Whether to print out extra information.

  • plot_parameters (dict, optional) -- Other parameters to be passed to the plotting function

Returns:

float -- Initial Henry's constant.

pygaps.characterisation.initial_henry.initial_henry_virial(isotherm: PointIsotherm | ModelIsotherm, optimization_params: dict = None, verbose: bool = False)[source]#

Calculate an initial Henry constant based on fitting the virial equation.

Parameters:
  • isotherm (PointIsotherm) -- Isotherm to use for the calculation.

  • verbose (bool, optional) -- Whether to print out extra information.

  • optimization_params (dict) -- Custom parameters to pass to SciPy.optimize.least_squares.

Returns:

float -- Initial Henry's constant.

Isotherm fitting and models#

Base Model Class#

Base class for all isotherm models.

class pygaps.modelling.base_model.IsothermBaseModel(**params)[source]#

Base class for all isotherm models.

to_dict()[source]#

Convert model to a dictionary.

abstract loading(pressure: float) float[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

abstract pressure(loading: float) float[source]#

Calculate pressure at specified loading.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

abstract spreading_pressure(pressure: float) float[source]#

Calculate spreading pressure at specified gas pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure: list[float], loading: list[float])[source]#

Return initial guess for fitting.

Parameters:
  • pressure (array) -- Pressure data.

  • loading (array) -- Loading data.

Returns:

  • saturation_loading (float) -- Loading at the saturation plateau.

  • langmuir_k (float) -- Langmuir calculated constant.

initial_guess_bounds(guess)[source]#

Trim initial guess to the bounds.

fit(pressure: list[float], loading: list[float], param_guess: list[float], optimization_params: dict = None, verbose: bool = False)[source]#

Fit model to data using nonlinear optimization with least squares loss function.

Resulting parameters are assigned to self.

Parameters:
  • pressure (ndarray) -- The pressures of each point.

  • loading (ndarray) -- The loading for each point.

  • param_guess (ndarray) -- The initial guess for the fitting function.

  • optimization_params (dict) -- Custom parameters to pass to SciPy.optimize.least_squares.

  • verbose (bool, optional) -- Prints out extra information about steps taken.

fit_leastsq(leastsq_args: dict)[source]#

Try fitting parameters using least squares.

Henry#

Henry isotherm model.

class pygaps.modelling.henry.Henry(**params)[source]#

Henry's law. Assumes a linear dependence of adsorbed amount with pressure.

\[n(p) = K_H p\]

Notes

The simplest method of describing adsorption on a surface is Henry’s law. It assumes only interactions with the adsorbate surface and is described by a linear dependence of adsorbed amount with increasing pressure.

It is derived from the Gibbs isotherm, by substituting a two dimensional analogue to the ideal gas law. From a physical standpoint, Henry's law is unrealistic as adsorption sites will saturate at higher pressures. However, the constant kH, or Henry’s constant, can be thought of as a measure of the strength of the interaction of the probe gas with the surface. At very low concentrations of gas there is a thermodynamic requirement for the applicability of Henry's law. Therefore, most models reduce to the Henry equation as \(\lim_{p \to 0} n(p)\).

Usually, Henry's law is unrealistic because the adsorption sites will saturate at higher pressures. Only use if your data is linear.

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Henry model, a direct relationship can be found by rearranging the function.

\[p = n / K_H\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Henry model is solved analytically.

\[\pi = K_H p\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Langmuir#

Langmuir isotherm model.

class pygaps.modelling.langmuir.Langmuir(**params)[source]#

Langmuir isotherm model.

\[n(p) = n_m\frac{K p}{1 + K p}\]

Notes

The Langmuir theory [1], proposed at the start of the 20th century, states that adsorption takes place on specific sites on a surface, until all sites are occupied. It was originally derived from a kinetic model of gas adsorption and is based on several assumptions.

  • All sites are equivalent and have the same chance of being occupied

  • Each adsorbate molecule can occupy one adsorption site

  • There are no interactions between adsorbed molecules

  • The rates of adsorption and desorption are proportional to the number of sites currently free and currently occupied, respectively

  • Adsorption is complete when all sites are filled.

Using these assumptions we can define rates for both adsorption and desorption. The adsorption rate \(r_a\) will be proportional to the number of sites available on the surface, as well as the number of molecules in the gas, which is given by pressure. The desorption rate \(r_d\), on the other hand, will be proportional to the number of occupied sites and the energy of adsorption. It is also useful to define \(\theta = n_{ads}/n_{ads}^m\) as the fractional surface coverage, the number of sites occupied divided by the total sites. At equilibrium, the rate of adsorption and the rate of desorption are equal, therefore the two equations can be combined. The equation can then be arranged to obtain an expression for the loading called the Langmuir model. Mathematically:

\[ \begin{align}\begin{aligned}r_a = k_a p (1 - \theta)\\r_d = k_d \theta \exp{\Big(-\frac{E_{ads}}{R_g T}\Big)}\end{aligned}\end{align} \]

At equilibrium, the rate of adsorption and the rate of desorption are equal, therefore the two equations can be combined.

\[k_a p (1 - \theta) = k_d \theta \exp{\Big(-\frac{E_{ads}}{R_gT}\Big)}\]

Rearranging to get an expression for the loading, the Langmuir equation becomes:

\[n(p) = n_m \frac{K p}{1 + K p}\]

Here, \(n_m\) is the moles adsorbed at the completion of the monolayer, and therefore the maximum possible loading. The Langmuir constant is the product of the individual desorption and adsorption constants \(k_a\) and \(k_d\) and exponentially related to the energy of adsorption \(\exp{(-\frac{E}{RT})}\).

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Langmuir model, a direct relationship can be found by rearranging the function.

\[p = \frac{n}{K (n_m - n)}\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Langmuir model is solved analytically.

\[\pi = n_m \log{1 + K p}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Double Site Langmuir#

Double Site Langmuir isotherm model.

class pygaps.modelling.dslangmuir.DSLangmuir(**params)[source]#

Dual-site Langmuir adsorption isotherm.

\[n(p) = n_{m_1}\frac{K_1 p}{1+K_1 p} + n_{m_2}\frac{K_2 p}{1+K_2 p}\]

Notes

An extension to the Langmuir model is to consider the experimental isotherm to be the sum of several Langmuir-type isotherms with different monolayer capacities and affinities [2]. The assumption is that the adsorbent presents several distinct types of homogeneous adsorption sites, and that separate Langmuir equations should be applied to each. This is particularly applicable in cases where the structure of the adsorbent suggests that different types of sites are present, such as in crystalline materials of variable chemistry like zeolites and MOFs. The resulting isotherm equation is:

\[n(p) = \sum_i n_{m_i} \frac{K_i p}{1+K_i p}\]

In practice, up to three adsorption sites are considered. This model is the dual-site model (\(i=2\))

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Double Site Langmuir model, an analytical inversion is possible. See function code for implementation.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Double Site Langmuir model is solved analytically.

\[\pi = n_{m_1} \log{1 + K_1 p} + n_{m_2} \log{1 + K_2 p}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Triple Site Langmuir#

Triple Site Langmuir isotherm model.

class pygaps.modelling.tslangmuir.TSLangmuir(**params)[source]#

Triple-site Langmuir (TSLangmuir) adsorption isotherm

\[n(p) = n_{m_1} \frac{K_1 p}{1+K_1 p} + n_{m_2} \frac{K_2 p}{1+K_2 p} + n_{m_3} \frac{K_3 p}{1+K_3 p}\]

Notes

An extension to the Langmuir model is to consider the experimental isotherm to be the sum of several Langmuir-type isotherms with different monolayer capacities and affinities [3]. The assumption is that the adsorbent material presents several distinct types of homogeneous adsorption sites, and that separate Langmuir equations should be applied to each. This is particularly applicable in cases where the structure of the adsorbent suggests that different types of sites are present, such as in crystalline materials of variable chemistry like zeolites and MOFs. The resulting isotherm equation is:

\[n(p) = \sum_i n_{m_i}\frac{K_i p}{1+K_i p}\]

In practice, up to three adsorption sites are considered. This model is the triple-site model (\(i=3\)).

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the TS Langmuir model, the pressure will be computed numerically as no analytical inversion is possible.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Triple Site Langmuir model is solved analytically.

\[\pi = n_{m_1} \log{1 + K_1 p} + n_{m_2} \log{1 + K_2 p} + n_{m_3} \log{1 + K_3 p}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Brunnauer-Emmet-Teller (BET)#

BET isotherm model.

class pygaps.modelling.bet.BET(**params)[source]#

Brunauer-Emmett-Teller (BET) adsorption isotherm.

\[n(p) = n_m \frac{C p}{(1 - N p)(1 - N p + C p)}\]

Notes

Like the Langmuir model, the BET model [4] assumes that adsorption is kinetically driven and takes place on adsorption sites at the material surface. However, each adsorbed molecule becomes, in itself, a secondary adsorption site, such that incremental layers are formed. The conditions imagined by the BET model are:

  • The adsorption sites are equivalent, and therefore the surface is heterogeneous

  • There are no lateral interactions between adsorbed molecules

  • The adsorption occurs in layers, with adsorbed molecules acting as adsorption sites for new molecules

  • The adsorption energy of a molecule on the second and higher layers equals the condensation energy of the adsorbent \(E_L\).

A particular surface percentage \(\theta_x\) is occupied with x layers. For each layer at equilibrium, the adsorption and desorption rates must be equal. We can then apply the Langmuir theory for each layer. It is assumed that the adsorption energy of a molecule on the second and higher layers is just the condensation energy of the adsorbent \(E_{i>1} = E_L\).

\[ \begin{align}\begin{aligned}k_{a_1} p \theta_0 &= k_{d_1} \theta_1 \exp{(-\frac{E_1}{R_gT})}\\k_{a_2} p \theta_1 &= k_{d_2} \theta_2 \exp{(-\frac{E_L}{R_gT})}\\...\\k_{a_i} p \theta_{i-1} &= k_{d_i} \theta_i \exp{-\frac{E_L}{R_gT}}\end{aligned}\end{align} \]

Since we are assuming that all layers beside the first have the same properties, we can define \(g= \frac{k_{d_2}}{k_{a_2}} = \frac{k_{d_3}}{k_{a_3}} = ...\). The coverages \(\theta\) can now be expressed in terms of \(\theta_0\).

\[ \begin{align}\begin{aligned}\theta_1 &= y \theta_0 \quad where \quad y = \frac{k_{a_1}}{k_{d_1}} p \exp{(-\frac{E_1}{R_gT})}\\\theta_2 &= x \theta_1 \quad where \quad x = \frac{p}{g} \exp{(-\frac{E_L}{R_gT})}\\\theta_3 &= x \theta_2 = x^2 \theta_1\\...\\\theta_i &= x^{i-1} \theta_1 = y x^{i-1} \theta_0\end{aligned}\end{align} \]

A constant C may be defined such that

\[ \begin{align}\begin{aligned}C = \frac{y}{x} = \frac{k_{a_1}}{k_{d_1}} g \exp{(\frac{E_1 - E_L}{R_gT})}\\\theta_i = C x^i \theta_0\end{aligned}\end{align} \]

For all the layers, the equations can be summed:

\[\frac{n}{n_m} = \sum_{i=1}^{\infty} i \theta^i = C \sum_{i=1}^{\infty} i x^i \theta_0\]

Since

\[ \begin{align}\begin{aligned}\theta_0 = 1 - \sum_{1}^{\infty} \theta_i\\\sum_{i=1}^{\infty} i x^i = \frac{x}{(1-x)^2}\end{aligned}\end{align} \]

Then we obtain the BET equation

\[n(p) = \frac{n}{n_m} = n_m\frac{C p}{(1-N p)(1-N p+ C p)}\]

The BET constant C is exponentially proportional to the difference between the surface adsorption energy and the intermolecular attraction, and can be seen to influence the knee a BET-type isotherm has at low pressure, before statistical monolayer formation.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the BET model, an analytical inversion is possible. See function code for implementation.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the BET model is solved analytically.

\[\pi = n_m \ln{\frac{1 - N p + C p}{1- N p}}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Guggenheim-Anderson-de Boer (GAB)#

GAB isotherm model.

class pygaps.modelling.gab.GAB(**params)[source]#

Guggenheim-Anderson-de Boer (GAB) adsorption isotherm.

\[n(p) = n_m \frac{C K p}{(1 - K p)(1 - K p + C K p)}\]

Notes

An extension of the BET model which introduces a constant K, accounting for a different enthalpy of adsorption of the adsorbed phase when compared to liquefaction enthalpy of the bulk phase.

It is often used to fit adsorption and desorption isotherms of water in the food industry. [5]

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the BET model, an analytical inversion is possible. See function code for implementation.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the GAB model is solved analytically.

\[\pi = n_m \ln{\frac{1 - K p + C K p}{1- K p}}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Freundlich#

Freundlich isotherm model.

class pygaps.modelling.freundlich.Freundlich(**params)[source]#

Freundlich adsorption isotherm.

\[n(p) = K p^{ 1/m }\]

Warning: this model is not physically consistent as it does not converge to a maximum plateau.

Notes

The Freundlich [6] isotherm model is an empirical attempt to modify Henry's law in order to account for adsorption site saturation by using a decreasing slope with increased loading. It should be noted that the model never converges to a "maximum", and therefore is not strictly physically consistent. However, it is often good for fitting experimental data before complete saturation.

There are two parameters which define the model:

  • A surface interaction constant K denoting the interaction with the material surface.

  • An exponential term m accounting for the decrease in available adsorption sites at higher loading.

The model can also be derived from a more physical basis, using the potential theory of Polanyi, essentially resulting in a Dubinin-Astakov model where the exponential is equal to 1.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Freundlich model, a direct relationship can be found analytically.

\[p = (n/K)^{m}\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Freundlich model is solved analytically.

\[\pi = m K p^{ 1/m }\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Dubinin-Radushkevitch (DR)#

Dubinin-Radushkevitch isotherm model.

class pygaps.modelling.dr.DR(**params)[source]#

Dubinin-Radushkevitch (DR) adsorption isotherm.

\[n(p) = n_t \exp\Big[-\Big(\frac{-RT\ln(p/p_0)}{\varepsilon}\Big)^{2}\Big]\]

The pressure passed should be in a relative basis.

Notes

The Dubinin-Radushkevich isotherm model [7] extends the potential theory of Polanyi, which asserts that molecules near a surface are subjected to a potential field. The adsorbate at the surface is in a liquid state and its local pressure is conversely equal to the vapour pressure at the adsorption temperature. The Polanyi theory attempts to relate the surface coverage with the Gibbs free energy of adsorption, \(\Delta G^{ads} = - R T \ln p/p_0\) and the total coverage \(\theta\).

There are two parameters which define the model:

  • The total amount adsorbed (n_t), analogous to the monolayer capacity in the Langmuir model.

  • A potential energy term e.

It describes adsorption in a single uniform type of pores. To note that the model does not reduce to Henry's law at low pressure and is therefore not strictly physical.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the DR model, a direct relationship can be found analytically.

\[p/p_0 = \exp\Big( -\frac{\varepsilon}{RT}\sqrt{-\ln n/n_m} \Big)\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the DR model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Dubinin-Astakov (DA)#

Dubinin-Astakov isotherm model.

class pygaps.modelling.da.DA(**params)[source]#

Dubinin-Astakov (DA) adsorption isotherm.

\[n(p) = n_t \exp\Big[-\Big(\frac{-RT\ln(p/p_0)}{\varepsilon}\Big)^{m}\Big]\]

The pressure passed should be in a relative basis.

Notes

The Dubinin-Astakov isotherm model [8] extends the Dubinin-Radushkevich model, itself based on the potential theory of Polanyi, which asserts that molecules near a surface are subjected to a potential field. The adsorbate at the surface is in a liquid state and its local pressure is conversely equal to the vapour pressure at the adsorption temperature. The Polanyi theory attempts to relate the surface coverage with the Gibbs free energy of adsorption, \(\Delta G^{ads} = - R T \ln p/p_0\) and the total coverage \(\theta\).

There are three parameters which define the model:

  • The total amount adsorbed (n_t), analogous to the monolayer capacity in the Langmuir model.

  • A potential energy term e.

  • A power term, m, which can vary between 1 and 3. The DA model becomes the DR model when m=2.

It describes adsorption in a single uniform type of pores. To note that the model does not reduce to Henry's law at low pressure and is therefore not strictly physical.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the DA model, a direct relationship can be found analytically.

\[p/p_0 = \exp\Big( -\frac{\varepsilon}{RT}\sqrt[m]{-\ln n/n_m} \Big)\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the DA model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Quadratic#

Quadratic isotherm model.

class pygaps.modelling.quadratic.Quadratic(**params)[source]#

Quadratic isotherm model.

\[n(p) = n_m \frac{p (K_a + 2 K_b p)}{1 + K_a p + K_b p^2}\]

Notes

The quadratic adsorption isotherm exhibits an inflection point; the loading is convex at low pressures but changes concavity as it saturates, yielding an S-shape. The S-shape can be explained by adsorbate-adsorbate attractive forces; the initial convexity is due to a cooperative effect of adsorbate-adsorbate attractions aiding in the recruitment of additional adsorbate molecules [9].

The parameter \(K_a\) can be interpreted as the Langmuir constant; the strength of the adsorbate-adsorbate attractive forces is embedded in \(K_b\). It is often useful in systems where the energy of guest-guest interactions is actually higher than the energy of adsorption, such as when adsorbing water on a hydrophobic surface.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Quadratic model, an analytical inversion is possible. See function code for implementation.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Quadratic model is solved analytically.

\[\pi = n_m \ln{1+K_a p + K_b p^2}\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Tempkin#

Temkin Approximation isotherm model.

class pygaps.modelling.temkinapprox.TemkinApprox(**params)[source]#

Asymptotic approximation to the Temkin isotherm.

\[n(p) = n_m \frac{K p}{1 + K p} + n_m \theta (\frac{K p}{1 + K p})^2 (\frac{K p}{1 + K p} -1)\]

Notes

The Temkin adsorption isotherm [10], like the Langmuir model, considers a surface with n_m identical adsorption sites, but takes into account adsorbate- adsorbate interactions by assuming that the enthalpy of adsorption is a linear function of the coverage. The Temkin isotherm is derived [11] using a mean-field argument and used an asymptotic approximation to obtain an explicit equation for the loading.

Here, \(n_m\) and K have the same physical meaning as in the Langmuir model. The additional parameter \(\theta\) describes the strength of the adsorbate-adsorbate interactions (\(\theta < 0\) for attractions).

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the TemkinApprox model, the pressure will be computed numerically as no analytical inversion is possible.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the TemkinApprox model is solved analytically.

\[\pi = n_m \Big( \ln{(1 + K p)} + \frac{\theta (2 K p + 1)}{2(1 + K p)^2}\Big)\]
Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Toth#

Toth isotherm model.

class pygaps.modelling.toth.Toth(**params)[source]#

Toth isotherm model.

\[n(p) = n_m \frac{K p}{\sqrt[t]{1 + (K p)^t}}\]

Notes

The Toth model is an empirical modification to the Langmuir equation. The parameter \(t\) is a measure of the system heterogeneity.

Thanks to this additional parameter, the Toth equation can accurately describe a large number of adsorbent/adsorbate systems and is such as hydrocarbons, carbon oxides, hydrogen sulphide and alcohols on activated carbons and zeolites.

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Toth model, a direct relationship can be found analytically:

\[p = \frac{n/(n_m K)}{\sqrt[t]{1-(n/n_m)^t)}}\]
Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Toth model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Jensen-Seaton#

Jensen-Seaton isotherm model.

class pygaps.modelling.jensenseaton.JensenSeaton(**params)[source]#

Jensen-Seaton isotherm model.

\[n(p) = K p \Big[1 + \Big(\frac{K p}{(n_m (1 + k p)}\Big)^t\Big]^{-1/t}\]

Notes

When modelling adsorption in micropores, a requirement was highlighted by Jensen and Seaton in 1996 [12], that at sufficiently high pressures the adsorption isotherm should not reach a horizontal plateau corresponding to saturation but that this asymptote should continue to rise due to the compression of the adsorbate in the pores. They came up with a semi-empirical equation to describe this phenomenon based on a function that interpolates between two asymptotes: the Henry’s law asymptote at low pressure and an asymptote reflecting the compressibility of the adsorbate at high pressure.

Here \(K\) is the Henry constant, \(k\) is the compressibility of the adsorbed phase and \(t\) an empirical constant.

The equation can be used to model both absolute and excess adsorption as the pore volume can be incorporated into the definition of \(b\), although this can lead to negative adsorption slopes for the compressibility asymptote. This equation has been found to provide a better fit for experimental data from microporous solids than the Langmuir or Toth equation, in particular for adsorbent/adsorbate systems with high Henry’s constants where the amount adsorbed increases rapidly at relatively low pressures and then slows down dramatically.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

For the Jensen-Seaton model, the pressure will be computed numerically as no analytical inversion is possible.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Jensen-Seaton model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Virial#

Virial isotherm model.

class pygaps.modelling.virial.Virial(**params)[source]#

A virial isotherm model with 3 factors.

\[p(n) = n \exp{(-\ln{K_H} + An + Bn^2 + Cn^3)}\]

Notes

A virial isotherm model attempts to fit the measured data to a factorized exponent relationship between loading and pressure.

\[p = n \exp{(K_1n^0 + K_2n^1 + K_3n^2 + K_4n^3 + ... + K_i n^{i-1})}\]

It has been applied with success to describe the behaviour of standard as well as supercritical isotherms. The factors are usually empirical, although some relationship with physical can be determined: the first constant is related to the Henry constant at zero loading, while the second constant is a measure of the interaction strength with the surface.

\[K_1 = -\ln{K_{H,0}}\]

In practice, besides the first constant, only 2-3 factors are used.

loading(pressure)[source]#

Calculate loading at specified pressure.

Careful! For the Virial model, the loading has to be computed numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

The Virial model calculates the pressure directly.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the Virial model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

fit(pressure, loading, param_guess, optimization_params=None, verbose=False)[source]#

Fit model to data using nonlinear optimization with least squares loss function.

Resulting parameters are assigned to self.

Parameters:
  • pressure (ndarray) -- The pressures of each point.

  • loading (ndarray) -- The loading for each point.

  • optimization_params (dict) -- Custom parameters to pass to SciPy.optimize.least_squares.

  • verbose (bool, optional) -- Prints out extra information about steps taken.

Wilson Vacancy Solution Theory (W-VST)#

Wilson-VST isotherm model.

class pygaps.modelling.wvst.WVST(**params)[source]#

Wilson Vacancy Solution Theory isotherm model.

Notes

As a part of the Vacancy Solution Theory (VST) family of models, it is based on concept of a “vacancy” species, denoted v, and assumes that the system consists of a mixture of these vacancies and the adsorbate [13].

The VST model is defined as follows:

  • A vacancy is an imaginary entity defined as a vacuum space which acts as the solvent in both the gas and adsorbed phases.

  • The properties of the adsorbed phase are defined as excess properties in relation to a dividing surface.

  • The entire system including the material are in thermal equilibrium however only the gas and adsorbed phases are in thermodynamic equilibrium.

  • The equilibrium of the system is maintained by the spreading pressure which arises from a potential field at the surface

It is possible to derive expressions for the vacancy chemical potential in both the adsorbed phase and the gas phase, which when equated give the following equation of state for the adsorbed phase:

\[\pi = - \frac{R_g T}{\sigma_v} \ln{y_v x_v}\]

where \(y_v\) is the activity coefficient and \(x_v\) is the mole fraction of the vacancy in the adsorbed phase. This can then be introduced into the Gibbs equation to give a general isotherm equation for the Vacancy Solution Theory where \(K_H\) is the Henry’s constant and \(f(\theta)\) is a function that describes the non-ideality of the system based on activity coefficients:

\[p = \frac{n_{ads}}{K_H} \frac{\theta}{1-\theta} f(\theta)\]

The general VST equation requires an expression for the activity coefficients. The Wilson equation can be used, which expresses the activity coefficient in terms of the mole fractions of the two species (adsorbate and vacancy) and two constants \(\Lambda_{1v}\) and \(\Lambda_{1v}\). The equation becomes:

\[p = \bigg( \frac{n_{ads}}{K_H} \frac{\theta}{1-\theta} \bigg) \bigg( \Lambda_{1v} \frac{1-(1-\Lambda_{v1})\theta}{\Lambda_{1v}+(1-\Lambda_{1v})\theta} \bigg) \exp{\bigg( -\frac{\Lambda_{v1}(1-\Lambda_{v1})\theta}{1-(1-\Lambda_{v1})\theta} -\frac{(1 - \Lambda_{1v})\theta}{\Lambda_{1v} + (1-\Lambda_{1v}\theta)} \bigg)}\]

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Careful! For the W-VST model, the loading has to be computed numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

The W-VST model calculates the pressure directly.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the W-VST model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Flory-Huggins Vacancy Solution Theory (FH-VST)#

Flory-Huggins-VST isotherm model.

class pygaps.modelling.fhvst.FHVST(**params)[source]#

Flory-Huggins Vacancy Solution Theory isotherm model.

Notes

As a part of the Vacancy Solution Theory (VST) family of models, it is based on concept of a “vacancy” species, denoted v, and assumes that the system consists of a mixture of these vacancies and the adsorbate [14].

The VST model is defined as follows:

  • A vacancy is an imaginary entity defined as a vacuum space which acts as the solvent in both the gas and adsorbed phases.

  • The properties of the adsorbed phase are defined as excess properties in relation to a dividing surface.

  • The entire system including the adsorbent are in thermal equilibrium however only the gas and adsorbed phases are in thermodynamic equilibrium.

  • The equilibrium of the system is maintained by the spreading pressure which arises from a potential field at the surface

It is possible to derive expressions for the vacancy chemical potential in both the adsorbed phase and the gas phase, which when equated give the following equation of state for the adsorbed phase:

\[\pi = - \frac{R_g T}{\sigma_v} \ln{y_v x_v}\]

where \(y_v\) is the activity coefficient and \(x_v\) is the mole fraction of the vacancy in the adsorbed phase. This can then be introduced into the Gibbs equation to give a general isotherm equation for the Vacancy Solution Theory where \(K_H\) is the Henry’s constant and \(f(\theta)\) is a function that describes the non-ideality of the system based on activity coefficients:

\[p = \frac{n_{ads}}{K_H} \frac{\theta}{1-\theta} f(\theta)\]

The general VST equation requires an expression for the activity coefficients. Cochran [15] developed a simpler, three parameter equation based on the Flory – Huggins equation for the activity coefficient. The equation then becomes:

\[ \begin{align}\begin{aligned}p &= \bigg( \frac{n_{ads}}{K_H} \frac{\theta}{1-\theta} \bigg) \exp{\frac{\alpha^2_{1v}\theta}{1+\alpha_{1v}\theta}}\\with\\\alpha_{1v} &= \frac{\alpha_{1}}{\alpha_{v}} - 1\end{aligned}\end{align} \]

where \(\alpha_{1}\) and \(\alpha_{v}\) are the molar areas of the adsorbate and the vacancy respectively.

References

loading(pressure)[source]#

Calculate loading at specified pressure.

Careful! For the FH-VST model, the loading has to be computed numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the loading.

Returns:

float -- Loading at specified pressure.

pressure(loading)[source]#

Calculate pressure at specified loading.

The FH-VST model calculates the pressure directly.

Parameters:

loading (float) -- The loading at which to calculate the pressure.

Returns:

float -- Pressure at specified loading.

spreading_pressure(pressure)[source]#

Calculate spreading pressure at specified gas pressure.

Function that calculates spreading pressure by solving the following integral at each point i.

\[\pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i\]

The integral for the FH-VST model cannot be solved analytically and must be calculated numerically.

Parameters:

pressure (float) -- The pressure at which to calculate the spreading pressure.

Returns:

float -- Spreading pressure at specified pressure.

initial_guess(pressure, loading)[source]#

Return initial guess for fitting.

Parameters:
  • pressure (ndarray) -- Pressure data.

  • loading (ndarray) -- Loading data.

Returns:

dict -- Dictionary of initial guesses for the parameters.

Multi-component adsorption modelling#

IAST#

Module calculating IAST, given the pure-component adsorption isotherm model.

pygaps.iast.pgiast.iast_binary_vle(isotherms, total_pressure, branch='ads', npoints=30, adsorbed_mole_fraction_guess=None, warningoff=False, verbose=False, ax=None)[source]#

Perform IAST calculations to predict the vapour-liquid equilibrium curve at a fixed pressure, over the entire range of gas phase composition (0-1 of the first component).

Pass a list of two of pure-component adsorption isotherms isotherms, with the first one being selected as a basis.

Parameters:
  • isotherms (list of ModelIsotherms or PointIsotherms) -- Model adsorption isotherms. e.g. [methane_isotherm, ethane_isotherm]

  • total_pressure (float) -- Pressure at which the vapour-liquid equilibrium is to be calculated.

  • npoints (int) -- Number of points in the resulting curve. More points will be more computationally intensive.

  • branch (str) -- which branch of the isotherm to use

  • adsorbed_mole_fraction_guess (array or list, optional) -- Starting guesses for adsorbed phase mole fractions that iast solves for.

  • warningoff (bool, optional) -- When False, logger.warning will print when the IAST calculation result required extrapolation of the pure-component adsorption isotherm beyond the highest pressure in the data.

  • verbose (bool, optional) -- Print off a extra information, as well as a graph.

  • ax (matplotlib axes object, optional) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

dict --

Dictionary with two components:
  • y the mole fraction of the selected adsorbate in the gas phase

  • x mole fraction of the selected adsorbate in the adsorbed phase

pygaps.iast.pgiast.iast_binary_svp(isotherms, mole_fractions, pressures, branch='ads', warningoff=False, adsorbed_mole_fraction_guess=None, verbose=False, ax=None)[source]#

Perform IAST calculations to predict the selectivity of one of the components as a function of pressure.

Pass a list of two of pure-component adsorption isotherms isotherms, with the first one being selected as a basis.

Parameters:
  • isotherms (list of ModelIsotherms or PointIsotherms) -- Model adsorption isotherms. e.g. [methane_isotherm, ethane_isotherm]

  • mole_fractions (float) -- Fraction of the gas phase for each component. Must add to 1. e.g. [0.1, 0.9]

  • pressures (list) -- Pressure values at which the selectivity should be calculated.

  • branch (str) -- which branch of the isotherm to use

  • warningoff (bool, optional) -- When False, logger.warning will print when the IAST calculation result required extrapolation of the pure-component adsorption isotherm beyond the highest pressure in the data.

  • adsorbed_mole_fraction_guess (array or list, optional) -- Starting guesses for adsorbed phase mole fractions that iast solves for.

  • verbose (bool, optional) -- Print off a extra information, as well as a graph.

  • ax (matplotlib axes object, optional) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

dict --

Dictionary with two components:
  • selectivity the selectivity of the selected component

  • pressure the pressure for each selectivity

pygaps.iast.pgiast.iast_point_fraction(isotherms, gas_mole_fraction, total_pressure, branch='ads', verbose=False, warningoff=False, adsorbed_mole_fraction_guess=None)[source]#

Perform IAST calculation to predict multi-component adsorption isotherm from pure component adsorption isotherms.

The material is now in equilibrium with a mixture of gases with partial pressures in the array partial_pressures in units corresponding to those passed in the list of isotherms.

Pass a list of pure-component adsorption isotherms isotherms.

Parameters:
  • isotherms (list of ModelIsotherms or PointIsotherms) -- Model adsorption isotherms. e.g. [methane_isotherm, ethane_isotherm, ...]

  • gas_mole_fraction (array or list) -- Fractions of gas components, e.g. [0.5, 0.5].

  • total_pressure (float) -- Total gas phase pressure, e.g. 5 (bar)

  • branch (str) -- which branch of the isotherm to use

  • verbose (bool, optional) -- Print off a lot of information.

  • warningoff (bool, optional) -- When False, logger.warning will print when the IAST calculation result required extrapolation of the pure-component adsorption isotherm beyond the highest pressure in the data.

  • adsorbed_mole_fraction_guess (array or list, optional) -- Starting guesses for adsorbed phase mole fractions that iast solves for.

Returns:

loading (array) -- Predicted uptakes of each component (mmol/g or equivalent in isotherm units).

pygaps.iast.pgiast.iast_point(isotherms, partial_pressures, branch='ads', verbose=False, warningoff=False, adsorbed_mole_fraction_guess=None)[source]#

Perform IAST calculation to predict multi-component adsorption isotherm from pure component adsorption isotherms.

The material is now in equilibrium with a mixture of gases with partial pressures in the array partial_pressures in units corresponding to those passed in the list of isotherms.

Pass a list of pure-component adsorption isotherms isotherms.

Parameters:
  • isotherms (list of ModelIsotherms or PointIsotherms) -- e.g. [methane_isotherm, ethane_isotherm, ...]

  • partial_pressures (array or list) -- Partial pressures of gas components, e.g. [1.5, 5].

  • branch (str) -- which branch of the isotherm to use

  • verbose (bool, optional) -- Print off a lot of information.

  • warningoff (bool, optional) -- When False, logger.warning will print when the IAST calculation result required extrapolation of the pure-component adsorption isotherm beyond the highest pressure in the data.

  • adsorbed_mole_fraction_guess (array or list, optional) -- Starting guesses for adsorbed phase mole fractions that iast solves for.

Returns:

loading (array) -- Predicted uptakes of each component (mmol/g or equivalent in isotherm units).

pygaps.iast.pgiast.reverse_iast(isotherms, adsorbed_mole_fractions, total_pressure, branch='ads', verbose=False, warningoff=False, gas_mole_fraction_guess=None)[source]#

Perform reverse IAST to predict gas phase composition at total pressure total_pressure that will yield adsorbed mole fractions adsorbed_mole_fractions.

Pass a list of pure-component adsorption isotherms isotherms.

Parameters:
  • isotherms (list) -- Pure-component adsorption isotherms. e.g. [ethane_isotherm, methane_isotherm]

  • adsorbed_mole_fractions (array) -- Desired adsorbed mole fractions, e.g. [.5, .5]

  • total_pressure (float) -- Total bulk gas pressure.

  • branch (str) -- which branch of the isotherm to use

  • verbose (bool) -- Print extra information.

  • warningoff (bool) -- When False, logger.warning will print when the IAST calculation result required extrapolation of the pure-component adsorption isotherm beyond the highest pressure in the data.

  • gas_mole_fraction_guess (array or list) -- Starting guesses for gas phase mole fractions that iast.reverse_iast solves for.

Returns:

  • gas_mole_fractions (array) -- Bulk gas mole fractions that yield desired adsorbed mole fractions adsorbed_mole_fractions at total_pressure.

  • loadings (array) -- Adsorbed component loadings according to reverse IAST (mmol/g or equivalent in isotherm units).

Parsing and interfaces#

JSON#

Parse to and from a JSON string/file format for isotherms.

The _parser_version variable documents any changes to the format, and is used to check for any deprecations.

pygaps.parsing.json.isotherm_to_json(isotherm, path=None, **args_to_json)[source]#

Convert an isotherm object to a json representation.

If the path is specified, the isotherm is saved as a file, otherwise it is returned as a string. Structure is inspired by the NIST format.

Parameters:
  • isotherm (Isotherm) -- Isotherm to be written to json.

  • path (str, None) -- Path to the file to be written.

  • args_to_json (dict) -- Custom arguments to be passed to "json.dump".

Returns:

None or str -- If path is None, returns the resulting json format as a string. Otherwise returns None.

pygaps.parsing.json.isotherm_from_json(str_or_path, fmt=None, loading_key='loading', pressure_key='pressure', **isotherm_parameters)[source]#

Read a pyGAPS isotherm from a file or from a string.

Structure is inspired by the NIST format.

Parameters:
  • str_or_path (str) -- The isotherm in a json string format or a path to where one can be read.

  • loading_key (str) -- The title of the pressure data in the json provided.

  • pressure_key -- The title of the loading data in the json provided.

  • fmt ({None, 'NIST'}, optional) -- If the format is set to NIST, then the json format a specific version used by the NIST database of adsorbents.

Other Parameters:

isotherm_parameters -- Any other options to be overridden in the isotherm creation.

Returns:

Isotherm -- The isotherm contained in the json string or file.

AIF#

An AIF (adsorption information file) parsing implementation.

Format developed in this publication:

Evans, Jack D., Volodymyr Bon, Irena Senkovska, and Stefan Kaskel. ‘A Universal Standard Archive File for Adsorption Data’. Langmuir, 2 April 2021, acs.langmuir.1c00122. https://doi.org/10.1021/acs.langmuir.1c00122.

pygaps.parsing.aif.isotherm_to_aif(isotherm: PointIsotherm, path: str | None = None)[source]#

Convert isotherm into an AIF representation [1].

If the path is specified, the isotherm is saved as a file, otherwise it is returned as a string.

Parameters:
  • isotherm (Isotherm) -- Isotherm to be written to AIF.

  • path (str, None) -- Path to the file to be written.

Returns:

str (optional) -- String representation of the AIF, if path not provided.

References

pygaps.parsing.aif.isotherm_from_aif(str_or_path: str, **isotherm_parameters: dict)[source]#

Parse an isotherm from an AIF format (file or raw string) [2].

Parameters:
  • str_or_path (str) -- The isotherm in a AIF string format or a path to where one can be read.

  • isotherm_parameters -- Any other options to be overridden in the isotherm creation.

Returns:

Isotherm -- The isotherm contained in the AIF file or string.

References

CSV#

Parse to and from a CSV string/file format for isotherms.

The _parser_version variable documents any changes to the format, and is used to check for any deprecations.

pygaps.parsing.csv.isotherm_to_csv(isotherm, path=None, separator=',')[source]#

Convert isotherm into a CSV representation.

If the path is specified, the isotherm is saved as a file, otherwise it is returned as a string.

Parameters:
  • isotherm (Isotherm) -- Isotherm to be written to csv.

  • path (str, None) -- Path to the file to be written.

  • separator (str, optional) -- Separator used int the csv file. Defaults to '',''.

Returns:

str (optional) -- String representation of the CSV, if path not provided.

pygaps.parsing.csv.isotherm_from_csv(str_or_path, separator=',', **isotherm_parameters)[source]#

Load an isotherm from a CSV file.

Parameters:
  • str_or_path (str) -- The isotherm in a CSV string format or a path to where one can be read.

  • separator (str, optional) -- Separator used int the csv file. Defaults to ,.

  • isotherm_parameters -- Any other options to be overridden in the isotherm creation.

Returns:

Isotherm -- The isotherm contained in the csv string or file.

Excel#

Parse to and from a Excel format for isotherms.

The _parser_version variable documents any changes to the format, and is used to check for any deprecations.

pygaps.parsing.excel.isotherm_to_xl(isotherm, path)[source]#

Save an isotherm to an excel file.

Parameters:
  • isotherm (Isotherm) -- Isotherm to be written to excel.

  • path (str) -- Path to the file to be written.

pygaps.parsing.excel.isotherm_from_xl(path, *isotherm_parameters)[source]#

Load an isotherm from a pyGAPS Excel file.

Parameters:
  • path (str) -- Path to the file to be read.

  • isotherm_parameters -- Any other options to be overridden in the isotherm creation.

Returns:

Isotherm -- The isotherm contained in the excel file.

Apparatus files#

Parse aa file generated by commercial apparatus.

Parameters:
  • path (str) -- the location of the file.

  • manufacturer ({'mic', 'bel', '3p'}) -- Manufacturer of the apparatus.

  • fmt ({'xl', 'txt', ...}) -- The format of the import for the isotherm.

returns:

PointIsotherm

Sqlite#

This module contains the sql interface for data manipulation.

pygaps.parsing.sqlite.with_connection(func)[source]#

Contextmanager for sqlite connection.

pygaps.parsing.sqlite.adsorbate_to_db(adsorbate: Adsorbate, db_path: str = None, autoinsert_properties: bool = True, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Upload an adsorbate to the database.

If overwrite is set to true, the adsorbate is overwritten. Overwrite is done based on adsorbate.name

Parameters:
  • adsorbate (Adsorbate) -- Adsorbate class to upload to the database.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the adsorbate or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.adsorbates_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[Adsorbate][source]#

Get database adsorbates and their properties.

The number of adsorbates is usually small, so all can be loaded in memory at once.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

list -- list of Adsorbates

pygaps.parsing.sqlite.adsorbate_delete_db(adsorbate: Adsorbate, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete adsorbate from the database.

Parameters:
  • adsorbate (Adsorbate or str) -- The Adsorbate class to delete or its name.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.adsorbate_property_type_to_db(type_dict, db_path: str = None, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Uploads an adsorbate property type.

The type_dict takes the form of:

{
    'type' : 'the_type',
    'unit': 'the_unit',
    'description': 'the_description'
}
Parameters:
  • type_dict (dict) -- A dictionary that contains property type.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the property type or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.adsorbate_property_types_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[dict][source]#

Get all adsorbate property types.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

dict -- dict of property types

pygaps.parsing.sqlite.adsorbate_property_type_delete_db(property_type, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete property type in the database.

Parameters:
  • property_type (str) -- Name of the property type to delete.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.material_to_db(material: Material, db_path: str = None, autoinsert_properties: bool = True, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Upload a material to the database.

If overwrite is set to true, the material is overwritten. Overwrite is done based on material.name

Parameters:
  • material (Material) -- Material class to upload to the database.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the material or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.materials_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[Material][source]#

Get all materials and their properties.

The number of materials is usually small, so all can be loaded in memory at once.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

list -- list of Materials

pygaps.parsing.sqlite.material_delete_db(material: Material, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete material from the database.

Parameters:
  • material (Material or str) -- Material class to upload to the database.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.material_property_type_to_db(type_dict: dict, db_path: str = None, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Uploads a material property type.

The type_dict takes the form of:

{
    'type' : 'the_type',
    'unit': 'the_unit',
    'description': 'the_description'
}
Parameters:
  • type_dict (dict) -- A dictionary that contains property type.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the property type or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.material_property_types_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[dict][source]#

Get all material property types.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

dict -- dict of property types

pygaps.parsing.sqlite.material_property_type_delete_db(material_prop_type: dict, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete material property type in the database.

Parameters:
  • material_prop_type (str) -- The type to delete.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherm_to_db(isotherm: BaseIsotherm | PointIsotherm | ModelIsotherm, db_path: str = None, autoinsert_material: bool = True, autoinsert_adsorbate: bool = True, verbose: bool = True, **kwargs: dict)[source]#

Uploads isotherm to the database.

If overwrite is set to true, the isotherm is overwritten. Overwrite is done based on isotherm.iso_id

Parameters:
  • isotherm (Isotherm) -- Isotherm, PointIsotherm or ModelIsotherm to upload to the database.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • autoinsert_material (bool, True) -- Whether to automatically insert an isotherm material if it is not found in the database.

  • autoinsert_adsorbate (bool, True) -- Whether to automatically insert an isotherm adsorbate if it is not found in the database.

  • verbose (bool, True) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherms_from_db(criteria: dict = None, db_path: str = None, verbose: bool = True, **kwargs: dict) list[BaseIsotherm | PointIsotherm | ModelIsotherm][source]#

Get isotherms with the selected criteria from the database.

Parameters:
  • criteria (dict, None) -- Dictionary of isotherm parameters on which to filter database from base parameters ('material', 'adsorbate', 'temperature', 'type'). For example {'material': 'm1', 'temperature': '77'}. Parameters must exist for the filtering to take place otherwise all results are returned.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

list -- list of Isotherms

pygaps.parsing.sqlite.isotherm_delete_db(iso_id: str | BaseIsotherm | PointIsotherm | ModelIsotherm, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete isotherm in the database.

Parameters:
  • isotherm (Isotherm or Isotherm.iso_id) -- The Isotherm object to delete from the database or its ID.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherm_type_to_db(type_dict: dict, db_path: str = None, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Upload an isotherm type.

The type_dict takes the form of:

{
    'type' : 'the_type',
    'unit': 'the_unit',
    'description': 'the_description'
}
Parameters:
  • type_dict (dict) -- A dictionary that contains isotherm type.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the isotherm type or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherm_types_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[dict][source]#

Get all isotherm types.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

dict -- dict of isotherm types

pygaps.parsing.sqlite.isotherm_type_delete_db(iso_type: str, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete isotherm type in the database.

Parameters:
  • data_type (str) -- The type to delete.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherm_property_type_to_db(type_dict: dict, db_path: str = None, overwrite: bool = False, verbose: bool = True, **kwargs: dict)[source]#

Uploads a property type.

The type_dict takes the form of:

{
    'type' : 'the_type',
    'unit': 'the_unit',
    'description': 'the_description'
}
Parameters:
  • type_dict (dict) -- A dictionary that contains property type.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • overwrite (bool) -- Whether to upload the property type or overwrite it. WARNING: Overwrite is done on ALL fields.

  • verbose (bool) -- Extra information printed to console.

pygaps.parsing.sqlite.isotherm_property_types_from_db(db_path: str = None, verbose: bool = True, **kwargs: dict) list[dict][source]#

Get all isotherm property types.

Parameters:
  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

Returns:

dict -- dict of property types

pygaps.parsing.sqlite.isotherm_property_type_delete_db(property_type: str, db_path: str = None, verbose: bool = True, **kwargs: dict)[source]#

Delete isotherm property type in the database.

Parameters:
  • property_type (str) -- Property type to delete.

  • db_path (str, None) -- Path to the database. If none is specified, internal database is used.

  • verbose (bool) -- Extra information printed to console.

NIST ISODB#

Interaction with the NIST ISODB.

pygaps.parsing.isodb.isotherm_from_isodb(filename)[source]#

Load an isotherm from the NIST ISODB.

Parameters:

filename (str) -- ISODB filename to retrieve using the API.

Returns:

Isotherm -- The isotherm from ISODB.

Graphs and plotting#

Isotherm plotting#

Functions for plotting and comparing isotherms.

pygaps.graphing.isotherm_graphs.plot_iso(isotherms, ax=None, x_data: str = 'pressure', y1_data: str = 'loading', y2_data: str = None, branch: str = 'all', x_range: Tuple[float, float] = (None, None), y1_range: Tuple[float, float] = (None, None), y2_range: Tuple[float, float] = (None, None), x_points: Iterable[float] = None, y1_points: Iterable[float] = None, material_basis: str = None, material_unit: str = None, loading_basis: str = None, loading_unit: str = None, pressure_mode: str = None, pressure_unit: str = None, logx: bool = False, logy1: bool = False, logy2: bool = False, color: bool | str | Iterable[str] = True, marker: bool | str | Iterable[str] = True, y1_line_style: dict = None, y2_line_style: dict = None, lgd_keys: list = None, lgd_pos: str = 'best', save_path: str = None)[source]#

Plot the isotherm(s) provided on a single graph.

Parameters:
  • isotherms (PointIsotherms or list of Pointisotherms) -- An isotherm or iterable of isotherms to be plotted.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

  • x_data (str) -- Key of data to plot on the x axis. Defaults to 'pressure'.

  • y1_data (tuple) -- Key of data to plot on the left y axis. Defaults to 'loading'.

  • y2_data (tuple) -- Key of data to plot on the right y axis. Defaults to None.

  • branch (str) -- Which branch to display, adsorption ('ads'), desorption ('des'), or both ('all').

  • x_range (tuple) -- Range for data on the x axis. eg: (0, 1). Is applied to each isotherm, in the unit/mode/basis requested.

  • y1_range (tuple) -- Range for data on the regular y axis. eg: (0, 1). Is applied to each isotherm, in the unit/mode/basis requested.

  • y2_range (tuple) -- Range for data on the secondary y axis. eg: (0, 1). Is applied to each isotherm, in the unit/mode/basis requested.

  • x_points (tuple) -- Specific points of pressure where to evaluate an isotherm. Assumes x=pressure.

  • y1_points (tuple) -- Specific points of loading where to evaluate an isotherm. Assumes y1=loading.

  • material_basis (str, optional) -- Whether the adsorption is read in terms of either 'per volume' or 'per mass'.

  • material_unit (str, optional) -- Unit of loading, otherwise first isotherm value is used.

  • loading_basis (str, optional) -- Loading basis, otherwise first isotherm value is used.

  • loading_unit (str, optional) -- Unit of loading, otherwise first isotherm value is used.

  • pressure_mode (str, optional) -- The pressure mode, either absolute pressures or relative in the form of p/p0, otherwise first isotherm value is used.

  • pressure_unit (str, optional) -- Unit of pressure, otherwise first isotherm value is used.

  • logx (bool) -- Whether the graph x axis should be logarithmic.

  • logy1 (bool) -- Whether the graph y1 axis should be logarithmic.

  • logy2 (bool) -- Whether the graph y2 axis should be logarithmic.

  • color (bool, int, list, optional) -- If a boolean, the option controls if the graph is coloured or grayscale. Grayscale graphs are usually preferred for publications or print media. Otherwise, give a list of matplotlib colours or a number of colours to repeat in the cycle.

  • marker (bool, int, list, optional) -- Whether markers should be used to denote isotherm points. If an int, it will be the number of markers used. Otherwise, give a list of matplotlib markers or a number of markers to repeat in the cycle.

  • y1_line_style (dict) -- A dictionary that will be passed into the matplotlib plot() function. Applicable for left axis.

  • y2_line_style (dict) -- A dictionary that will be passed into the matplotlib plot() function. Applicable for right axis.

  • lgd_keys (iterable) -- The components of the isotherm which are displayed on the legend. For example pass ['material', 'adsorbate'] to have the legend labels display only these two components. Works with any isotherm properties and with 'branch' and 'key', the isotherm branch and the y-axis key respectively. Defaults to 'material' and 'adsorbate'.

  • lgd_pos ([None, Matplotlib legend classifier, 'out bottom', 'out top', 'out left', out right]) -- Specify to have the legend position outside the figure (out...) or inside the plot area itself (as determined by Matplotlib). Defaults to 'best'.

  • save_path (str, optional) -- Whether to save the graph or not. If a path is provided, then that is where the graph will be saved.

Returns:

axes (matplotlib.axes.Axes or numpy.ndarray of them)

Characterisation plotting#

Functions for plotting calculation-specific graphs.

pygaps.graphing.calc_graphs.roq_plot(pressure: Iterable[float], roq_points: Iterable[float], minimum: int, maximum: int, p_monolayer: float, roq_monolayer: float, ax=None)[source]#

Draw a Rouquerol plot.

Parameters:
  • pressure (array) -- Pressure points which will make up the x axis.

  • roq_points (array) -- Rouquerol-transformed points which will make up the y axis.

  • minimum (int) -- Lower bound of the selected points.

  • maximum (int) -- Higher bound of the selected points.

  • p_monolayer (float) -- Pressure at which statistical monolayer is achieved.

  • rol_monolayer (float) -- Rouquerol transform of the point at which statistical monolayer is achieved.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.bet_plot(pressure: Iterable[float], bet_points: Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, p_monolayer: float, bet_monolayer: float, ax=None)[source]#

Draw a BET plot.

Parameters:
  • pressure (array) -- Pressure points which will make up the x axis.

  • bet_points (array) -- BET-transformed points which will make up the y axis.

  • minimum (int) -- Lower bound of the selected points.

  • maximum (int) -- Higher bound of the selected points.

  • slope (float) -- Slope of the chosen linear region.

  • intercept (float) -- Intercept of the chosen linear region.

  • p_monolayer (float) -- Pressure at which statistical monolayer is achieved.

  • rol_monolayer (float) -- BET transform of the point at which statistical monolayer is achieved.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.langmuir_plot(pressure: Iterable[float], langmuir_points: Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, ax=None)[source]#

Draw a Langmuir plot.

Parameters:
  • pressure (array) -- Pressure points which will make up the x axix.

  • langmuir_points (array) -- Langmuir-transformed points which will make up the y axis.

  • minimum (int) -- Lower bound of the selected points.

  • maximum (int) -- Higher bound of the selected points.

  • slope (float) -- Slope of the chosen linear region.

  • intercept (float) -- Intercept of the chosen linear region.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.tp_plot(thickness_curve: Iterable[float], loading: Iterable[float], results: dict, units: dict, alpha_s: bool = False, alpha_reducing_p: float = None, ax=None)[source]#

Draw a t-plot (also used for the alpha-s plot).

Parameters:
  • thickness_curve (array) -- Thickness of the adsorbed layer at selected points. In the case of alpha_s plot, it is the alpha_s transform of the reference isotherm.

  • loading (array) -- Loading of the isotherm to plot.

  • results (dict) -- Dictionary of linear regions selected with the members:

    • 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

  • units (dict) -- A unit dictionary to fill in labels.

  • alpha_s (bool) -- Whether the function is used for alpha_s display.

  • alpha_reducing_p (float) -- The reducing pressure used for alpha_s.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.psd_plot(pore_widths: Iterable[float], pore_dist: Iterable[float], pore_vol_cum: Iterable[float] = None, method: str = None, labeldiff: str = 'distribution', labelcum: str = 'cumulative', log: bool = True, right: int = None, left: int = None, ax=None)[source]#

Draw a pore size distribution plot.

Parameters:
  • pore_widths (array) -- Array of the pore radii which will become the x axis.

  • pore_dist (array) -- Contribution of each pore radius which will make up the y axis.

  • pore_vol_cum (array) -- Pre-calculated cumulative value.

  • method (str) -- The method used. Will be a string part of the title.

  • labeldiff (str) -- The label for the plotted data, which will appear in the legend.

  • labelcum (str, optional) -- The label for the cumulative data, which will appear in the legend. Set to None to remove cumulative distribution

  • log (bool) -- Whether to display a logarithmic graph.

  • right (int) -- Higher bound of the selected pore widths.

  • right (int) -- Lower bound of the selected pore widths.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.isosteric_enthalpy_plot(loading: Iterable[float], isosteric_enthalpy: Iterable[float], std_err: Iterable[float], units: dict, log: bool = False, ax=None)[source]#

Draws the isosteric enthalpy plot.

Parameters:
  • loading (array) -- Loadings for which the isosteric enthalpy was calculated.

  • isosteric_enthalpy (array) -- The isosteric enthalpy corresponding to each loading.

  • std_err (array) -- Standard error for each point.

  • units (dict) -- A unit dictionary to fill in labels.

  • log (int) -- Whether to display a logarithmic graph.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.initial_enthalpy_plot(loading: Iterable[float], enthalpy: Iterable[float], fitted_enthalpy: Iterable[float], log: bool = False, title: str = None, extras=None, ax=None)[source]#

Draws the initial enthalpy calculation plot.

Parameters:
  • loading (array) -- Loadings for which the initial enthalpy was calculated.

  • enthalpy (array) -- The enthalpy corresponding to each loading.

  • fitted_enthalpy (array) -- The predicted enthalpy corresponding to each loading.

  • log (int) -- Whether to display a logarithmic graph

  • title (str) -- Name of the material to put in the title.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.dra_plot(logv: Iterable[float], log_n_p0p: Iterable[float], minimum: int, maximum: int, slope: float, intercept: float, exp: float, ax=None)[source]#

Draw a Dubinin plot.

Parameters:
  • logv (array) -- Logarithm of volume adsorbed.

  • log_n_p0p (array) -- Logarithm of pressure term.

  • minimum (int) -- Lower index of the selected points.

  • maximum (int) -- Higher index of the selected points.

  • slope (float) -- Slope of the fitted line.

  • intercept (float) -- Intercept of the fitted line.

  • exp (float) -- The exponent of the DA/DR graph.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

matplotlib.axes -- Matplotlib axes of the graph generated. The user can then apply their own styling if desired.

pygaps.graphing.calc_graphs.virial_plot(loading: Iterable[float], ln_p_over_n: Iterable[float], n_load: Iterable[float], p_load: Iterable[float], added_point: bool, ax=None)[source]#

Draw a Virial plot.

Model fitting plots#
pygaps.graphing.model_graphs.plot_model_guesses(attempts, pressure, loading)[source]#

Plot one or more isotherm model fits.

IAST plotting#

Functions for plotting graphs related to IAST calculations.

pygaps.graphing.iast_graphs.plot_iast(p_data: list, l_data: list, ads: list, p_label: str, l_label: str, ax=None)[source]#

Plot uptake-vs-pressure graph from IAST data.

Parameters:
  • p_data (array or list) -- The pressures at which uptakes are calculated.

  • l_data (2D array or list of lists) -- Uptake for each component a function of pressure.

  • ads (list[str]) -- Names of the adsorbates.

  • p_unit (str) -- Unit of pressure, for axis labelling.

  • l_unit (str) -- Unit of loading, for axis labelling.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

ax (matplotlib ax) -- The ax object.

pygaps.graphing.iast_graphs.plot_iast_vle(x_data: list, y_data: list, ads1: str, ads2: str, pressure: float, p_unit: str, ax=None)[source]#

Plot a vapour-adsorbed equilibrium graph from IAST data.

Parameters:
  • x_data (array or list) -- The molar fraction in the adsorbed phase.

  • y_data (array or list) -- The molar fraction in the gas phase.

  • ads1 (str) -- Name of the adsorbate which is regarded as the main component.

  • ads2 (str) -- Name of the adsorbate which is regarded as the secondary component.

  • pressure (float) -- Pressure at which the vle is plotted.

  • p_unit (str) -- Pressure unit, for labelling.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

ax (matplotlib ax) -- The ax object.

pygaps.graphing.iast_graphs.plot_iast_svp(p_data: list, s_data: list, ads1: str, ads2: str, fraction: float, p_unit: str, ax=None)[source]#

Plot a selectivity-vs-pressure graph from IAST data.

Parameters:
  • p_data (array or list) -- The pressures at which selectivity is calculated.

  • s_data (array or list) -- The selectivity towards the main component as a function of pressure.

  • ads1 (str) -- Name of the adsorbate which is regarded as the main component.

  • ads2 (str) -- Name of the adsorbate which is regarded as the secondary component.

  • fraction (float) -- Molar fraction of the main component in the mixture.

  • p_unit (str) -- Unit of the pressure, for axis labelling.

  • ax (matplotlib axes object, default None) -- The axes object where to plot the graph if a new figure is not desired.

Returns:

ax (matplotlib ax) -- The ax object.

Default matplotlib styles#

Contains global style dictionaries for matplotlib to be applied.

pygaps.graphing.mpl_styles.ISO_MARKERS: tuple[str] = ('o', 's', 'D', 'P', '*', '<', '>', 'X', 'v', '^')#

Markers used in plotting.

pygaps.graphing.mpl_styles.Y1_COLORS: tuple[str] = ('#003f5c', '#58508d', '#bc5090', '#ff6361', '#ffa600')#

Colors used in main (y1) plotting.

pygaps.graphing.mpl_styles.Y2_COLORS: tuple[str] = ('#0082bd', '#8674c5', '#d15d9e', '#ea5e59', '#ca8300')#

Colors used in secondary (y2) plotting.

pygaps.graphing.mpl_styles.BASE_STYLE: dict[str, t.Any] = {'legend.frameon': False}#

Style applied to ALL matplotlib figures in pygaps.

pygaps.graphing.mpl_styles.ISO_STYLE: dict[str, t.Any] = {'axes.labelsize': 15, 'axes.titlesize': 20, 'axes.titley': 1.01, 'figure.figsize': (6, 6), 'image.aspect': 'equal', 'legend.fontsize': 11, 'legend.handlelength': 2, 'lines.linewidth': 1.5, 'lines.markersize': 5, 'xtick.labelsize': 13, 'ytick.labelsize': 13}#

Style applied to Isotherm figures.

pygaps.graphing.mpl_styles.POINTS_MUTED: dict[str, t.Any] = {'lines.linestyle': 'none', 'lines.marker': 'o', 'lines.markeredgecolor': 'grey', 'lines.markeredgewidth': 1.5, 'lines.markerfacecolor': 'none', 'lines.markersize': 5}#

Style component to generate "muted/unselected" points.

pygaps.graphing.mpl_styles.POINTS_HIGHLIGHTED: dict[str, t.Any] = {'lines.linestyle': 'none', 'lines.marker': 'o', 'lines.markeredgecolor': 'r', 'lines.markerfacecolor': 'r', 'lines.markersize': 5}#

Style component to generate "highlighted/elected" points.

pygaps.graphing.mpl_styles.POINTS_IMPORTANT: dict[str, t.Any] = {'lines.linestyle': 'none', 'lines.marker': 'X', 'lines.markeredgecolor': 'k', 'lines.markerfacecolor': 'k', 'lines.markersize': 10}#

Style component to generate "single important" points.

pygaps.graphing.mpl_styles.LINE_FIT: dict[str, t.Any] = {'lines.linestyle': '--', 'lines.marker': ''}#

Style component to generate a line fitted through points.

pygaps.graphing.mpl_styles.LINE_ERROR: dict[str, t.Any] = {'lines.linestyle': '--', 'lines.marker': 'o', 'lines.markersize': 3}#

Style component to apply to error bars.

Unit handling#

Perform conversions between different variables used.

pygaps.units.converter_mode.c_pressure(value: float, mode_from: str, mode_to: str, unit_from: str, unit_to: str, adsorbate=None, temp: float | None = None)[source]#

Convert pressure units and modes.

Adsorbate name and temperature have to be specified when converting between modes.

Parameters:
  • value (float) -- The value to convert.

  • mode_from (str) -- Whether to convert from a mode.

  • mode_to (str) -- Whether to convert to a mode.

  • unit_from (str) -- Unit from which to convert.

  • unit_to (str) -- Unit to which to convert.

  • adsorbate (Adsorbate, optional) -- Adsorbate on which the pressure is to be converted. Required for mode change.

  • temp (float, optional) -- Temperature at which the pressure is measured, in K. Required for mode changes to relative pressure.

Returns:

float -- Pressure converted as requested.

Raises:

ParameterError -- If the mode selected is not an option.

pygaps.units.converter_mode.c_loading(value: float, basis_from: str, basis_to: str, unit_from: str, unit_to: str, adsorbate=None, temp: float | None = None, basis_material: str | None = None, unit_material: str | None = None)[source]#

Convert loading units and basis.

Adsorbate name and temperature have to be specified when converting between basis.

Parameters:
  • value (float) -- The value to convert.

  • basis_from (str) -- Whether to convert from a basis.

  • basis_to (str) -- Whether to convert to a basis.

  • unit_from (str) -- Unit from which to convert.

  • unit_to (str) -- Unit to which to convert.

  • adsorbate (str, optional) -- Adsorbate for which the pressure is to be converted. Only required for some conversions.

  • temp (float, optional) -- Temperature at which the loading is measured, in K. Only required for some conversions.

  • basis_material (str, optional) -- The basis of the material. Only required for conversions involving percentage/fraction.

  • unit_material (str, optional) -- The unit of the material. Only required for conversions involving percentage/fraction.

Returns:

float -- Loading converted as requested.

Raises:

ParameterError -- If the mode selected is not an option.

pygaps.units.converter_mode.c_material(value: float, basis_from: str, basis_to: str, unit_from: str, unit_to: str, material=None)[source]#

Convert material units and basis.

The name of the material has to be specified when converting between basis.

Parameters:
  • value (float) -- The value to convert.

  • basis_from (str) -- Whether to convert from a basis.

  • basis_to (str) -- Whether to convert to a basis.

  • unit_from (str) -- Unit from which to convert.

  • unit_to (str) -- Unit to which to convert.

  • material (str) -- Name of the material on which the value is based.

Returns:

float -- Loading converted as requested.

Raises:

ParameterError -- If the mode selected is not an option.

pygaps.units.converter_mode.c_temperature(value: float, unit_from: str, unit_to: str)[source]#

Convert temperatures.

Parameters:
  • value (float) -- The value to convert.

  • unit_from (str) -- Unit from which to convert.

  • unit_to (str) -- Unit to which to convert.

Returns:

float -- Temperature converted as requested.

Raises:

ParameterError -- If the unit selected is not an option.

Define and perform conversions between different units used.

pygaps.units.converter_unit.c_unit(unit_list, value, unit_from, unit_to, sign=1)[source]#

Convert units based on their proportions in a dictionary.

Parameters:
  • unit_list (dict) -- The dictionary with the units and their relationship.

  • value (dict) -- The value to convert.

  • unit_from (str) -- Unit from which to convert.

  • unit_from (str) -- Unit to which to convert.

  • sign (int) -- If the conversion is inverted or not.

Returns:

float -- Value converted as requested.

Raises:

ParameterError -- If the unit selected is not an option.

Utilities#

Isotherm interpolator#

A class used for isotherm interpolation.

class pygaps.utilities.isotherm_interpolator.IsothermInterpolator(known_data, interp_data, interp_branch='ads', interp_kind='linear', interp_fill=None)[source]#

Class used to interpolate between isotherm points.

Call directly to use.

It is mainly a wrapper around scipy.interpolate.interp1d.

Parameters:
  • interp_type (str) -- What variable the interpolator works on (pressure, loading etc).

  • known_data (str) -- The values corresponding to the input variable.

  • interp_data (str) -- The values corresponding to the variable to be interpolated.

  • interp_branch (str, optional) -- Stores which isotherm branch the interpolator is based on.

  • interp_kind (str, optional) -- Determine which kind of interpolation is done between the datapoints.

  • interp_fill (str, optional) -- The parameter passed to the scipy.interpolate.interp1d function to determine what to do outside data bounds.

Thermodynamic backend utilities#

Utilities for interacting with the CoolProp backend.

pygaps.utilities.coolprop_utilities.COOLPROP_BACKEND = 'HEOS'#

The backend which CoolProp uses, normally either HEOS or REFPROP.

pygaps.utilities.coolprop_utilities.backend_use_refprop()[source]#

Switch the equation of state used to REFPROP. User should have REFPROP installed.

pygaps.utilities.coolprop_utilities.backend_use_coolprop()[source]#

Switch the equation of state used to HEOS (CoolProp).

Python utilities#

Collections of various python utilities.

pygaps.utilities.python_utilities.zip_varlen(*iterables)[source]#

Variable length zip() function.

pygaps.utilities.python_utilities.grouped(iterable, n)[source]#

Divide an iterable in subgroups of max n elements.

pygaps.utilities.python_utilities.deep_merge(a, b, path=None, update=True)[source]#

Recursive updates of a dictionary.

class pygaps.utilities.python_utilities.SimpleWarning[source]#

Context manager overrides warning formatter to remove unneeded info.

Exceptions#

Custom errors thrown by the program.

exception pygaps.utilities.exceptions.pgError[source]#

Base error raised by pyGAPS.

exception pygaps.utilities.exceptions.ParameterError[source]#

Raised when one of the parameters is unsuitable.

exception pygaps.utilities.exceptions.CalculationError[source]#

Raised when a calculation fails.

exception pygaps.utilities.exceptions.ParsingError[source]#

Raised when parsing fails.

exception pygaps.utilities.exceptions.GraphingError[source]#

Raised when graphing fails.

Math utilities#

Function-independent mathematical calculations.

pygaps.utilities.math_utilities.split_ads_data(data, pressure_key)[source]#

Find the inflection in an adsorption dataset with adsorption/desorption.

pygaps.utilities.math_utilities.find_limit_indices(array: list, limits: tuple[float, float] = None, smallest_selection: int = 3)[source]#

Find the indices of an array limits

pygaps.utilities.math_utilities.find_linear_sections(xdata, ydata)[source]#

Find all sections of a curve which are linear.

pygaps.utilities.math_utilities.bspline(xs, ys, n=100, degree=2, periodic=False)[source]#

Calculate n samples on a b-spline.

Adapted from: https://stackoverflow.com/questions/24612626/b-spline-interpolation-with-python

Parameters:
  • xs (array) -- X points of the curve to fit

  • ys (array) -- Y points of the curve to fit

  • n (int) -- Number of samples to return

  • degree (int) -- Curve degree

  • periodic (bool) -- True - Curve is closed False - Curve is open

String processing utilities#

General functions for string transformations.

pygaps.utilities.string_utilities.convert_chemformula_ltx(string: str) str[source]#

Convert a chemical formula string to a matplotlib parsable format (latex).

Parameters:

string or Adsorbate (str) -- String to process.

Returns:

str -- Processed string.

pygaps.utilities.string_utilities.convert_unit_ltx(string: str, negative: bool = False) str[source]#

Convert a unit string to a nice matplotlib parsable format (latex).

Parameters:
  • string (str) -- String to process.

  • negative (bool) -- Whether the power is negative instead.

Returns:

str -- Processed string.

pygaps.utilities.string_utilities.cast_string(s)[source]#

Check and cast strings of various data types.

SQLite utilities#

General functions for SQL query building.

pygaps.utilities.sqlite_utilities.db_execute_general(statement: str, pth: str, verbose: bool = False)[source]#

Execute general SQL statements.

Parameters:
  • statement (str) -- SQL statement to execute.

  • pth (str) -- Path where the database is located.

  • verbose (bool) -- Print out extra information.

pygaps.utilities.sqlite_utilities.build_update(table: str, to_set: list, where: list, prefix: str | None = None)[source]#

Build an update request.

Parameters:
  • table (str) -- Table where query will be directed.

  • to_set (iterable) -- The list of columns to update.

  • where (iterable) -- The list of conditions to constrain the query.

  • prefix (str, optional) -- The prefix to introduce to the second part of the constraint.

Returns:

str -- Built query string.

pygaps.utilities.sqlite_utilities.build_insert(table: str, to_insert: list)[source]#

Build an insert request.

Parameters:
  • table (str) -- Table where query will be directed.

  • to_insert (iterable) -- The list of columns where the values will be inserted.

Returns:

str -- Built query string.

pygaps.utilities.sqlite_utilities.build_select(table: str, to_select: list, where: list | None = None)[source]#

Build a select request.

Parameters:
  • table (str) -- Table where query will be directed.

  • to_set (iterable) -- The list of columns to select.

  • where (iterable) -- The list of conditions to constrain the query.

Returns:

str -- Built query string.

pygaps.utilities.sqlite_utilities.build_select_unnamed(table: str, to_select: list, where: list, join: str = 'AND')[source]#

Build an select request with multiple parameters.

Parameters:
  • table (str) -- Table where query will be directed.

  • to_set (iterable) -- The list of columns to select

  • where (iterable) -- The list of conditions to constrain the query.

  • join (str) -- The joining clause of the parameters.

Returns:

str -- Built query string.

pygaps.utilities.sqlite_utilities.build_delete(table: str, where: list)[source]#

Build a delete request.

Parameters:
  • table (str) -- Table where query will be directed.

  • where (iterable) -- The list of conditions to constrain the query.

Returns:

str -- Built query string.

pygaps.utilities.sqlite_utilities.check_SQL_bool(val)[source]#

Check if a value is a bool. Useful for storage.

pygaps.utilities.sqlite_utilities.check_SQL_python_type(val)[source]#

Convert between the database string dtype and a python data type.

pygaps.utilities.sqlite_utilities.find_SQL_python_type(val)[source]#

Convert between a python data type and a database string.

Generate the default sqlite database.

pygaps.utilities.sqlite_db_creator.db_create(path: str, verbose: bool = False)[source]#

Create the entire database.

Parameters:
  • path (str) -- Path where the database is created.

  • verbose (bool) -- Print out extra information.

All sql pragmas to generate the sqlite database.

Changelog#

4.5.0 (2023-06-20)

  • Added a new function that calculates the enthalpy of adsorption using a method proposed by Whittaker et al in pygaps.characterisation.enth_sorp_whittaker. Thanks to L Scott Blankenship for contribution!

  • Updated AIF parser to latest version. Thanks to Jack Evans for contribution.

  • Refactored DR/DA calculations.

  • Docs updating to fix issue #42

  • Added data types to excel output parsing to fix edge cases where these were not correctly parsed.

  • Testing correctly on Python 3.11.

  • Fixed various accumulating issues and bugs and deprecations.

4.4.0 (2022-09-03)#

4.3.0 (2022-08-27)#

  • AIF can read/write all isotherm types (models/points). Also further improved parsing of units and metadata.

  • Quantachrome txt files are now fully parseable

  • Isotherm data can now be other things beside numbers.

  • Added two standard isotherms (silica+carbon black). Can be used in thickness calculations.

4.2.0 (2022-06-29)#

  • Better error handling of nonphysical mesoporous PSD models

  • CLI now prints package version with --version argument

4.1.1 (2022-03-10)#

Fixes:

  • AIF file export now more reliable with custom properties

  • Isotherm models correctly instantiate their variables (parameters would otherwise be shared between instances)

4.1.0 (2022-03-02)#

New features:

  • 🐍 Minimum python reduced to 3.6 to increase compatibility.

  • 🎆 pyGAPS is now (actually) on conda-forge.

4.0.0 (2022-02-23)#

New features:

  • ⚠️ To reduce import bloat pyGAPS is now modular. Previously global functions must be accessed through submodules. For example: pygaps.area_BET now must be imported from pygaps.characterisation.area_BET.

  • 🎆 pyGAPS is now on conda-forge

  • Volumetric adsorbed amount is now given in either volume_gas or volume_liquid basis, corresponding to the volume amount the adsorbate would occupy in the bulk gas phase, or the volume of an ideal liquid phase of adsorbate (at isotherm temperature). The old loading_basis of volume is deprecated and automatically converted to volume_gas while emitting a warning.

  • Manufacturer parsing is streamlined with the introduction of the pygaps.parsing.isotherm_from_commercial function.

  • Drastically improved reliability and modularity of code in preparation for the release of the pyGAPS-gui interface.

  • Added a "zero" thickness model that assumes no mono/multilayer sorption. Useful in the case of condensation in hydrophobic materials.

  • Better documentation.

Changes:

  • ⚠️ Minimum python is now 3.7, maximum increased to 3.10.

  • Removed the need to pass DataFrame column names with the other_keys syntax. PointIsotherms, now save all passed DataFrame columns.

  • Smart assigning of isotherm metadata caused confusion and was removed. Metadata assigned like isotherm.myparam is now no longer serialized to dictionaries, parsers etc. Instead, isotherms have a isotherm.properties dictionary which contains all metadata.

  • Adsorption/desorption branches are internally represented as 0 and 1 instead of False/True. This allows the possibility for further cycles to be introduced in a future release.

  • Isotherm material and adsorbate are now always instantiated as pygaps.Material and pygaps.Adsorbate classes.

Plus many small and large bugs fixed.

3.1.0 (2021-04-22)#

New features:

  • pyGAPS is now capable of parsing to and from Adsorption Information Files (AIF). For more info, see Evans, Jack D., Volodymyr Bon, Irena Senkovska, and Stefan Kaskel. ‘A Universal Standard Archive File for Adsorption Data’. Langmuir, 2 April 2021, DOI: 10.1021/acs.langmuir.1c00122.

3.0.0 (2021-03-14)#

New features:

  • The internal Adsorbate list now contains over 170 analytes, 81 of which have a correspondence in the thermodynamic backend. This includes multiple vapours, VOCs, and refrigerants.

  • Added new mode for pressure as "relative%", which represents relative pressure as a percentage rather than a fraction (i.e. p/p0 * 100).

  • Added two new modes for loading as "fraction" and "percent". This ties the uptake to the same basis as the adsorbate (i.e. weight%, volume%, mol% or fractions thereof).

  • Conversely, NIST format isotherms based on "wt%" can also be converted.

  • Significant improvements to the Horvath-Kawazoe methods of pore size distribution, including more pore geometries (cylindrical and spherical through the Saito-Foley and Cheng-Yang modifications) and the inclusion of extended HK models, with the Cheng-Yang and Rege-Yang corrections.

  • Command-line interface: a CLI entry point is automatically added during pyGAPS installation and can be called with pygaps -h to perform some simple commands.

  • Isotherm JSON parser (pygaps.isotherm_to_json) now passes all extra parameters to the json.dump function.

  • Perform isotherm branch separation based on maximum pressure, rather than first derivative. In such way, slight uncertainty in pressures won't lead to a wrong assignment.

  • The reference area for an alpha_s calculation can now be specified as either "BET" or "Langmuir".

  • Convenience function isotherm.convert() which combines all isotherm conversion modes.

  • Convenience function pygaps.model_iso() which fits a model to a PointIsotherm, effectively wrapping ModelIsotherm.from_pointisotherm.

  • Convenience functions for isotherm parsing: isotherm.to_json(), isotherm.to_csv() and isotherm.to_xl().

  • Parsing from instrument output files now gets more information.

  • Plot quality has been overall improved.

  • Improved performance for isotherm conversions.

  • General refactoring and speed-ups.

  • Switched to GitHub actions for CI, now MacOS builds are also tested.

Breaking changes:

  • Included Python 3.8 and deprecated Python 3.5.

  • All parameters like adsorbate_basis or adsorbate_unit have been changed to material_basis and material_unit for consistency. Old format should still work for some time.

  • Some model names have been changed to include only ASCII: JensenSeaton, FHVST, WVST.

  • For isotherm pressure/loading, a limits tuple is now passed instead of min_range and max_range, as for other functions in pyGAPS.

  • JSON ModelIsotherms now have name instead of model as the model name. This is now consistent with both CSV and Excel.

  • The isotherm_to_jsonf and isotherm_from_jsonf functions have been removed. Functionality has been merged with isotherm_to_json similarly to the pandas model.

  • Removed the util_get_file_paths function.

Fixes:

  • Volumetric -> molar conversions were not calculated correctly.

  • Isosteric enthalpy could not be calculated if the isotherm was not in mmol/g.

  • ModelIsotherm creation could in some cases ignore isotherm branch splitting.

  • BET area now attempts to pick at least 3 points if physically consistent. Should stop failing on some isotherms.

  • BET/Langmuir area maximum calculation was offset by one point.

  • The "section" returned in tplot/alphas is now consistent for both manual and automatic limits: a list indices for selected points

2.0.2 (2019-12-18)#

New features:

  • Added fluids to database: n-pentane, n-hexane, n-octane, o-xylene, m-xylene, p-xylene, cyclohexane, hydrogen sulphide and sulphur hexafluoride.

Fixes:

  • Converting Adsorbates to a dictionary now correctly outputs the list of aliases.

  • Changed stored critical point molar mass values for some adsorbates.

2.0.1 (2019-07-08)#

  • Fixed error in dft kernel acquisition.

  • Removed duplicate plot generation from virial initial henry.

  • Fixed Appveyor testing.

2.0.0 (2019-07-08)#

Major pyGAPS release following peer review on related manuscript. Several breaking changes with previous codebase, in particular with basic isotherm parameters and module structure. Several function names and parameters have changed as well.

Breaking changes:

  • Renamed isotherm parameter t_iso to temperature for clarity.

  • Renamed isotherm parameter material_name to material.

  • Made material_batch an optional parameter.

  • Renamed the pytest.calculations submodule to pytest.characterisation.

  • Placed all isotherm models in a pytest.modelling submodule.

New features:

  • The isotherm branches are now saved in the file representation (JSON, CSV, Excel).

  • Not specifying units now raises a warning.

  • After attempting a model fit or guess for the creation of a ModelIsotherm, a fit graph is now plotted alongside the data to be modelled.

  • Added a new parameters named logy1 and logy2 to set the plotting vertical axes to be logarithmic.

  • To remove the legend now set the lgd_pos to None

  • Pore size distribution improvements:

    • Changed names of PSD functions to psd_microporous, psd_mesoporous and psd_dft, respectively.

    • Simplified functions for ease of use and understanding.

    • Added cumulative pore volume to the return dictionary of all psd functions.

    • Generalized Kelvin methods (psd_mesoporous) to other pore geometries, such as slit and sphere.

    • Added a new Kelvin function, the Kelvin Kruck-Jaroniec-Sayari correction (use with kelvin_function='Kelvin-KJS'

    • Corrected a conversion error in the DFT fitting routing.

    • Changed HK dictionary name OxideIon(SF) -> 'AlSiOxideIon'

    • Added a new HK dictionary 'AlPhOxideIon'

1.6.1 (2019-05-09)#

New features:

  • Simplified the slope method for Henry's constant calculation

Bugfixes:

  • Ensured that model initial fitting guess cannot be outside the bounds of the variables.

1.6.0 (2019-05-08)#

New features:

  • Added a function to get isotherms from the NIST ISODB, pygaps.load_nist_isotherm which takes the ISODB filename as an argument.

  • Added hexane as an adsorbate in the database.

  • Isotherm adsorbate is now a pygaps.Adsorbate object and can be accessed directly for all attributes (only when available in the internal database, otherwise still a string).

  • ModelIsotherms can now be saved and imported from JSON, CSV and Excel.

  • Added a marker option to the plot_iso function which acts similar to the color parameter and allows simple selection of the marker style.

  • Added three new isotherm models: Freundlich, Dubinin-Radushkevich and Dubinin-Astakov. They can be used for fitting by specifying Freundlich, DR or DA as the model, respectively.

  • Faster performance of some models due to analytical calculations, as well as more thorough testing

  • Isotherm modelling backend is now more robust.

  • Added an isotherm plot function to plot an individual isotherm.

  • Added functions to import and export JSON files directly from a file: isotherm_from_jsonf and isotherm_to_jsonf.

  • Added github issue templates.

  • Removed some plotting styles.

Breaking changes:

  • Deprecated and removed the MADIREL excel format.

  • Renamed isosteric_heat functions as isosteric_enthalpy for more correct nomenclature.

  • Some model parameters have been renamed for consistency.

Bugfixes:

  • REFPROP backend now correctly accessible (it was previously impossible to activate).

  • Fixed issue in excel import which could lead to incorrect import.

  • Some of the adsorbate values in the database were incorrect. They have been now updated.

  • Fixed secondary data not being automatically plotted when print_info called.

1.5.0 (2019-03-12)#

New features:

  • Increased number of adsorbates available in pyGAPS to 40.

  • New material characterisation functions: Dubinin-Radushkevich (dr_plot) and Dubinin-Astakov (da_plot) plots.

  • Added a new way to create an isotherm, from an two arrays of pressure and loading (the old DataFrame method is still valid but changed: check breaking changes).

  • Made adsorbates searchable by a list of aliases rather than a single name.

  • Exposed the CoolProp backend on adsorbate objects for convenience, it is accessible through the adsorbate.backend property.

  • Streamlined the internal database functions.

  • Updated NIST json import to new format. Cannot import multicomponent isotherms.

  • Functions which generate matplotlib graphs now can take an Ax as parameter (similar to behaviour of pandas) to plot on existing figures.

  • Changed behaviour of ModelIsotherm.guess function to accept a list of models to attempt to guess for.

  • Added b-spline smoothing to output of dft fitting.

Breaking changes:

  • The Sample class is now renamed as Material.

  • Isotherm creation parameters have changed from 'sample_name', 'sample_batch' and 't_exp' to 'material_name', 'material_batch' and 't_iso'.

  • Backend database has been simplified. Many required fields are no longer present and left to the discretion of the user.

  • Several database functions have been renamed. All functions switched: 'sample' -> 'material' and 'experiment' -> 'isotherm'.

  • When passing a DataFrame for isotherm creation, it now has to be specified as the parameter 'isotherm_data'.

  • Isotherm unique ID is now generated on the fly (previously generated at each isotherm modification). It also now takes into account only the required parameters for each isotherm ( 'sample_name', 'sample_batch', 't_exp' and 'adsorbate') as well as the model name, if the isotherm is a ModelIsotherm.

  • Renamed Adsorbate.from_list() method to Adsorbate.find()

Bugfixes:

  • Fixed issue in CSV import which read all values as strings (instead of floats/bools)

  • Fixed an issue with Excel import of bools, as they were previously read as 1/0

  • Fixed a bug where the automatic branch detection was not working when the DataFrame passed had a non-standard index.

  • Fixed not being able to call _repr_ on an isotherm.

1.4.0 (2018-11-10)#

New features:

  • Added the GAB isotherm model

Bugfixes:

  • Improved pore size distribution calculations to display cumulative pore volume when called.

  • Fixed the "all-nol" selection parameter for legend display in isotherm graphs.

1.3.0 (2018-08-13)#

New features:

  • Added an excel import which can take Micromeritics or Belsorp report (.xls) files. Micromeritics code was taken from the official python repo.

  • Added an import option which can read and import Belsorp data (.DAT) files.

  • Improved plotting functions to allow for more customisation over how the graph looks.

  • The extra arguments to print_info() are now passed to the plotting function allowing for styles such as #8.

Breaking changes:

  • The unique isotherm ID is now generated only on a small subset of properties instead of all isotherm properties.

  • The isotherm 'other_properties' subdictionary has been removed. Instead, all isotherm properties are now direct members of the class.

  • When plotting, isotherm branches are now defined as 'ads', 'des' 'all' (both branches) and 'all-nol' (both branches without legend entry) instead of a list of branches.

  • Plot types are now universal. Any property can be plotted against any other property by specifying the x_data, y1_data and y2_data.

Bugfixes:

  • Fixed 'source' not being recognised as an isotherm field

  • Re-worked plot_iso color selection to avoid errors (#10)

  • Re-worked plot_isp legend placement to ensure no overlap

  • Added correct common name for ethylene, propylene, methanol and ethanol in the database

  • Renamed some model parameters for consistency

  • A lot of typo fixes

1.2.0 (2018-02-19)#

New features:

  • The plotting legend now works with any isotherm attribute specified

  • Changed model parent class to print out model name when displayed

  • Added Toth and Jensen-Seaton models to the IAST calculation (spreading pressure is computed numerically using scipy.integrate.quad, #7)

Bugfixes:

  • Fixed an issue where the returned IAST selectivity v pressure data would not include all pressures

  • Changed sqlite retrieval order to improve performance (#2)

  • Fixed an error where IAST vle data was plotted opposite to the graph axes

  • Fixed a mistake in the Jensen-Seaton equation

  • Fixed a mistake in the FH-VST equation

1.1.1 (2018-02-11)#

New features:

  • Allowed for branch selection for isosteric heat and fixed an error where this was an issue (#3)

Bugfixes:

  • Fixed an issue when plotting isotherms with and without secondary data simultaneously

  • Fixed error with magnitude of polarizability of adsorbate from database in microporous PSD

1.1.0 (2018-01-24)#

  • Automatic travis deployment to PyPI

  • Improved enthalpy modelling for initial enthalpy determination

  • Improved documentation

1.0.1 (2018-01-08)#

  • Fixed wrong value of polarizability for nitrogen in database

  • Added a check for initial enthalpy when the isotherm is measured in supercritical mode

1.0.0 (2018-01-01)#

  • Improved unit management by adding a unit/basis for both the adsorbent (ex: amount adsorbed per g, kg or cm3 of material are all valid) and loading (ex: mmol, g, kg of gas adsorbed per amount of material are all valid)

  • Separated isotherm models so that they can now be easily created by the used.

  • Added new isotherm models: Toth, Jensen-Seaton, W-VST, FH-VST.

  • Made creation of classes (Adsorbate/Sample/Isotherms) more intuitive.

  • Many small fixes and improvements

0.9.3 (2017-10-24)#

  • Added unit_adsorbate and basis_loading as parameters for an isotherm, although they currently do not have any influence on data processing

0.9.2 (2017-10-24)#

  • Slightly changed json format for efficiency

0.9.1 (2017-10-23)#

  • Better examples

  • Small fixes and improvements

0.9.0 (2017-10-20)#

  • Code is now in mostly working state.

  • Manual and reference are built.

0.1.0 (2017-07-27)#

  • First release on PyPI.

Contributing#

Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.

Bug reports#

When reporting a bug please include:

  • Your operating system name and version.

  • Any details about your local setup that might be helpful in troubleshooting.

  • Detailed steps to reproduce the bug.

A helpful GitHub issue template is provided.

Documentation improvements#

The package could always use more documentation, whether as part of the official docs, in docstrings, or even on the web in blog posts, articles, and such. If you think something is unclear, incomplete or should be rewritten, please submit an issue report or make a pull request.

Feature requests and feedback#

The best way to send feedback is to file an an improvement proposal

If you are proposing a feature:

  • Explain in detail how it would work.

  • Keep the scope as narrow as possible, to make it easier to implement.

  • And of course, remember that this project is developed in the spare time of the maintainers, and that code contributions are welcome :)

Development#

To set up pyGAPS for local development:

  1. Fork pyGAPS (look for the "Fork" button).

  2. Clone your fork locally:

    git clone https://github.com/YOURNAMEHERE/pyGAPS
    
  3. Install the package in editable mode with its dev requirements:

    pip install -e .[dev,docs]
    
  4. Create a branch for local development. This project uses the GIT FLOW model:

    git checkout -b name-of-your-bugfix-or-feature
    

    Now you can make your changes locally.

  5. When you're done making changes, run all the tests:

    pytest
    
  6. Commit your changes and push your branch to GitHub:

    git add .
    git commit -m "Your detailed description of your changes."
    git push origin name-of-your-bugfix-or-feature
    
  7. Submit a pull request through the GitHub website. Testing on all environments will be automatically performed.

Pull Request Guidelines#

If you need some code review or feedback while you're developing the code just make the pull request.

For merging, you should:

  1. Include passing tests (run pytest) [1].

  2. Update documentation when there's new API, functionality etc.

  3. Add a note to CHANGELOG.rst about the changes.

  4. Add yourself to AUTHORS.rst.

Authors#

  • Paul Iacomi - main developer

  • Cory Simon - pyIAST functionality

  • Bastien Aillet - BET surface area functionality

  • Chris Murdock - originally coded Micromeritics report import functionality

  • L. Scott Blankenship - implementing Whittaker method for isosteric enthalpy of adsorption

  • Jack Evans - contributions towards AIF parsing

Contributors#

  • Anne Galarneau (ICGM) - scientific discussion for appropriate standard isotherms

  • Thomas B. Cooper - feedback for BET/Langmuir area improvements

  • Sebastian Rejman - additional t_curve

Indices and tables#