chemistrykit.surface#

chemistrykit.surface: surface chemistry and heterogeneous catalysis.

Langmuir, Freundlich, and BET adsorption isotherms, each with its standard linearization for fitting parameters from data; Langmuir- Hinshelwood surface-reaction kinetics (single- and dual-site mechanisms); and a turnover-number/rate-enhancement catalysis model built on chemistrykit.kinetics’s Arrhenius equation. Also the Gibbs adsorption equation, Polanyi/Dubinin-Radushkevich and Temkin isotherms, Eley-Rideal kinetics, and temperature-programmed desorption with Redhead’s peak analysis.

class chemistrykit.surface.AdsorptionIsotherm[source]#

Bases: ABC

Common interface for an equilibrium adsorption isotherm.

Concrete subclasses (chemistrykit.surface.systems.langmuir.LangmuirIsotherm, chemistrykit.surface.systems.freundlich.FreundlichIsotherm, chemistrykit.surface.systems.bet.BETIsotherm) implement loading(), the amount adsorbed at a given equilibrium pressure; fractional_coverage() (relative to a model-specific saturation loading) is then available for every subclass for free.

fractional_coverage(P)[source]#

Return the loading as a fraction of the saturation (monolayer) loading.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray – In \([0, 1]\) for a genuine monolayer model.

abstractmethod loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

saturation_loading: float = 1.0#

Saturation (monolayer) loading used to normalize fractional_coverage(). Concrete subclasses set this in __init__ (Langmuir/BET: a genuine monolayer capacity; Freundlich has no saturation loading, so its subclass overrides fractional_coverage() to raise instead).

Type:

float

class chemistrykit.surface.BETFit(Vm, C, P0, r_squared)[source]#

Bases: object

Result of fitting loading-vs-pressure data to the BET isotherm.

Parameters:
C: float#

Fitted BET energy constant.

Type:

float

P0: float#

Saturation vapor pressure used (an input to the fit, not itself fitted).

Type:

float

Vm: float#

Fitted monolayer capacity.

Type:

float

predict(P)[source]#

Evaluate the fitted BET isotherm at pressure(s) P.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray

r_squared: float#

Coefficient of determination of the linearized fit.

Type:

float

to_isotherm()[source]#

Return a BETIsotherm built from the fitted parameters.

Return type:

BETIsotherm

class chemistrykit.surface.BETIsotherm(Vm, C, P0)[source]#

Bases: AdsorptionIsotherm

The BET multilayer adsorption isotherm.

Parameters:
  • Vm (float) – Monolayer capacity.

  • C (float) – BET energy constant.

  • P0 (float) – Saturation vapor pressure of the adsorbate.

Examples

>>> iso = BETIsotherm(Vm=2.0, C=100.0, P0=10.0)
>>> round(float(iso.loading(P=1.0)), 4)
2.0387
loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

class chemistrykit.surface.CatalyticRateComparison(k_uncatalyzed, k_catalyzed, rate_enhancement, delta_Ea)[source]#

Bases: object

Result of comparing a catalyzed and uncatalyzed rate constant at a given temperature.

Parameters:
delta_Ea: float#

Activation-energy reduction \(E_{a,uncat}-E_{a,cat}\), in J/mol.

Type:

float

k_catalyzed: float#

Catalyzed rate constant at temperature T.

Type:

float

k_uncatalyzed: float#

Uncatalyzed rate constant at temperature T.

Type:

float

rate_enhancement: float#

\(k_{catalyzed}/k_{uncatalyzed}\).

Type:

float

class chemistrykit.surface.DubininRadushkevichFit(W0, E, r_squared)[source]#

Bases: object

Result of fitting loading-vs-pressure data to the Dubinin-Radushkevich isotherm.

Parameters:
E: float#

Fitted characteristic energy, in J/mol.

Type:

float

W0: float#

Fitted limiting micropore volume.

Type:

float

r_squared: float#

Coefficient of determination of the linearized (ln W vs A^2) fit.

Type:

float

class chemistrykit.surface.DubininRadushkevichIsotherm(W0, E, P0, T)[source]#

Bases: AdsorptionIsotherm

The Dubinin-Radushkevich isotherm at a fixed temperature.

Parameters:
  • W0 (float) – Limiting micropore volume (saturation loading).

  • E (float) – Characteristic energy, in J/mol.

  • P0 (float) – Saturation vapor pressure at T.

  • T (float) – Absolute temperature, in K.

Examples

>>> iso = DubininRadushkevichIsotherm(W0=0.5, E=12e3, P0=1.0, T=300.0)
>>> round(float(iso.fractional_coverage(1.0)), 10)
1.0
loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

class chemistrykit.surface.FreundlichFit(Kf, n, r_squared)[source]#

Bases: object

