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).
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.
If you are interested in implementing a particular feature,
or obtaining professional level support, contact us here
Bugs or questions?.
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
To install the development branch, clone the repository from GitHub.
Then install the package with pip either in regular or developer mode.
gitclonehttps://github.com/pauliacomi/pyGAPS
# then install editable/develop mode# adding [dev] will install the development dependencies
pipinstall-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.
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.
The installation process should take care of the dependencies for you.
If using pip all you need to do is:
pipinstallpygaps
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:
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 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.
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:
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.
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:
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]:
importpandasaspd# create (or import) a DataFramedata=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 modeloading_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.
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.
To see just a plot of the isotherm, use the plot function:
[10]:
isotherm.plot()
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 unitisotherm.plot(pressure_unit='torr',loading_basis='percent')# The isotherm is still internally in the same unitsprint(f"Isotherm is still in {isotherm.pressure_unit} and {isotherm.loading_unit}.")
Isotherm is still in bar and mmol.
[12]:
# While the underlying units can be completely convertedisotherm.convert(pressure_mode='relative')print(f"Isotherm is now permanently in {isotherm.pressure_mode} pressure.")isotherm.plot()
Isotherm is now permanently in relative pressure.
Now that the PointIsotherm is created, we are ready to do some analysis of its properties.
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
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:
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.
Attempting to model using DSLangmuir.
Model DSLangmuir success, RMSE is 0.0402
<ModelIsotherm 38c2a31aae475e0e5f6285194152baf0>: 'nitrogen' on 'Carbon X1' at 77.355 K
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.
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.
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 infomodel_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
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 modelmodel_iso.loading_at(1.0)
17.46787643653736
[20]:
# Returns the loading for three points in the 0-1 bar rangepressure=[0.1,0.5,1]model_iso.loading_at(pressure)
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]:
importpygaps.graphingaspggpgg.plot_iso([isotherm,model_iso],# Two isothermsbranch='ads',# Plot only the adsorption branchlgd_keys=['material','adsorbate','type'],# Text in the legend, as taken from the isotherms)
Here is a more involved plot, where we create the figure beforehand, and specify many more customisation options.
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.
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:
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).
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 hereloading=[1,2,3],# loading here# Required metadatamaterial='carbon',# Requiredadsorbate='nitrogen',# Requiredtemperature=77,# Required# Unit parameters can be specifiedpressure_mode='absolute',# Working in absolute pressurepressure_unit='bar',# with units of barloading_basis='molar',# Working on a loading molar basisloading_unit='mmol',# with units of mmolmaterial_basis='mass',# Working on a per mass material basismaterial_unit='g',# with units of g# Finally some other isotherm metadataapparatus='X1',# User specificactivation_temperature=150,# User specificuser='John',# User specificDOI='10.000/mydoi',# User specificsomething='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.DataFrameisotherm_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 columnpressure_key='pressure',# The pressure column# Required metadatamaterial='carbon',# Requiredadsorbate='nitrogen',# Requiredtemperature=77,# Required)
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 hereloading=[1,2,3],# loading here# Now the model details can be specifiedmodel='Henry',# Want to fit using the Henry modelbranch='ads',# on the adsorption branchparam_guess={"K":2}# from an initial guess of 2 for the constantparam_bounds={"K":[0,20]}# a lower bound of 0 and an upper bound of 20verbose='True',# and increased verbosity.# Required metadatamaterial='carbon',# Requiredadsorbate='nitrogen',# Requiredtemperature=77,# Required# Unit parameters can be specifiedpressure_mode='absolute',# Working in absolute pressurepressure_unit='bar',# with units of barmaterial_basis='mass',# Working on a mass material basismaterial_unit='kg',# with units of kgloading_basis='mass',# Working on a loading mass basisloading_unit='g',# with units of g# Finally some other isotherm metadataapparatus='X1',# User specificactivation_temperature=150,# User specificuser='John',# User specificDOI='10.000/mydoi',# User specificsomething='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 PointIsothermmodel=['Henry','Langmuir'],# Try multiple models and return best fitverbose='True',# and increased verbosity.)
For more info on isotherm modelling read the section
of the manual.
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.
For getting loading: PointIsotherm
loading() and ModelIsotherm
loading().
For getting tertiary data columns: PointIsotherm
other_data().
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 branchisotherm.data(branch='ads')# Or access the underlying DataFrameisotherm.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 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 isothermloading_unit='mol',# return the loading in molmaterial_basis='mass',# return the adsorbent in mass basismaterial_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.
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.
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:
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.
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.
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.
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.
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.
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:
ifpoint_isotherm1==point_isotherm2:print("same data")ifisoinlist_of_isos:print("isotherm in collection")
Note
Both ModelIsotherm and PointIsotherm classes are supported and contain an
ID.
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.
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',# Requiredformula='C4H10',# Recognisedalias=['n-butane','Butane'],# Recognisedbackend_name='butane',# Recognised, Required for CoolProp interactionsaturation_pressure=2.2,# Recognisedcarbon_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.
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 instanceads=pygaps.Adsorbate.find("butane")ads=pygaps.Adsorbate.find("n-butane")ads=pygaps.Adsorbate.find("c4h10")
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:
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.
If an Adsorbate is manually created, a user can
add it to the list of adsorbates by appending it.
# To store in the main listpygaps.ADSORBATE_LIST.append(my_adsorbate)
A useful shorthand is to pass an optional parameter store at creation
# Automatically stored in ADSORBATE_LISTads=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:
importpygaps.parsingaspgp# To permanently store in the databasepgp.adsorbate_to_db(ads_new)# To store any modifications to an adsorbate in the databasepgp.adsorbate_to_db(ads_modified,overwrite=True)
For more info, check out the sqlite section of the
manual.
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.
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',# Namedensity=1,# Recognisedmolar_mass=256,# Recognisedbatch='X1',# User specificowner='Test User',# User specificform='powder',# User specifictreatment='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.
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 listpyGAPS.MATERIAL_LIST.append(my_material)
A useful shorthand is to pass an optional parameter store at creation
# Automatically stored in MATERIAL_LISTmat=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:
importpygaps.parsingaspgp# To permanently store in the databasepgp.material_to_db(mat_new)# To store any modifications to an material in the databasepgp.material_to_db(mat_modified,overwrite=True)
For more info, check out the sqlite section of the
manual.
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:
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 torrisotherm.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 modeisotherm.convert_pressure(mode_to='relative',)# or to relative percent modeisotherm.convert_pressure(mode_to='relative%',)# absolute pressure mode# unit must be specified hereisotherm.convert_pressure(mode_to='absolute',unit_to='torr',)
Internally, pressure conversions are handled by the
c_pressure() function.
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 basisisotherm.convert_loading(basis_to='mass',unit_to='g',)# to percentageisotherm.convert_loading(basis_to='percent',)
Internally, loading conversions are handled by the
c_loading() function.
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/cm3isotherm.material.properties['density']=2# now conversion is possibleisotherm.convert_material(basis_to='volume',unit_to='cm3',)
Internally, material conversions are handled by the
c_material().
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.
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.
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.
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.
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.
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.
importpygaps.parsingaspgp# to a stringjson_string=pgp.isotherm_to_json(my_isotherm)# to a filepgp.isotherm_to_json(my_isotherm,'path/to/file.json')# or for conveniencemy_isotherm.to_json('path/to/file.json')
To parse JSON into an isotherm, use the from function.
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.
Assuming we have an isotherm which was previously created, use the following
code to convert it to a AIF string or file.
importpygaps.parsingaspgp# to a stringaif_string=pgp.isotherm_to_aif(my_isotherm)# to a filepgp.isotherm_to_aif(my_isotherm,'path/to/file.aif')# or for conveniencemy_isotherm.to_aif('path/to/file.aif')
To parse an AIF file as an isotherm, use the from function.
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.
Assuming we have an isotherm which was previously created, use the following
code to convert it to a CSV string or file.
importpygaps.parsingaspgp# to a stringcsv_string=pgp.isotherm_to_csv(my_isotherm)# to a filepgp.isotherm_to_csv(my_isotherm,'path/to/file.csv')# or for conveniencemy_isotherm.to_csv('path/to/file.csv')
To parse CSV into an isotherm, use the from function.
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.
importpygaps.parsingaspgp# export the isothermpgp.isotherm_to_xl(my_isotherm,'path/to/file.xls')# or for conveniencemy_isotherm.to_xl('path/to/file.xls')
To parse an Excel file as an isotherm, use the from function.
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:
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.
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.
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:
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:
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.
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.
fhvst - Flory-Huggins Vacancy Solution Theory
(FH-VST)
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.
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 functionimportpygaps.modellingaspgmmodel_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.
importpygaps.modellingaspgm# Attempting all basic modelsmodel_isotherm=pgm.model_iso(point_isotherm,branch='des',model='guess',)# With a subset of models insteadmodel_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``sdonotcontainthe``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.
ModelIsotherms can easily be plotted using the same function as
PointIsotherms. For example, to graphically compare a model and an
experimental 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.
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 modelnew_point_isotherm=pygaps.PointIsotherm.from_modelisotherm(model_isotherm,pressure_points=[1,2,3,4],)# Use a previous PointIsotherm as referencenew_point_isotherm=pygaps.PointIsotherm.from_modelisotherm(model_isotherm,pressure_points=point_isotherm,)
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.
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.
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.
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.
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:
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:
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.
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.
A diagram of the database schema can be seen below:
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.
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.
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.
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.
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.
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 kelvinisotherms_n2_77k_paths=Path(json_path/'characterisation').rglob("*.json")isotherms_n2_77k=[pgp.isotherm_from_json(filepath)forfilepathinisotherms_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 dataisotherms_calorimetry_paths=Path(json_path/'calorimetry').rglob("*.json")isotherms_calorimetry=[pgp.isotherm_from_json(filepath)forfilepathinisotherms_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 calculationsisotherms_iast_paths=Path(json_path/'iast').rglob("*.json")isotherms_iast=[pgp.isotherm_from_json(filepath)forfilepathinisotherms_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 calculationsisotherms_isosteric_paths=list(Path(json_path/'enth_isosteric').rglob("*.json"))isotherms_isosteric=[pgp.isotherm_from_json(filepath)forfilepathinisotherms_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
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.
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 functionisotherms_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
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 moduleimportpygaps.graphingaspggpgg.plot_iso(isotherms_iast,# the isothermsbranch='ads',# only the adsorption branchlgd_keys=['material','adsorbate'],# the isotherm properties making up the legend)
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.
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 moduleimportpygaps.characterisationaspgc
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.
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
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.
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
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.
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.
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
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.
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.
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
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.
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
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]:
importmatplotlib.pyplotaspltarea_langmuir=[]area_langmuir_lim=[]area_bet=[]forisotherminisotherms_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]')
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).
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 moduleimportpygaps.characterisationaspgc
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.
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
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.
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
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.
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.
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
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.
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 moduleimportpygaps.characterisationaspgc
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:
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.
Attempting to model using BET.
Model BET success, RMSE is 0.474
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
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.
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 moduleimportpygaps.characterisationaspgc
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
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.
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:
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.
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:
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.
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.
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:
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.
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.
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.
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.
Micropore volume is: 0.448 cm3/g
Effective adsorption potential is : 6 kJ/mol
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)
Micropore volume is: 0.422 cm3/g
Effective adsorption potential is : 6.34 kJ/mol
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.
Sorption enthalpy, \(\Delta H_{ads}\), is an indication of the strength of the adsorbate-material interaction and can be estimated through several methods.
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 moduleimportpygaps.graphingaspggpgg.plot_iso(isotherms_isosteric,lgd_keys=['adsorbate','temperature'],)
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.
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.
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.
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...
Alternatively, the PointIsotherm and the desired model can be passed as parameters, and fitting is performed automatically before the method is applied.
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...
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 moduleimportpygaps.characterisationaspgc
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 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.
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.
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
More information about the functions and their use can be found in the manual.
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 moduleimportpygaps.characterisationaspgc
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 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]:
importmatplotlib.pyplotasplt# Initial point methodisotherm=next(iforiinisotherms_calorimetryifi.material=='HKUST-1(Cu)')res=pgc.initial_enthalpy_point(isotherm,'enthalpy',verbose=True)plt.show()isotherm=next(iforiinisotherms_calorimetryifi.material=='Takeda 5A')res=pgc.initial_enthalpy_point(isotherm,'enthalpy',verbose=True)plt.show()
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.
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.
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
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:
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
Let's now apply the same model to another isotherm.
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.
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 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.
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.
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.
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.
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.
We can manually set bounds on fitted parameters by using a param_bounds dictionary, passed to the ModelIsotherm.
Attempting to model using Langmuir.
Model Langmuir success, RMSE is 0.247
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.
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:
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'
Or if a model is to be created from pre-defined parameters, one can do:
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'
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 moduleimportpygapsimportpygaps.iastaspgiimportmatplotlib.pyplotaspltimportnumpy
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 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.
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
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.
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:
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.
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.
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 functionforisotherm,pathinzip(isotherms,aif_file_paths):pgp.isotherm_to_aif(isotherm,path)# save to file with convenience functionisotherms[0].to_aif('isotherm.aif')# stringisotherm_string=isotherms[0].to_aif()
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 functionforisotherm,pathinzip(isotherms,json_file_paths):pgp.isotherm_to_json(isotherm,path,indent=4)# save to file with convenience functionisotherms[0].to_json('isotherm.json')# stringisotherm_string=isotherms[0].to_json()
# Export each isotherm in turnforisotherm,pathinzip(isotherms,xl_file_paths):pgp.isotherm_to_xl(isotherm,path)# save to file with convenience functionisotherms[0].to_xl('isotherm.xls')
# Export each isotherm in turnforisotherm,pathinzip(isotherms,csv_file_paths):pgp.isotherm_to_csv(isotherm,path)# save to file with convenience functionisotherms[0].to_csv('isotherm.csv')# string representationisotherm_string=isotherms[0].to_csv()
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.
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()exceptExceptionase: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 FOREIGNKEY 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 foundisos=pgp.isotherms_from_db()# Filtering isotherms with an adsorbate: 0 foundisos=pgp.isotherms_from_db(criteria={'adsorbate':'neon'})# Filtering isotherms on a material: 1 foundisos=pgp.isotherms_from_db(criteria={'material':'Carbon X1'})# Check if the isotherm is the sameprint(isotherm==isos[0])
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.
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 internallyprint(novel_materialinpygaps.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.
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.
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.
Material properties type uploaded 'contact'
Material properties type uploaded 'source'
Material properties type uploaded 'treatment'
Material uploaded: 'Carbon X1'
Isotherm uploaded: '29c44c6f12c0f4735b6de977bdcef5c5'
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.
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=[...]).
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='NovelBehaviour').
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)).
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']).
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')
Only some ranges selected for display from all the isotherms (x_range=(0.2,0.6) and y1_range=(3,10)).
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')
Only desorption branch of some isotherms (branch='des'), displaying the user who recorded the isotherms in the graph legend.
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/--characterizea_bet
for the BET area for example)
attempt to model the isotherm using a requested model or guess the best
fitting model (-md/--modelguess) and save the resulting isotherm
model using the -o/--outfile path.
convert the isotherm to any unit/basis
(-cv/--convertpressure_mode=absolute,pressure_unit=bar) and save the
resulting isotherm model using the -o/--outfile path.
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.
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.
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.
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.
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.
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 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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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:
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:
ParameterError -- When something is wrong with the function parameters.
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:
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.
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.
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.
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:
ParameterError -- When something is wrong with the function parameters.
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.
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.
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.
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):
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)
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.
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):
alphacurve (list) : Calculated alpha-s curve
results (list of dicts):
section (array) : the points of the plot chosen for the line
area (float) : calculated surface area, from the section parameters
adsorbed_volume (float) : the amount adsorbed in the pores as calculated per section
slope (float) : slope of the straight trendline fixed through the region
intercept (float) : intercept of the straight trendline through the region
corr_coef (float) : correlation coefficient of the linear region
Raises:
ParameterError -- When something is wrong with the function parameters.
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.
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.
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}\).
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:
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:
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\).
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}\).
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:
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:
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\).
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.
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:
ParameterError -- When something is wrong with the function parameters.
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.
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\) 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\).
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\) 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.
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:
\(\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.
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.
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.
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.
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.
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:
ParameterError -- When something is wrong with the function parameters.
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].
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].
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.
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:
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:
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:
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.
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.
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.
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:
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:
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:
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:
\[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:
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:
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}\).
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.
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.
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:
ParameterError -- When something is wrong with the function parameters.
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.
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:
The function is then minimised using the scipy.optimise.minimise module, with the
constraint that the contribution of each kernel isotherm cannot be negative.
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:
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.
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.
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:
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:
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.
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:
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;
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
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.
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:
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})}\).
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\))
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\)).
\[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\]
\[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.
\[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]
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.
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.
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.
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.
\[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).
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.
\[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.
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.
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:
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:
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:
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:
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
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
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).
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).
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).
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.
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.
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)
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.
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.
⚠️ 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.
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.
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
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 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.
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.
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
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.
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.