Source code for pygaps.modelling.bet

"""BET isotherm model."""

import numpy

from pygaps.modelling.base_model import IsothermBaseModel


[docs]class BET(IsothermBaseModel): r""" Brunauer-Emmett-Teller (BET) adsorption isotherm. .. math:: n(p) = n_m \frac{C p}{(1 - N p)(1 - N p + C p)} Notes ----- Like the Langmuir model, the BET model [#]_ 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 :math:`E_L`. A particular surface percentage :math:`\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 :math:`E_{i>1} = E_L`. .. math:: 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}} Since we are assuming that all layers beside the first have the same properties, we can define :math:`g= \frac{k_{d_2}}{k_{a_2}} = \frac{k_{d_3}}{k_{a_3}} = ...`. The coverages :math:`\theta` can now be expressed in terms of :math:`\theta_0`. .. math:: \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 A constant C may be defined such that .. math:: 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 For all the layers, the equations can be summed: .. math:: \frac{n}{n_m} = \sum_{i=1}^{\infty} i \theta^i = C \sum_{i=1}^{\infty} i x^i \theta_0 Since .. math:: \theta_0 = 1 - \sum_{1}^{\infty} \theta_i \sum_{i=1}^{\infty} i x^i = \frac{x}{(1-x)^2} Then we obtain the BET equation .. math:: n(p) = \frac{n}{n_m} = n_m\frac{C p}{(1-N p)(1-N p+ C p)} The BET constant C is exponentially proportional to the difference between the surface adsorption energy and the intermolecular attraction, and can be seen to influence the knee a BET-type isotherm has at low pressure, before statistical monolayer formation. References ---------- .. [#] “Adsorption of Gases in Multimolecular Layers”, Stephen Brunauer, P. H. Emmett and Edward Teller, J. Amer. Chem. Soc., 60, 309(1938) """ # Model parameters name = 'BET' formula = r"n(p) = n_m \frac{C p}{(1 - N p)(1 - N p + C p)}" calculates = 'loading' param_names = ("n_m", "C", "N") param_default_bounds = ( (0, numpy.inf), (0, numpy.inf), (0, 1), )
[docs] def loading(self, pressure): """ Calculate loading at specified pressure. Parameters ---------- pressure : float The pressure at which to calculate the loading. Returns ------- float Loading at specified pressure. """ nm = self.params['n_m'] N = self.params['N'] C = self.params['C'] return nm * C * pressure / ((1.0 - N * pressure) * (1.0 - N * pressure + C * pressure))
[docs] def pressure(self, loading): """ Calculate pressure at specified loading. For the BET model, an analytical inversion is possible. See function code for implementation. Parameters ---------- loading : float The loading at which to calculate the pressure. Returns ------- float Pressure at specified loading. """ nm = self.params['n_m'] N = self.params['N'] C = self.params['C'] x = loading * N * (N - C) y = loading * C - 2 * loading * N - nm * C res = (-y - numpy.sqrt(y**2 - 4 * x * loading)) / (2 * x) if numpy.isnan(res).any(): res = numpy.nan_to_num(res, copy=False) return res
[docs] def spreading_pressure(self, pressure): r""" Calculate spreading pressure at specified gas pressure. Function that calculates spreading pressure by solving the following integral at each point i. .. math:: \pi = \int_{0}^{p_i} \frac{n_i(p_i)}{p_i} dp_i The integral for the BET model is solved analytically. .. math:: \pi = n_m \ln{\frac{1 - N p + C p}{1- N p}} Parameters ---------- pressure : float The pressure at which to calculate the spreading pressure. Returns ------- float Spreading pressure at specified pressure. """ nm = self.params['n_m'] N = self.params['N'] C = self.params['C'] return nm * numpy.log((1.0 - N * pressure + C * pressure) / (1.0 - N * pressure))
[docs] def initial_guess(self, pressure, loading): """ Return initial guess for fitting. Parameters ---------- pressure : ndarray Pressure data. loading : ndarray Loading data. Returns ------- dict Dictionary of initial guesses for the parameters. """ saturation_loading, langmuir_k = super().initial_guess(pressure, loading) guess = {"n_m": saturation_loading, "C": langmuir_k, "N": 0.01} guess = self.initial_guess_bounds(guess) return guess