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:
ABCCommon interface for an equilibrium adsorption isotherm.
Concrete subclasses (
chemistrykit.surface.systems.langmuir.LangmuirIsotherm,chemistrykit.surface.systems.freundlich.FreundlichIsotherm,chemistrykit.surface.systems.bet.BETIsotherm) implementloading(), 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.
- 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 overridesfractional_coverage()to raise instead).- Type:
- class chemistrykit.surface.BETFit(Vm, C, P0, r_squared)[source]#
Bases:
objectResult of fitting loading-vs-pressure data to the BET isotherm.
- to_isotherm()[source]#
Return a
BETIsothermbuilt from the fitted parameters.- Return type:
- class chemistrykit.surface.BETIsotherm(Vm, C, P0)[source]#
Bases:
AdsorptionIsothermThe BET multilayer adsorption isotherm.
- Parameters:
Examples
>>> iso = BETIsotherm(Vm=2.0, C=100.0, P0=10.0) >>> round(float(iso.loading(P=1.0)), 4) 2.0387
- class chemistrykit.surface.CatalyticRateComparison(k_uncatalyzed, k_catalyzed, rate_enhancement, delta_Ea)[source]#
Bases:
objectResult of comparing a catalyzed and uncatalyzed rate constant at a given temperature.
- class chemistrykit.surface.DubininRadushkevichFit(W0, E, r_squared)[source]#
Bases:
objectResult of fitting loading-vs-pressure data to the Dubinin-Radushkevich isotherm.
- class chemistrykit.surface.DubininRadushkevichIsotherm(W0, E, P0, T)[source]#
Bases:
AdsorptionIsothermThe Dubinin-Radushkevich isotherm at a fixed temperature.
- Parameters:
Examples
>>> iso = DubininRadushkevichIsotherm(W0=0.5, E=12e3, P0=1.0, T=300.0) >>> round(float(iso.fractional_coverage(1.0)), 10) 1.0
- class chemistrykit.surface.FreundlichFit(Kf, n, r_squared)[source]#
Bases:
objectResult of fitting loading-vs-pressure data to the Freundlich isotherm.
- to_isotherm()[source]#
Return a
FreundlichIsothermbuilt from the fitted parameters.- Return type:
- class chemistrykit.surface.FreundlichIsotherm(Kf, n)[source]#
Bases:
AdsorptionIsothermThe Freundlich adsorption isotherm \(q(P) = K_f P^{1/n}\).
- Parameters:
Notes
Unlike
LangmuirIsotherm, the Freundlich isotherm has no saturation (monolayer) loading, sofractional_coverage()is not physically meaningful here and raisesNotImplementedError.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.
- class chemistrykit.surface.LangmuirFit(K, qmax, r_squared)[source]#
Bases:
objectResult of fitting loading-vs-pressure data to the Langmuir isotherm.
- to_isotherm()[source]#
Return a
LangmuirIsothermbuilt from the fitted parameters.- Return type:
- class chemistrykit.surface.LangmuirIsotherm(K, qmax=1.0)[source]#
Bases:
AdsorptionIsothermThe Langmuir monolayer adsorption isotherm \(q(P) = q_{max}\,KP/(1+KP)\).
- Parameters:
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:
- Returns:
float
Examples
>>> LangmuirIsotherm(K=4.0).half_saturation_pressure() 0.25
- class chemistrykit.surface.TPDResult(T, coverage, desorption_rate, peak_temperature)[source]#
Bases:
objectResult of a simulated temperature-programmed desorption run.
- class chemistrykit.surface.TemkinFit(A_T, b_T, r_squared)[source]#
Bases:
objectResult of fitting loading-vs-pressure data to the Temkin isotherm.
- class chemistrykit.surface.TemkinIsotherm(A_T, b_T, T)[source]#
Bases:
AdsorptionIsothermThe Temkin isotherm \(q = (RT/b_T)\ln(A_TP)\) at fixed temperature.
- Parameters:
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.
- 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_uncatalyzedfor a genuine catalyst).Ea_catalyzed (
float) – Activation energies of the uncatalyzed and catalyzed pathways, in J/mol (Ea_catalyzed < Ea_uncatalyzedfor 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:
- 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:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- Return type:
- 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=1the 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:
- 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:
- 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:
- 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:
- 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:
- 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:
- 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:
- 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:
- Return type:
- 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:
- Return type:
- 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:
- 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