The Isotherm classes#
Overview#
In pyGAPS, an adsorption isotherm can be represented in two ways: as a
PointIsotherm
or as a
ModelIsotherm
. These two classes have many
common methods and attributes, but they differ in the way they define the
relationship between pressure (concentration) and loading (uptake):
The
PointIsotherm
class is a collection of discrete points, stored in apandas.DataFrame
object.The
ModelIsotherm
class is a dictionary of parameters which are used to describe a model of adsorption behaviour (Henry, Langmuir, etc..).
Both classes are derived from the
BaseIsotherm
class. This class holds
parameters of the isotherm besides data, such as the adsorbate used, the
material's name and the temperature at which the isotherm was measured. These
parameters are then inherited by the two isotherm objects. The user is unlikely
to use this class directly.
To work with pyGAPS isotherms check out the following topics:
Note
A detailed explanation of each isotherm class and methods is written in their docstrings and can be also read in the reference.
Creating isotherms#
Creating a PointIsotherm#
There are several ways to create a
PointIsotherm
object:
Passing a
pressure
and aloading
array.From a
pandas.DataFrame
. This is the most extensible way to create a PointIsotherm, as other data types can be specified.Parsed from one of the formats known to pyGAPS:
From an AIF format. See parsing from aif.
From a pyGAPS json string or file. See parsing from json.
From a pyGAPS csv string or file. See parsing from csv.
From an pyGAPS excel file. See parsing from excel.
From the NIST ISODB. See parsing from ISODB.
Imported from one of many device manufacturer formats. See parsing manufacturer files.
From an internal database: pyGAPS contains functionality to store and retrieve constructed isotherms in an sqlite database. See database.
This section will explain how to create an isotherm from raw data. The fastest
way is to pass pressure and loading arrays to the constructor as the
pressure
and loading
parameters.
The other information that needs to be passed to the constructor is isotherm metadata. This is information about the material the isotherm was measured on, the adsorbate which was used, as well as data about the temperature, units used and so on. The isotherm metadata must include:
The material name (
material
orm
).The adsorbate used (
adsorbate
ora
).The temperature at which the data was recorded (
temperature
ort
).
As such, the simplest creation statement is:
point_isotherm = pygaps.PointIsotherm(
pressure=[1,2,3,4],
loading=[1,2,3,4],
m='carbon',
a='nitrogen',
t=77,
)
The isotherm units can also be specified. If not given, the framework will assume default values: temperature in Kelvin, absolute pressure in bar and amount adsorbed in terms of mmol per g (molar basis loading per mass basis material). Options are:
pressure_mode
specifies if the pressure is absolute or relative (p/p0). If not passed, the pressure is assumed to be absolute.pressure_unit
specifies the unit the pressure is measured in, if applicable. It can be bar, Pa, kPa, etc. and it defaults to bar.loading_basis
specifies if the amount adsorbed is defined in terms of moles, gas volume, liquid volume, mass, fractional or percentage. If not passed, it is assumed to be molar.loading_unit
specifies the unit the amount adsorbed is in. Depending on theloading_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 thematerial_basis
it can be a mass, volume or molar unit. By default, the material is is read in g.temperature_unit
for the giventemperature
, K by default.
Other user metadata can be passed as well, and will be stored in the isotherm
object properties
dictionary. Will these components, an isotherm can now be
created. A more complex instantiation is given below, with explanations.
point_isotherm = pygaps.PointIsotherm(
pressure=[1,2,3], # pressure here
loading=[1,2,3], # loading here
# Required metadata
material='carbon', # Required
adsorbate='nitrogen', # Required
temperature=77, # Required
# Unit parameters can be specified
pressure_mode='absolute', # Working in absolute pressure
pressure_unit='bar', # with units of bar
loading_basis='molar', # Working on a loading molar basis
loading_unit='mmol', # with units of mmol
material_basis='mass', # Working on a per mass material basis
material_unit='g', # with units of g
# Finally some other isotherm metadata
apparatus='X1', # User specific
activation_temperature=150, # User specific
user='John', # User specific
DOI='10.000/mydoi', # User specific
something='something', # User specific
)
The code does its best to attempt to guess whether the data passed is part of an
adsorption branch, desorption branch or has both. It does this by looking at
whether pressure is increasing or decreasing between two consecutive points and
marks branches internally. If the data isn't well conditioned, this
functionality will likely not produce good results. In this case, the user can
specify whether the data is an adsorption or desorption branch by using the
branch
argument. The user can also divide the data directly by passing a
list of int
as the branch
parameter. See more in the
PointIsotherm
reference.
Caution
The data in the columns is assumed to be free of errors and anomalies. Negative pressures or loadings, noisy signals or erroneous points may give undefined behaviour.
Alternatively, a pandas.DataFrame
can be passed in. This allows for more
data than just pressure and loading to be stored in a single isotherm. The
DataFrame should have at least two columns: one for pressure and one for
loading. Other data columns can represent secondary measurements: calorimetry
data, magnetic field strengths, and are stored in the PointIsotherm
.
If a DataFrame is used, pressure_key
and loading_key
are two required
parameters specifying which column in the DataFrame contains the pressure and
loading, respectively. As an example:
point_isotherm = pygaps.PointIsotherm(
# First the pandas.DataFrame
isotherm_data=pandas.DataFrame({
'pressure' : [1, 2, 3, 4, 5, 3, 2], # required
'loading' : [1, 2, 3, 4, 5, 3, 2], # required
'enthalpy' : [15, 15, 15, 15, 15, 15, 15],
'xrd_peak_1' : [0, 0, 1, 2, 2, 1, 0],
}),
# and the keys to what the columns represent.
loading_key='loading', # The loading column
pressure_key='pressure', # The pressure column
# Required metadata
material='carbon', # Required
adsorbate='nitrogen', # Required
temperature=77, # Required
)
Creating a ModelIsotherm#
To create a ModelIsotherm
, one can either
use raw similar to above, or create one from an existing PointIsotherm.
ModelIsotherm creation from raw data is almost identical to the PointIsotherm creation. The same data and parameters can be used, with a few differences:
model
specifies which model/models to use to attempt to fit the data.branch
will specify which single isotherm branch (adsorption or desorption) is represented by the model. It defaults to the adsorption branch.param_guess
specifies the initial model parameter guesses where fitting optimisation should start. The parameter is optional, and will be automatically filled unless the user specifies it.param_bounds
specifies the bounds for optimisation parameters.optimization_params
is a dictionary which will be passed toscipy.optimise.least_squares
.Finally, the
verbose
parameter can be used to increase the amount of information printed during the model fitting procedure.
Note
The ModelIsotherm cannot be used to model tertiary data. Therefore, only loading and pressure will be saved. Any other columns in the DataFrame will be ignored.
The code to generate a ModelIsotherm is then:
model_isotherm = pygaps.ModelIsotherm(
pressure=[1,2,3], # pressure here
loading=[1,2,3], # loading here
# Now the model details can be specified
model='Henry', # Want to fit using the Henry model
branch='ads', # on the adsorption branch
param_guess={"K" : 2} # from an initial guess of 2 for the constant
param_bounds={"K" : [0, 20]} # a lower bound of 0 and an upper bound of 20
verbose='True', # and increased verbosity.
# Required metadata
material='carbon', # Required
adsorbate='nitrogen', # Required
temperature=77, # Required
# Unit parameters can be specified
pressure_mode='absolute', # Working in absolute pressure
pressure_unit='bar', # with units of bar
material_basis='mass', # Working on a mass material basis
material_unit='kg', # with units of kg
loading_basis='mass', # Working on a loading mass basis
loading_unit='g', # with units of g
# Finally some other isotherm metadata
apparatus='X1', # User specific
activation_temperature=150, # User specific
user='John', # User specific
DOI='10.000/mydoi', # User specific
something='something', # User specific
)
ModelIsotherms can also be constructed from PointIsotherms and vice-versa. The best model can also be guessed automatically. As an example:
model_isotherm = pygaps.model_iso(
point_isotherm, # a PointIsotherm
model=['Henry', 'Langmuir'], # Try multiple models and return best fit
verbose='True', # and increased verbosity.
)
For more info on isotherm modelling read the section of the manual.
Accessing isotherm data#
Once an isotherm is created, it is useful to check if it contains the correct parameters or make a plot of the isotherm. The isotherm classes can be inspected using the following functions:
The Python
print(iso)
will display all isotherm properties.The
iso.plot()
function will display an isotherm plot (plot()
).The
iso.print_info()
function combines the two above (print_info()
).
To access the isotherm data, one of several functions can be used. There are
individual methods for each data type: pressure
, loading
and
other_data
. The first two are applicable to both PointIsotherms and
ModelIsotherms. PointIsotherm methods return the actual discrete data, while
ModelIsotherms use their internal model to generate data with the
characteristics required.
For getting loading: PointIsotherm
loading()
and ModelIsothermloading()
.For getting pressure: PointIsotherm
pressure()
and ModelIsothermpressure()
.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 branch
isotherm.data(branch = 'ads')
# Or access the underlying DataFrame
isotherm.data_raw
Besides functions which give access to the internal data points, the isotherm
object can also return the value of pressure and loading at any point specified
by the user. To differentiate them from the functions returning internal data,
the functions have _at
in their name.
In the ModelIsotherm class, the internal model is used to calculate the data
required. In the PointIsotherm class, the functions rely on an internal
interpolator, which uses the scipy.interpolate
module. To optimize performance
working with isotherms, the interpolator is constructed in the same units as the
isotherm. If the user requests the return values in a different unit or basis,
they will be converted after interpolation. If a large number of requests
are to be made in a different unit or basis, it is better to first convert the
entire isotherm data in the required mode using the conversion functions.
The point methods are:
For loading: PointIsotherm
loading_at()
and ModelIsothermloading_at()
For pressure: PointIsotherm
pressure_at()
and ModelIsothermpressure_at()
The methods take parameters that describe the unit/mode of both the input parameters and the output parameters.
isotherm.loading_at(
1,
pressure_unit='atm', # the pressure is passed in atmospheres (= 1 atm)
branch='des', # use the desorption branch of the isotherm
loading_unit='mol', # return the loading in mol
material_basis='mass', # return the adsorbent in mass basis
material_unit='g', # with a unit of g
)
Caution
Interpolation can be dangerous. pyGAPS does not implicitly allow
interpolation outside the bounds of the data, although the user can force it
to by passing an interp_fill
parameter to the interpolating functions,
usually if the isotherm is known to have reached the maximum adsorption
plateau. Otherwise, the user is responsible for making sure the data is fit
for purpose.
Converting isotherm units, modes and basis#
The PointIsotherm class also includes methods which can be used to permanently convert the internal data. This is useful in certain cases, like when you want to export the converted isotherm. To understand how units work in pyGAPS, see this section. If what is desired is instead a slice of data in a particular unit, it is easier to get it directly via the data access functions above. The conversion functions are:
convert()
which can handle any conversion quantities.convert_pressure()
will permanently convert the unit or mode of pressure, for example from bar to atm.convert_loading()
will permanently convert the unit or basis loading of the isotherm, for example from molar in mmol to mass in g.convert_material()
will permanently convert the adsorbent material units or basis, for example from a mass basis in g to a mass basis in kg.convert_temperature()
will permanently the temperature unit.
Important
In order for pyGAPS to correctly convert between some modes and basis, the user might have to take some extra steps to provide the required information for these conversions (adsorbate molar mass for instance, which is calculated automatically for known adsorbates).
These conversion functions also reset the internal interpolator to the particular unit and basis set requested. An example of how to convert the pressure from an relative mode into an absolute mode, with units of atm:
isotherm.convert_pressure(
mode_to='absolute',
unit_to='atm',
)
Or a complicated conversion using the convenience function.
isotherm.convert(
pressure_mode='absolute',
pressure_unit='atm',
loading_basis='fraction',
material_basis='volume',
material_unit='cm3',
)
Note
The ModelIsotherm model parameters cannot be converted permanently to new states (although the data can still be obtained in that state by using the data functions). For fast calculations, it is better to first convert a PointIsotherm, then re-fit the ModelIsotherm.
Converting to relative pressures#
To convert an absolute pressure in a relative pressure, the critical pressure of the gas at the experiment temperature must be known. Of course, this conversion only works when the isotherm is measured in a subcritical regime. To calculate the vapour pressure, pyGAPS relies on the CoolProp thermodynamic library. Therefore, the name of the gas in a format CoolProp understands must be passed to the CoolProp API. pyGAPS does this by having an internal list of adsorbates, which is loaded from its database at import-time. The steps are:
The
isotherm.adsorbate
is linked to apygaps.Adsorbate
class at isotherm creation.User requests conversion from absolute to relative pressure for an isotherm object.
CoolProp backend calculates the vapour pressure (p0) for the adsorbate.
The relative pressure is calculated by dividing by p0.
If using common gasses, the user should not be worried about this process, as an extensive list of adsorbates is available. However, if a new adsorbate is to be used, the user might have to add it to the list themselves. For more info on this see the Adsorbate class manual.
Converting loading basis#
For loading basis conversions, the relationship between the two bases must be known. Between a mass and a volume basis, density of the adsorbent is needed and between mass and molar basis, the specific molar mass is required.
For most adsorbates, these properties are also calculated using the thermodynamic backend. The molar mass is independent of any variables, while the gas/liquid density is a function of temperature.
Converting material basis#
For the material basis, the same properties (density and molar mass) are required, depending on the conversion requested. These properties are specific to each material and cannot be calculated. Therefore, they have to be specified by the user.
Similar to the list of adsorbates described above, pyGAPS stores a list of
Material
objects. This is linked to the isotherm.material
. To specify
the properties, the user must manually set density and the molar mass for an
isotherm material. For more info on this see the
Material class manual.
Exporting an isotherm#
To export an isotherm, pyGAPS provides several choices to the user:
Converting the isotherm to an AIF format., using the
isotherm_to_aif()
functionConverting the isotherm in a JSON format, using the
isotherm_to_json()
functionConverting the isotherm to a CSV file, using the
isotherm_to_csv()
functionConverting the isotherm to an Excel file, using the
isotherm_to_xl()
functionUploading the isotherm to a sqlite database, either using the internal database or a user-specified external one. For more info on interacting with the sqlite database see the respective section of the manual.
More info can be found on the respective parsing pages of the manual.
Ensuring isotherm uniqueness#
An interesting question is how to ensure an isotherm is unique. To this end, each Isotherm generates an id, which is an md5 hash of the isotherms parameters and data/model. The id is also used internally for database storage.
The id is generated automatically every time the isotherm.iso_id
is called.
The hashlib.md5
function is used to obtain a hash of the json string. It can
be read as:
point_isotherm.iso_id
This means that we can perform identity checks such as:
if point_isotherm1 == point_isotherm2:
print("same data")
if iso in list_of_isos:
print("isotherm in collection")
Note
Both ModelIsotherm and PointIsotherm classes are supported and contain an ID.