Result of fitting loading-vs-pressure data to the Freundlich isotherm.

Parameters:
Kf: float#

Fitted Freundlich capacity constant.

Type:

float

n: float#

Fitted Freundlich heterogeneity exponent.

Type:

float

predict(P)[source]#

Evaluate the fitted Freundlich isotherm at pressure(s) P.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray

r_squared: float#

Coefficient of determination of the linearized (log q vs log P) fit.

Type:

float

to_isotherm()[source]#

Return a FreundlichIsotherm built from the fitted parameters.

Return type:

FreundlichIsotherm

class chemistrykit.surface.FreundlichIsotherm(Kf, n)[source]#

Bases: AdsorptionIsotherm

The Freundlich adsorption isotherm \(q(P) = K_f P^{1/n}\).

Parameters:
  • Kf (float) – Freundlich capacity constant.

  • n (float) – Freundlich heterogeneity exponent.

Notes

Unlike LangmuirIsotherm, the Freundlich isotherm has no saturation (monolayer) loading, so fractional_coverage() is not physically meaningful here and raises NotImplementedError.

Examples

>>> iso = FreundlichIsotherm(Kf=2.5, n=3.0)
>>> round(float(iso.loading(P=1.0)), 6)
2.5
fractional_coverage(P)[source]#

Return the loading as a fraction of the saturation (monolayer) loading.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray – In \([0, 1]\) for a genuine monolayer model.

loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

class chemistrykit.surface.LangmuirFit(K, qmax, r_squared)[source]#

Bases: object

Result of fitting loading-vs-pressure data to the Langmuir isotherm.

Parameters:
K: float#

Fitted Langmuir equilibrium constant.

Type:

float

predict(P)[source]#

Evaluate the fitted Langmuir isotherm at pressure(s) P.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray

qmax: float#

Fitted monolayer capacity.

Type:

float

r_squared: float#

Coefficient of determination of the linearized (1/q vs 1/P) fit.

Type:

float

to_isotherm()[source]#

Return a LangmuirIsotherm built from the fitted parameters.

Return type:

LangmuirIsotherm

class chemistrykit.surface.LangmuirIsotherm(K, qmax=1.0)[source]#

Bases: AdsorptionIsotherm

The Langmuir monolayer adsorption isotherm \(q(P) = q_{max}\,KP/(1+KP)\).

Parameters:
  • K (float) – Langmuir equilibrium constant, inverse to P’s units.

  • qmax (float) – Monolayer (saturation) capacity, in whatever amount unit q is measured in (e.g. mol/g).

Examples

>>> iso = LangmuirIsotherm(K=2.0, qmax=5.0)
>>> round(float(iso.loading(P=0.5)), 6)
2.5
>>> round(float(iso.fractional_coverage(P=0.5)), 6)
0.5
half_saturation_pressure()[source]#

Return the pressure \(P=1/K\) at which coverage is exactly one-half.

Return type:

float

Returns:

float

Examples

>>> LangmuirIsotherm(K=4.0).half_saturation_pressure()
0.25
loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

class chemistrykit.surface.TPDResult(T, coverage, desorption_rate, peak_temperature)[source]#

Bases: object

Result of a simulated temperature-programmed desorption run.

Parameters:
T: ndarray#

Temperature grid, in K.

Type:

ndarray

coverage: ndarray#

Fractional coverage \(\theta(T)\).

Type:

ndarray

desorption_rate: ndarray#

Desorption rate per kelvin, \(-d\theta/dT\).

Type:

ndarray

peak_temperature: float#

Temperature of maximum desorption rate, in K.

Type:

float

class chemistrykit.surface.TemkinFit(A_T, b_T, r_squared)[source]#

Bases: object

Result of fitting loading-vs-pressure data to the Temkin isotherm.

Parameters:
A_T: float#

Fitted Temkin binding constant.

Type:

float

b_T: float#

Fitted Temkin heat-of-adsorption constant, in J/mol.

Type:

float

r_squared: float#

Coefficient of determination of the q vs ln P fit.

Type:

float

class chemistrykit.surface.TemkinIsotherm(A_T, b_T, T)[source]#

Bases: AdsorptionIsotherm

The Temkin isotherm \(q = (RT/b_T)\ln(A_TP)\) at fixed temperature.

Parameters:
  • A_T (float) – Temkin binding constant.

  • b_T (float) – Temkin heat-of-adsorption constant, in J/mol.

  • T (float) – Absolute temperature, in K.

Examples

>>> round(float(TemkinIsotherm(A_T=2.0, b_T=500.0, T=300.0).loading(0.5)), 10)
0.0
fractional_coverage(P)[source]#

Return the loading as a fraction of the saturation (monolayer) loading.

Parameters:

P (float or array-like of float)

Returns:

float or ndarray – In \([0, 1]\) for a genuine monolayer model.

