The Isotherm classes#

Overview#

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

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

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

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

To work with pyGAPS isotherms check out the following topics:

Note

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

Creating isotherms#

Creating a PointIsotherm#

There are several ways to create a PointIsotherm object:

  • Passing a pressure and a loading array.

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

  • Parsed from one of the formats known to pyGAPS:

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

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

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

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

  • The material name (material or m).

  • The adsorbate used (adsorbate or a).

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

As such, the simplest creation statement is:

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

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

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

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

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

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

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

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

  • temperature_unit for the given temperature, K by default.

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

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

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

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

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

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

Caution

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

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

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

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

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

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

Creating a ModelIsotherm#

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

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

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

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

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

  • param_bounds specifies the bounds for optimisation parameters.

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

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

Note

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

The code to generate a ModelIsotherm is then:

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

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

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

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

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

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

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

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

Accessing isotherm data#

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

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

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

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

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

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

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

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

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

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

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

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

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

isotherm.data(branch = 'ads')

# Or access the underlying DataFrame

isotherm.data_raw

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

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

The point methods are:

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

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

Caution

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

Converting isotherm units, modes and basis#

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

  • convert() which can handle any conversion quantities.

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

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

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

  • convert_temperature() will permanently the temperature unit.

Important

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

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

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

Or a complicated conversion using the convenience function.

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

Note

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

Converting to relative pressures#

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

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

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

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

  • The relative pressure is calculated by dividing by p0.

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

Converting loading basis#

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

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

Converting material basis#

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

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

Exporting an isotherm#

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

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

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

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

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

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

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

Ensuring isotherm uniqueness#

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

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

point_isotherm.iso_id

This means that we can perform identity checks such as:

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

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

Note

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