Test isotherm fitting

Our strategy here is to generate data points that follow a given isotherm model, then fit an isotherm model to the data using pyIAST, and check that pyIAST identifies the parameters correctly.


In [13]:
import pyiast
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
%matplotlib inline

We test all analytical models implemented in pyIAST.


In [14]:
models = pyiast._MODELS
models


Out[14]:
['Langmuir', 'Quadratic', 'BET', 'Henry', 'TemkinApprox', 'DSLangmuir']

This dictionary gives the model parameters for which we generate synthetic data to test pyIAST fitting. Note that, because the DSLF model has so many parameters, it is highly likely that such a model will overfit the data. Thus, we expect pyIAST to reach a local minimum for DSLF yet still obtain a reasonable fit with the default starting guess.


In [15]:
model_params = {
    "Langmuir": {"M": 10.0, "K": 10.0},
    "Quadratic": {"M": 10.0, "Ka": 10.0, "Kb": 10.0 ** 2 * 3},
    "BET": {"M": 10.0, "Ka": 10.0, "Kb": .2},
    "DSLangmuir": {"M1": 10.0, "K1": 1.0,
                   "M2": 30.0, "K2": 30.0}, # warning: 1/2 is arbitrary
    "Henry": {"KH": 10.0},
    "TemkinApprox": {"M": 10.0, "K": 10.0, "theta": -0.1}
}

The loading function generates synthetic data for a given model. We pass it an array of pressures and it returns loading using the given model. Note that the parameters for each model are taken from the above dictionary.


In [16]:
def loading(P, model):
    """
    Return loading at pressure P using a given model.
    
    :param P: np.array array of pressures
    :param model: string specify model
    """
    if model not in models:
        raise Exception("This model is not implemented in the test suite.")
        
    if model == "Langmuir":
        M = model_params[model]["M"] 
        K = model_params[model]["K"] 
        return M * K * P / (1.0 + K * P)
    
    if model == "Quadratic":
        M = model_params[model]["M"] 
        Ka = model_params[model]["Ka"] 
        Kb = model_params[model]["Kb"] 
        return M * P * (Ka + 2.0 * Kb * P) / (1.0 + Ka * P + Kb * P ** 2)
    
    if model == "BET":
        M = model_params[model]["M"] 
        Ka = model_params[model]["Ka"] 
        Kb = model_params[model]["Kb"]
        return M * Ka * P / (1.0 - Kb * P) / (1.0 - Kb * P + Ka * P)
    
    if model == "DSLangmuir":
        M1 = model_params[model]["M1"] 
        K1 = model_params[model]["K1"]
        
        M2 = model_params[model]["M2"] 
        K2 = model_params[model]["K2"]
        
        return M1 * K1 * P / (1.0 + K1 * P) +\
               M2 * K2 * P / (1.0 + K2 * P)
    
    if model == "TemkinApprox":
        M = model_params[model]["M"]
        K = model_params[model]["K"]
        theta = model_params[model]["theta"]
        
        fractional_langmuir_loading = K * P / (1.0 + K * P)
        return M * (fractional_langmuir_loading + theta *
                    fractional_langmuir_loading ** 2 * 
                    (fractional_langmuir_loading - 1.0))
 
    if model == "Henry":
        return model_params[model]["KH"] * P

Test model fits

Loop through all models, generate synthetic data using parameters in model_params and the loading function here, then fit model using pyIAST. Plot data and fits, check that pyIAST identified parameters match the model.


In [17]:
for model in models:
    print("Testing model:", model)
    
    # Generate synthetic data
    df = pd.DataFrame()
    df['P'] = np.linspace(0, 1, 40)
    df['L'] = loading(df['P'], model)
    
    # use pyIAST to fit model to data
    isotherm = pyiast.ModelIsotherm(df, pressure_key='P', loading_key='L', 
                                    model=model)
    isotherm.print_params()
    
    # plot fit
    P_plot = np.linspace(0, 1, 100)

    fig = plt.figure()
    plt.scatter(df['P'], df['L'], label='Synthetic data', clip_on=False)
    plt.plot(P_plot, isotherm.loading(P_plot), label='pyIAST fit')
    plt.xlim([0, 1])
    plt.ylim(ymin=0)
    plt.xlabel('Pressure')
    plt.ylabel('Uptake')
    plt.title(model)
    plt.legend(loc='lower right')
    plt.show()
    
    # assert parameters are equal
    for param in isotherm.params.keys():
        np.testing.assert_almost_equal(isotherm.params[param], 
                                       model_params[model][param],
                                       decimal=3)


Testing model: Langmuir
Langmuir identified model parameters:
	M = 10.000008
	K = 9.999948
RMSE =  4.185844720128586e-06
Testing model: Quadratic
Quadratic identified model parameters:
	M = 10.000000
	Ka = 10.000002
	Kb = 299.999978
RMSE =  1.2329463414954408e-07
Testing model: BET
BET identified model parameters:
	M = 9.999978
	Ka = 10.000081
	Kb = 0.200001
RMSE =  4.221734254017726e-06
Testing model: Henry
Henry identified model parameters:
	KH = 9.999995
RMSE =  3.148658101891919e-06
Testing model: TemkinApprox
TemkinApprox identified model parameters:
	M = 10.000008
	K = 10.000030
	theta = -0.099990
RMSE =  1.6903461075627526e-06
Testing model: DSLangmuir
DSLangmuir identified model parameters:
	M1 = 9.999958
	K1 = 1.000018
	M2 = 29.999976
	K2 = 30.000034
RMSE =  3.0559622089099224e-06

Quick visual test on the Interpolator isotherm


In [18]:
isotherm = pyiast.InterpolatorIsotherm(df, pressure_key='P', loading_key='L')
pyiast.plot_isotherm(isotherm)