loading(P)[source]#

Return the amount adsorbed at equilibrium pressure(s) P.

Parameters:

P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Amount adsorbed, in whatever units the model’s capacity parameter was given (e.g. mol/g, cm^3(STP)/g).

chemistrykit.surface.bet_loading(Vm, C, P, P0)[source]#

The BET isotherm \(V(P) = V_m Cx/[(1-x)(1-x+Cx)]\), \(x=P/P_0\).

Parameters:
  • Vm (float) – Monolayer capacity (e.g. cm^3(STP)/g or mol/g).

  • C (float) – BET energy constant (\(C \gg 1\) gives a sharp “knee” at monolayer completion; \(C \to 1\) washes it out).

  • P (float or array-like of float) – Equilibrium pressure, in the same units as P0.

  • P0 (float) – Saturation vapor pressure of the adsorbate at the experiment’s temperature.

Returns:

float or ndarray – Volume (or amount) adsorbed. Diverges as \(P \to P_0\) (formal bulk condensation); only valid for \(P < P_0\).

Examples

At zero pressure nothing is adsorbed:

>>> round(float(bet_loading(Vm=1.0, C=100.0, P=0.0, P0=10.0)), 10)
0.0

BET reduces to the Langmuir isotherm when the saturation pressure is far above the working pressure range (multilayer condensation suppressed) – see the module docstring for the derivation:

>>> from chemistrykit.surface.systems.langmuir import langmuir_coverage
>>> K = 2.0
>>> P0 = 1.0e7
>>> Vm = 1.0
>>> C = K * P0
>>> P = np.array([0.01, 0.1, 0.5, 1.0, 2.0])
>>> V_bet = bet_loading(Vm, C, P, P0)
>>> theta_langmuir = langmuir_coverage(K, P)
>>> bool(np.allclose(V_bet, Vm * theta_langmuir, rtol=1e-4))
True
chemistrykit.surface.compare_catalyzed_rate(Ea_uncatalyzed, Ea_catalyzed, T, A_uncatalyzed, A_catalyzed=None, R_gas=8.31446261815324)[source]#

Compare catalyzed vs. uncatalyzed Arrhenius rate constants at temperature T.

Both rate constants are evaluated with chemistrykit.kinetics.systems.arrhenius.arrhenius_rate_constant(); when the two mechanisms share the same pre-exponential factor A (the common simplifying assumption – see Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.5), the rate enhancement reduces to a pure exponential in the activation-energy reduction:

\[\frac{k_{cat}}{k_{uncat}} = \exp\!\left(\frac{E_{a,uncat}-E_{a,cat}}{RT}\right)\]
Parameters:
  • Ea_uncatalyzed (float) – Activation energies of the uncatalyzed and catalyzed pathways, in J/mol (Ea_catalyzed < Ea_uncatalyzed for a genuine catalyst).

  • Ea_catalyzed (float) – Activation energies of the uncatalyzed and catalyzed pathways, in J/mol (Ea_catalyzed < Ea_uncatalyzed for a genuine catalyst).

  • T (float) – Absolute temperature, in K.

  • A_uncatalyzed (float) – Pre-exponential factor of the uncatalyzed pathway.

  • A_catalyzed (float | None) – Pre-exponential factor of the catalyzed pathway. Defaults to A_uncatalyzed (the common simplifying assumption that the catalyst changes only \(E_a\), not the attempt frequency).

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

Return type:

CatalyticRateComparison

Returns:

CatalyticRateComparison

Examples

With equal pre-exponential factors, the rate enhancement is exactly \(\exp(\Delta E_a/RT)\):

>>> import numpy as np
>>> comparison = compare_catalyzed_rate(Ea_uncatalyzed=80e3, Ea_catalyzed=50e3, T=298.15, A_uncatalyzed=1e13)
>>> expected = np.exp((80e3 - 50e3) / (8.31446261815324 * 298.15))
>>> round(float(comparison.rate_enhancement / expected), 6)
1.0

A catalyst that lowers \(E_a\) by 30 kJ/mol at room temperature speeds the reaction up by many orders of magnitude:

>>> comparison.rate_enhancement > 1.0e5
True
chemistrykit.surface.dubinin_radushkevich_loading(P, W0, E, P0, T, R_gas=8.31446261815324)[source]#

Dubinin-Radushkevich micropore filling \(W = W_0\exp[-(A/E)^2]\).

Parameters:
  • P (float or array-like of float) – Equilibrium pressure.

  • W0 (float) – Limiting micropore volume (or loading) at \(P=P_0\).

  • E (float) – Characteristic energy, in J/mol.

  • P0 (float) – Saturation vapor pressure at T.

  • T (float) – Absolute temperature, in K.

  • R_gas (float)

Returns:

float or ndarray

