A reaction's rate constant¶
A reaction's rate constant at a temperature.
Arrhenius's law, k = A exp(-Ea / (R T)), the prefactor A (the rate at infinite temperature, per second for a first-order reaction), the activation energy Ea per mole and R the molar gas constant (lusine.constants.R, CODATA's). A first-order conversion over a step dt then takes 1 - exp(-k dt) of what is left; a game's conversion row names A and Ea, never k at a temperature. Source: Arrhenius, Z. Phys. Chem. 4, 226 (1889); Atkins and de Paula, Physical Chemistry, 9th ed., section 21.5. Checked against a reference in numpy, which has its own tests.
- Source
- Arrhenius, Z. Phys. Chem. 4, 226 (1889); Atkins and de Paula, Physical Chemistry, 9th ed., section 21.5.
- Proof
- reference, invariant, limit.
arrhenius.trait
namespace lusine.laws;
float<s^-1> arrhenius(float<s^-1> prefactor, float<J/mol> activation, float<K> temperature)
{
return prefactor * exp(-(activation / (lusine.constants.R * temperature)));
}
Arrhenius's law, k = A exp(-Ea / (R T)), a reaction's rate constant from its prefactor and activation energy at a temperature.
import os, sys
root = os.getcwd()
while not os.path.isfile(os.path.join(root, 'docsite.json')):
root = os.path.dirname(root)
sys.path.insert(0, os.path.join(root, 'tools'))
import numpy as np
from lusine import trait_run
F32 = np.float32
U = float(np.finfo(F32).eps) / 2 # binary32's unit roundoff, 2^-24
rng = np.random.default_rng(2026)
The kernel¶
The law as the library states it.
print(trait_run.source('laws/arrhenius.trait'))
// lusine.laws.arrhenius: a reaction's rate constant at a temperature. Arrhenius's law,
// k = A exp(-Ea / (R T)),
// the prefactor A (the rate at infinite temperature, per second for a first-order reaction), the activation energy
// Ea per mole and R the molar gas constant (lusine.constants.R, CODATA's). A first-order conversion over a step dt
// then takes 1 - exp(-k dt) of what is left; a game's conversion row names A and Ea, never k at a temperature.
// Source: Arrhenius, Z. Phys. Chem. 4, 226 (1889); Atkins and de Paula, Physical Chemistry, 9th ed., section 21.5.
// Checked against a reference in numpy, which has its own tests.
namespace lusine.laws;
float<s^-1> arrhenius(float<s^-1> prefactor, float<J/mol> activation, float<K> temperature)
{
return prefactor * exp(-(activation / (lusine.constants.R * temperature)));
}
The program¶
Each section runs the kernel over a lattice of items, one a case: the host sets every parameter, and main() binds the kernel over the items.
print(trait_run.law_program('laws/arrhenius.trait', 'arrhenius'))
from lusine.kernels.laws.arrhenius import arrhenius as reference
def law(**kw):
return trait_run.law('laws/arrhenius.trait', 'arrhenius', **kw)
namespace probe;
lusine.version version = { { number = 1 } };
struct item {
host float<s^-1> prefactor;
host float<J/mol> activation;
host float<K> temperature;
float<s^-1> result_0;
};
lattice<item, 1> items(spacing = 1 m, boundary = wall);
void main() { items.result_0 = lusine.laws.arrhenius(items.prefactor, items.activation, items.temperature); }
Reference: reactions from cool to hot¶
Activation energies from 20 to 200 kJ/mol at temperatures from 250 to 2000 K: the kernel within binary32's roundings of the reference, relative to the rate, the exponent's own rounding growing with its size.
ea, t = rng.uniform(2e4, 2e5, 400), rng.uniform(250.0, 2000.0, 400)
got, want = law(prefactor=1e13, activation=ea, temperature=t), reference(1e13, ea, t)
x = ea / (8.314 * t) # the exponent's size: its rounding is x times binary32's
err = np.max(np.abs(got - want) / want / (1.0 + x))
assert err <= 8 * U, err / U
print(f'within {err / U:.1f} x (1 + Ea/RT) x 2^-24 of the reference over {len(t)} reactions')
within 2.3 x (1 + Ea/RT) x 2^-24 of the reference over 400 reactions
Invariant: the Arrhenius plot¶
ln k against 1 / T is a line whose slope is -Ea / R.
t = np.linspace(280.0, 420.0, 15)
k = law(prefactor=1e13, activation=np.full(15, 8e4), temperature=t)
slope = np.polyfit(1.0 / t, np.log(k), 1)[0]
assert abs(slope * 8.31446261815324 / -8e4 - 1.0) < 1e-5, slope
print(f'slope {slope:.1f} K, -Ea/R = {-8e4 / 8.31446261815324:.1f} K')
slope -9621.8 K, -Ea/R = -9621.8 K
Limit: no barrier¶
With no activation energy the rate is the prefactor at every temperature.
k = law(prefactor=1e13, activation=np.zeros(3), temperature=np.array([100.0, 300.0, 3000.0]))
assert np.allclose(k, 1e13, rtol=4 * U)
print('the prefactor, whatever the temperature')
the prefactor, whatever the temperature