Examples

When the adsorption potential equals the characteristic energy (\(A=E\)), the pores are filled to exactly \(W_0/e\):

>>> T, E = 300.0, 10.0e3
>>> P = 1.0 * np.exp(-E / (8.31446261815324 * T))
>>> round(float(dubinin_radushkevich_loading(P, W0=2.0, E=E, P0=1.0, T=T) * np.e), 8)
2.0
chemistrykit.surface.er_rate(k, K_A, P_A, P_B)[source]#

Eley-Rideal rate \(k\theta_AP_B\), with \(\theta_A\) the Langmuir coverage of A.

Parameters:
  • k (float) – Rate constant for gas-phase B striking adsorbed A.

  • K_A (float) – Langmuir adsorption equilibrium constant of A.

  • P_A (float or array-like of float) – Partial pressures of the adsorbed reactant A and gas-phase reactant B.

  • P_B (float or array-like of float) – Partial pressures of the adsorbed reactant A and gas-phase reactant B.

Returns:

float or ndarray

Examples

At the half-saturation pressure of A the rate is \(kP_B/2\):

>>> round(float(er_rate(k=4.0, K_A=2.0, P_A=0.5, P_B=3.0)), 10)
6.0

Doubling \(P_B\) always doubles the rate (first order in B):

>>> r1 = er_rate(4.0, 2.0, 10.0, 1.0)
>>> r2 = er_rate(4.0, 2.0, 10.0, 2.0)
>>> round(float(r2 / r1), 10)
2.0
chemistrykit.surface.first_order_peak_temperature(Ed, nu, beta, R_gas=8.31446261815324)[source]#

Exact first-order TPD peak temperature from \(E_d/(RT_p^2)=(\nu/\beta)e^{-E_d/RT_p}\).

Parameters:
  • Ed (float) – Desorption activation energy, in J/mol.

  • nu (float) – Pre-exponential factor, in 1/s.

  • beta (float) – Heating rate, in K/s.

  • R_gas (float)

Return type:

float

Returns:

float – Peak temperature, in K.

Examples

>>> Tp = first_order_peak_temperature(Ed=100e3, nu=1e13, beta=10.0)
>>> lhs = 100e3 / (8.31446261815324 * Tp**2)
>>> rhs = 1e13 / 10.0 * np.exp(-100e3 / (8.31446261815324 * Tp))
>>> round(float(lhs / rhs), 8)
1.0
chemistrykit.surface.fit_bet(P, V, P0)[source]#

Fit loading-vs-pressure data to the BET isotherm via its linearization.

The BET equation rearranges to a straight line in \(x=P/P_0\) (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.1; Brunauer, Emmett & Teller 1938):

\[\frac{x}{V(1-x)} = \frac{1}{V_m C} + \frac{C-1}{V_m C}\,x\]

so plotting \(x/[V(1-x)]\) against x gives a straight line of intercept \(1/(V_mC)\) and slope \((C-1)/(V_mC)\); solving the two simultaneously gives \(V_m = 1/(\text{slope}+\text{intercept})\) and \(C = 1 + \text{slope}/\text{intercept}\). Fitting is normally restricted to the range \(0.05 \lesssim x \lesssim 0.35\), where the BET assumptions are most reliable (Atkins & de Paula, loc. cit.) – not enforced here, since this function only performs the regression on whatever data it is given.

Parameters:
  • P (array-like of float) – Equilibrium pressures (at least 2 distinct values, all < P0).

  • V (array-like of float) – Corresponding measured adsorbed volumes/amounts.

  • P0 (float) – Saturation vapor pressure of the adsorbate.

Return type:

BETFit

Returns:

BETFit

Examples

Generate exact data from a known \((V_m, C)\) and recover them:

>>> P0 = 10.0
>>> P = np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0])
>>> V = BETIsotherm(Vm=5.0, C=80.0, P0=P0).loading(P)
>>> fit = fit_bet(P, V, P0=P0)
>>> round(fit.Vm, 4), round(fit.C, 2)
(5.0, 80.0)
>>> round(fit.r_squared, 6)
1.0
chemistrykit.surface.fit_dubinin_radushkevich(P, W, P0, T, R_gas=8.31446261815324)[source]#

Fit data to the Dubinin-Radushkevich isotherm via \(\ln W = \ln W_0 - A^2/E^2\).

Parameters:
  • P (array-like of float) – Equilibrium pressures, all below P0.

  • W (array-like of float) – Corresponding adsorbed volumes (all positive).

  • P0 (float) – Saturation vapor pressure at T.

  • T (float) – Absolute temperature, in K.

  • R_gas (float)

Return type:

DubininRadushkevichFit

Returns:

DubininRadushkevichFit

Examples

>>> P = np.array([1e-4, 1e-3, 1e-2, 0.05, 0.1, 0.3])
>>> W = dubinin_radushkevich_loading(P, W0=0.4, E=9.0e3, P0=1.0, T=77.0)
>>> fit = fit_dubinin_radushkevich(P, W, P0=1.0, T=77.0)
>>> round(fit.W0, 6), round(fit.E, 3)
(0.4, 9000.0)
chemistrykit.surface.fit_freundlich(P, q)[source]#

Fit loading-vs-pressure data to the Freundlich isotherm via its linearization.

Taking logarithms of \(q = K_f P^{1/n}\) gives a straight line (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.1):

\[\ln q = \ln K_f + \frac{1}{n}\ln P\]

so plotting \(\ln q\) against \(\ln P\) gives a straight line of intercept \(\ln K_f\) and slope \(1/n\).

Parameters:
  • P (array-like of float) – Equilibrium pressures (at least 2 distinct positive values).

  • q (array-like of float) – Corresponding measured loadings (all positive).

Return type:

FreundlichFit

Returns:

FreundlichFit

Examples

Generate exact data from a known \((K_f, n)\) and recover them:

>>> P = np.array([0.1, 0.5, 1.0, 2.0, 5.0, 10.0])
>>> q = FreundlichIsotherm(Kf=4.0, n=2.5).loading(P)
>>> fit = fit_freundlich(P, q)
>>> round(fit.Kf, 6), round(fit.n, 6)
(4.0, 2.5)
>>> round(fit.r_squared, 6)
1.0
chemistrykit.surface.fit_langmuir(P, q)[source]#

Fit loading-vs-pressure data to the Langmuir isotherm via its linearization.

The Langmuir equation \(q = q_{max}KP/(1+KP)\) is inverted to a straight line (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.1):

\[\frac{1}{q} = \frac{1}{q_{max}} + \frac{1}{q_{max}K}\cdot\frac{1}{P}\]

so plotting \(1/q\) against \(1/P\) gives a straight line of intercept \(1/q_{max}\) and slope \(1/(q_{max}K)\).

Parameters:
  • P (array-like of float) – Equilibrium pressures (at least 2 distinct positive values).

  • q (array-like of float) – Corresponding measured loadings (all positive).

Return type:

LangmuirFit

Returns:

LangmuirFit

Examples

Generate exact data from a known \((K, q_{max})\) and recover them:

>>> P = np.array([0.1, 0.5, 1.0, 2.0, 5.0, 10.0])
>>> q = LangmuirIsotherm(K=3.0, qmax=8.0).loading(P)
>>> fit = fit_langmuir(P, q)
>>> round(fit.K, 6), round(fit.qmax, 6)
(3.0, 8.0)
>>> round(fit.r_squared, 6)
1.0
chemistrykit.surface.fit_temkin(P, q, T, R_gas=8.31446261815324)[source]#

Fit data to the Temkin isotherm via \(q = (RT/b_T)\ln A_T + (RT/b_T)\ln P\).

Parameters:
  • P (array-like of float) – Equilibrium pressures and loadings in the middle-coverage range.

  • q (array-like of float) – Equilibrium pressures and loadings in the middle-coverage range.

  • T (float) – Absolute temperature, in K.

  • R_gas (float)

Return type:

TemkinFit

Returns:

TemkinFit

Examples

>>> P = np.array([0.5, 1.0, 2.0, 5.0, 10.0])
>>> q = temkin_loading(A_T=3.0, b_T=800.0, P=P, T=300.0)
>>> fit = fit_temkin(P, q, T=300.0)
>>> round(fit.A_T, 6), round(fit.b_T, 6)
(3.0, 800.0)
chemistrykit.surface.freundlich_loading(Kf, n, P)[source]#

The Freundlich isotherm \(q = K_f P^{1/n}\).

Parameters:
  • Kf (float) – Freundlich capacity constant (the loading at \(P=1\), in the pressure units used).

  • n (float) – Freundlich heterogeneity exponent (\(n>1\) is the usual, “favorable” case; \(n=1\) recovers a linear (Henry’s-law-like) isotherm).

  • P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray

Examples

At unit pressure, the loading equals \(K_f\) exactly, by construction:

>>> round(float(freundlich_loading(Kf=2.5, n=3.0, P=1.0)), 10)
2.5

With n=1 the isotherm is exactly linear in P:

>>> P = np.array([0.5, 1.0, 2.0, 4.0])
>>> q = freundlich_loading(Kf=3.0, n=1.0, P=P)
>>> bool(np.allclose(q, 3.0 * P))
True
chemistrykit.surface.gibbs_surface_excess(c, gamma, T, R_gas=8.31446261815324)[source]#

Surface excess from surface-tension data via the Gibbs equation \(\Gamma=-(1/RT)\,d\gamma/d\ln c\).

The derivative is taken numerically (second-order finite differences in \(\ln c\), numpy.gradient()), so c should be sampled densely enough that \(\gamma(\ln c)\) is smooth.

Parameters:
  • c (array-like of float) – Strictly positive, increasing bulk concentrations.

  • gamma (array-like of float) – Measured surface tensions at those concentrations, in N/m.

  • T (float) – Absolute temperature, in K.

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

Returns:

ndarray – Surface excess \(\Gamma\), in mol/m^2 (for gamma in N/m).

Examples

Applied to Szyszkowski surface-tension data, the Gibbs equation recovers a Langmuir isotherm for the surface excess:

>>> c = np.logspace(-3, 2, 2001)
>>> gamma = szyszkowski_surface_tension(c, gamma0=0.072, Gamma_max=5e-6, K=2.0, T=298.15)
>>> Gamma = gibbs_surface_excess(c, gamma, T=298.15)
>>> bool(np.allclose(Gamma[1:-1], 5e-6 * 2.0 * c[1:-1] / (1 + 2.0 * c[1:-1]), rtol=1e-4))
True
chemistrykit.surface.langmuir_coverage(K, P)[source]#

The Langmuir isotherm’s fractional surface coverage \(\theta = KP/(1+KP)\).

Parameters:
  • K (float) – Langmuir equilibrium (adsorption) constant, inverse to P’s units (e.g. 1/Pa for P in Pa).

  • P (float or array-like of float) – Equilibrium (partial) pressure of the adsorbate.

Returns:

float or ndarray – Fractional coverage \(\theta \in [0, 1)\).

Examples

Coverage is exactly one-half at the half-saturation pressure \(P = 1/K\) – the defining, exactly-solvable feature of the Langmuir isotherm (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.1):

>>> round(float(langmuir_coverage(K=2.0, P=0.5)), 10)
0.5

Coverage saturates toward 1 at high pressure and vanishes at zero pressure:

>>> round(float(langmuir_coverage(K=2.0, P=0.0)), 10)
0.0
>>> round(float(langmuir_coverage(K=2.0, P=1.0e6)), 6)
1.0
chemistrykit.surface.lh_rate_dual_site(k, K_A, P_A, K_B, P_B)[source]#

Dual-site (bimolecular) Langmuir-Hinshelwood rate: two species compete for the same sites.

Two reactants A and B adsorb competitively onto the same pool of surface sites, and react where an A and a B happen to sit on neighboring sites – the classic mechanism for, e.g., the catalytic oxidation \(2CO+O_2 \to 2CO_2\) on a metal surface (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.5). Competitive Langmuir adsorption gives each species’ coverage as

\[\theta_A = \frac{K_AP_A}{1+K_AP_A+K_BP_B}, \qquad \theta_B = \frac{K_BP_B}{1+K_AP_A+K_BP_B}\]

and the surface-reaction rate is \(k\theta_A\theta_B\). A characteristic (and often counterintuitive) feature: for fixed \(P_B\), the rate is not monotonic in \(P_A\) – it rises at low \(P_A\) (more A available to react) but falls again at high \(P_A\) (A crowds B off the surface entirely), peaking at an intermediate pressure.

Parameters:
  • k (float) – Surface-reaction rate constant.

  • K_A (float) – Langmuir adsorption equilibrium constants of A and B.

  • K_B (float) – Langmuir adsorption equilibrium constants of A and B.

  • P_A (float or array-like of float) – Partial pressures of A and B.

  • P_B (float or array-like of float) – Partial pressures of A and B.

Returns:

float or ndarray

Examples

At low coverage of both species (\(K_AP_A,K_BP_B \ll 1\), the denominator \(\approx 1\)), the rate reduces to the naive mass-action product \(kK_AP_AK_BP_B\):

>>> k, K_A, K_B = 4.0, 2.0, 5.0
>>> P_A = np.array([1e-4, 2e-4])
>>> P_B = np.array([1e-4, 3e-4])
>>> rate = lh_rate_dual_site(k, K_A, P_A, K_B, P_B)
>>> bool(np.allclose(rate, k * K_A * P_A * K_B * P_B, rtol=1e-2))
True

The rate vanishes if either reactant is entirely absent (nothing to react with, even at full coverage of the other):

>>> round(float(lh_rate_dual_site(k=4.0, K_A=2.0, P_A=0.0, K_B=5.0, P_B=1.0)), 10)
0.0

The rate is non-monotonic in \(P_A\) at fixed \(P_B\) (rises, then falls, as A crowds B off the surface):

>>> P_A_scan = np.linspace(0.01, 50.0, 200)
>>> rate_scan = lh_rate_dual_site(k=4.0, K_A=2.0, P_A=P_A_scan, K_B=5.0, P_B=0.2)
>>> peak_index = int(np.argmax(rate_scan))
>>> bool(0 < peak_index < len(P_A_scan) - 1)
True
chemistrykit.surface.lh_rate_single_site(k, K_A, P_A)[source]#

Single-site Langmuir-Hinshelwood rate: a lone adsorbed species reacts unimolecularly.

A single reactant A adsorbs onto the catalytic surface (Langmuir equilibrium, constant \(K_A\)) and then reacts on the surface at a rate proportional to its own coverage \(\theta_A\):

\[\text{rate} = k\,\theta_A = \frac{kK_AP_A}{1+K_AP_A}\]

(Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.5, eq. 22.19). At low pressure (\(K_AP_A \ll 1\)) this reduces to a rate that is first order in \(P_A\); at high pressure (\(K_AP_A \gg 1\), the surface saturated with A) it becomes zero order in \(P_A\) – the hallmark Langmuir-Hinshelwood pressure dependence, qualitatively different from a simple gas-phase elementary reaction.

Parameters:
  • k (float) – Surface-reaction rate constant (the rate at full coverage, \(\theta_A=1\)).

  • K_A (float) – Langmuir adsorption equilibrium constant of A.

  • P_A (float or array-like of float) – Partial pressure of A.

Returns:

float or ndarray

Examples

At the Langmuir half-saturation pressure \(P_A=1/K_A\), the rate is exactly half the saturation (zero-order) rate k:

>>> round(float(lh_rate_single_site(k=4.0, K_A=2.0, P_A=0.5)), 10)
2.0

Low-pressure limit is (to leading order) first order in P_A:

>>> k, K_A = 4.0, 2.0
>>> P_small = np.array([1e-4, 2e-4, 4e-4])
>>> rate = lh_rate_single_site(k, K_A, P_small)
>>> bool(np.allclose(rate, k * K_A * P_small, rtol=1e-3))
True
chemistrykit.surface.polanyi_potential(P, P0, T, R_gas=8.31446261815324)[source]#

Polanyi adsorption potential \(A = RT\ln(P_0/P)\), in J/mol.

Pressures above P0 (bulk condensation) are clipped to \(A=0\).

Parameters:
  • P (float or array-like of float) – Equilibrium pressure (same units as P0).

  • P0 (float) – Saturation vapor pressure at temperature T.

  • T (float) – Absolute temperature, in K.

  • R_gas (float)

Returns:

float or ndarray

Examples

At saturation the potential vanishes; at \(P=P_0/e\) it equals \(RT\):

>>> polanyi_potential(P=1.0, P0=1.0, T=77.0)
0.0
>>> round(polanyi_potential(P=np.exp(-1.0), P0=1.0, T=100.0, R_gas=8.0), 10)
800.0
chemistrykit.surface.redhead_desorption_energy(T_peak, nu, beta, R_gas=8.31446261815324)[source]#

Redhead’s estimate \(E_d = RT_p[\ln(\nu T_p/\beta) - 3.64]\), in J/mol.

Parameters:
  • T_peak (float or array-like of float) – Measured first-order TPD peak temperature, in K.

  • nu (float) – Assumed pre-exponential factor, in 1/s (commonly \(10^{13}\)).

  • beta (float) – Heating rate, in K/s.

  • R_gas (float)

Returns:

float or ndarray

Examples

Redhead’s formula recovers the true desorption energy to within about one percent at the common choice \(\nu=10^{13}\) s^-1:

>>> Tp = first_order_peak_temperature(Ed=100e3, nu=1e13, beta=10.0)
>>> bool(abs(redhead_desorption_energy(Tp, nu=1e13, beta=10.0) / 100e3 - 1.0) < 0.015)
True
chemistrykit.surface.simulate_tpd(Ed, nu, beta, theta0=1.0, order=1, T_start=100.0, T_end=800.0, n_points=20001, R_gas=8.31446261815324)[source]#

Simulate a TPD spectrum from the Polanyi-Wigner equation (first or second order).

Parameters:
  • Ed (float) – Desorption activation energy, in J/mol.

  • nu (float) – Pre-exponential factor, in 1/s (first order) or 1/(s * coverage) (second order).

  • beta (float) – Linear heating rate, in K/s.

  • theta0 (float) – Initial coverage.

  • order (int) – Desorption order.

  • T_start (float) – Temperature range of the ramp, in K.

  • T_end (float) – Temperature range of the ramp, in K.

  • n_points (int) – Number of grid points.

  • R_gas (float)

Return type:

TPDResult

Returns:

TPDResult

Examples

The simulated first-order peak sits where the exact peak condition puts it:

>>> res = simulate_tpd(Ed=100e3, nu=1e13, beta=10.0)
>>> bool(abs(res.peak_temperature - first_order_peak_temperature(100e3, 1e13, 10.0)) < 0.05)
True
chemistrykit.surface.szyszkowski_surface_tension(c, gamma0, Gamma_max, K, T, R_gas=8.31446261815324)[source]#

Szyszkowski surface tension \(\gamma = \gamma_0 - RT\Gamma_{max}\ln(1+Kc)\).

Parameters:
  • c (float or array-like of float) – Bulk solute concentration (mol/m^3 or any unit consistent with K).

  • gamma0 (float) – Surface tension of the pure solvent, in N/m.

  • Gamma_max (float) – Saturation surface excess, in mol/m^2.

  • K (float) – Adsorption constant, inverse to c’s units.

  • T (float) – Absolute temperature, in K.

  • R_gas (float) – Gas constant, in J mol^-1 K^-1.

Returns:

float or ndarray – Surface tension, in N/m.

Examples

Pure solvent (c=0) has surface tension exactly \(\gamma_0\):

>>> round(float(szyszkowski_surface_tension(0.0, gamma0=0.072, Gamma_max=5e-6, K=1.0, T=298.15)), 10)
0.072
chemistrykit.surface.temkin_loading(A_T, b_T, P, T, R_gas=8.31446261815324)[source]#

The Temkin isotherm \(q = (RT/b_T)\ln(A_TP)\).

Valid only in the middle-coverage range; it turns negative for \(A_TP<1\), where the full expression uniform_energy_coverage() should be used instead.

Parameters:
  • A_T (float) – Temkin equilibrium binding constant, inverse to P’s units.

  • b_T (float) – Temkin heat-of-adsorption constant, in J/mol per unit loading.

  • P (float or array-like of float)

  • T (float) – Absolute temperature, in K.

  • R_gas (float)

Returns:

float or ndarray

Examples

The loading vanishes at \(P=1/A_T\), and each factor of e in pressure adds exactly \(RT/b_T\):

>>> round(float(temkin_loading(A_T=4.0, b_T=1.0e3, P=0.25, T=300.0)), 10)
0.0
>>> q1 = temkin_loading(4.0, 1.0e3, 1.0, 300.0)
>>> q2 = temkin_loading(4.0, 1.0e3, np.e, 300.0)
>>> round(float((q2 - q1) / (8.31446261815324 * 300.0 / 1.0e3)), 10)
1.0
chemistrykit.surface.turnover_frequency(rate, active_site_concentration)[source]#

Turnover frequency \(\text{TOF} = \text{rate}/[\text{active sites}]\).

The rate of product formation per catalytic active site per unit time – the standard measure of intrinsic catalytic activity, independent of how much catalyst is used (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.6).

Parameters:
  • rate (float) – Reaction (product-formation) rate, in concentration/time.

  • active_site_concentration (float) – Concentration (or amount) of catalytically active sites, in the same amount units as rate’s numerator.

Return type:

float

Returns:

float – TOF, in 1/time.

Examples

>>> round(turnover_frequency(rate=5.0e-3, active_site_concentration=2.0e-6), 2)
2500.0
chemistrykit.surface.turnover_number(moles_converted, moles_catalyst)[source]#

Turnover number \(\text{TON} = n_{\text{converted}}/n_{\text{catalyst}}\).

The total number of catalytic cycles a given amount of catalyst has performed over the course of a reaction – a dimensionless measure of catalyst durability/efficiency, as distinct from turnover_frequency()’s measure of instantaneous activity (Atkins & de Paula, Physical Chemistry, 11th ed., Ch. 22.6).

Parameters:
  • moles_converted (float) – Total moles of substrate converted to product.

  • moles_catalyst (float) – Moles of catalyst (active sites) used.

Return type:

float

Returns:

float – Dimensionless.

Examples

>>> round(turnover_number(moles_converted=0.5, moles_catalyst=1.0e-4), 1)
5000.0
chemistrykit.surface.uniform_energy_coverage(K_max, f, P)[source]#

Langmuir coverage averaged over a uniform spread of site energies of width \(fRT\).

Parameters:
  • K_max (float) – Langmuir constant of the most strongly binding sites.

  • f (float) – Dimensionless width of the site-energy spread, \(\Delta Q/RT\) (> 0).

  • P (float or array-like of float) – Equilibrium pressure.

Returns:

float or ndarray – Mean fractional coverage in \([0, 1)\).

Examples

As the spread vanishes the surface is uniform again and the Langmuir coverage \(KP/(1+KP)\) is recovered:

>>> round(float(uniform_energy_coverage(K_max=2.0, f=1e-8, P=0.5)), 6)
0.5

In the middle-coverage range the result is Temkin’s logarithm:

>>> K_max, f, P = 1.0e6, 20.0, 1.0e-2
>>> theta = uniform_energy_coverage(K_max, f, P)
>>> bool(abs(theta - np.log(K_max * P) / f) < 1e-3)
True