Reads a reaction mechanism file and follows the species concentrations and temperature of a closed, constant-volume vessel over time.
How the precision is kept
Rate constants, equilibrium constants and species thermodynamics are closed forms in 64-bit from the coefficients in the person's file. The gas constant here is written to all its digits. The concentrations over time come from the stiff integrator with a relative tolerance of 1e-8 and an absolute tolerance of 1e-12 by default.
Calculations under this policy
8 calculations in 1 source file. The code is read from the released app when this page loads. It is the calculation people run, not a copy kept for this page.
R as used for rate constants, equilibrium constants and species energies in this file.
R = 8.31446261815324 J/(mol K)
All fifteen digits of the defined value. The rest of the process engine imports a ten-digit R from data.js, so the two parts of the engine do not use the same number.
process/mechanism.js
line 21
CadCraft 0.8.2
const RGAS = 8.31446261815324;
Activation-energy units
64-bit
The factor that turns the file's activation-energy unit into J/mol.
cal/mol = 4.184; kcal/mol = 4184; K = R; eV = 96485.33212
The calorie factors are the exact thermochemical definition. The electron-volt factor is typed to ten significant digits.
The polynomials are written term by term with powers, not in nested form. The coefficient set for the temperature is picked by coeffsAt.
process/mechanism.js
lines 103 to 109
CadCraft 0.8.2
export function speciesThermo(sp, T) {
const a = coeffsAt(sp, T); if (!a) throw new Error('species ' + sp.name + ' has no NASA7 thermo');
const cpR = a[0] + a[1] * T + a[2] * T * T + a[3] * T ** 3 + a[4] * T ** 4;
const hRT = a[0] + a[1] * T / 2 + a[2] * T * T / 3 + a[3] * T ** 3 / 4 + a[4] * T ** 4 / 5 + a[5] / T;
const sR = a[0] * Math.log(T) + a[1] * T + a[2] * T * T / 2 + a[3] * T ** 3 / 3 + a[4] * T ** 4 / 4 + a[6];
return { cpR, hRT, sR, gRT: hRT - sR };
}
Pre-exponential factor to SI
64-bit
The rate constant's pre-exponential factor in m³, mol and s.
A_SI = A × (L³ ÷ Q)^(order − 1); E_a (J/mol) = E_a × unit factor
The reaction order is the sum of the reactant coefficients, plus 1 for a third body. One power and one multiplication; the length and quantity factors are powers of ten.
process/mechanism.js
lines 139 to 140
CadCraft 0.8.2
const order = Object.values(reactants).reduce((a, b) => a + b, 0) + (type === 'three-body' ? 1 : 0);
const A = rate.A * Math.pow(L * L * L / Q, order - 1), Ea = (rate.Ea || 0) * eaFactor; // m3, mol, s; J/mol
Rate constant
64-bit
The forward rate constant of a reaction at a temperature.
k = A T^b exp(−E_a ÷ RT)
One 64-bit expression: a power of T and an exponential, multiplied by the pre-exponential factor.
K_c of a reaction, used to get the reverse rate from the forward one.
K_c = exp(−Σ νᵢ gᵢ⁄RT) × (P₀ ÷ RT)^Δν
The Gibbs sum adds products and subtracts reactants in the order of the file; the terms are large and of opposite sign, so the exponent has fewer relative digits than its terms.
process/mechanism.js
lines 147 to 153
CadCraft 0.8.2
/** Concentration equilibrium constant Kc of a reaction (mol/m3 units). */
export function kcOf(mech, rx, T) {
let dG = 0, dn = 0;
for (const [n, nu] of Object.entries(rx.products)) { dG += nu * speciesThermo(mech.species[mech.index[n]], T).gRT; dn += nu; }
for (const [n, nu] of Object.entries(rx.reactants)) { dG -= nu * speciesThermo(mech.species[mech.index[n]], T).gRT; dn -= nu; }
return Math.exp(-dG) * Math.pow(mech.p0 / (RGAS * T), dn);
}
Net production rates
64-bit
The rate at which each species is made or used by all reactions together.
Near equilibrium the forward and reverse rates are nearly equal and their difference loses digits. Negative concentrations are taken as zero. Reactions are added in the order of the file.
process/mechanism.js
lines 154 to 170
CadCraft 0.8.2
/** Net molar production rates (mol/m3/s) at concentrations C (mol/m3) and temperature T. */
export function production(mech, C, T) {
const w = new Array(mech.names.length).fill(0), M = C.reduce((a, b) => a + Math.max(b, 0), 0);
for (const rx of mech.reactions) {
let kf = rateConstant(rx, T), kr = 0;
if (rx.reversible) kr = kf / kcOf(mech, rx, T);
let rf = kf, rr = kr;
for (const [n, nu] of Object.entries(rx.reactants)) rf *= Math.pow(Math.max(C[mech.index[n]], 0), (rx.orders && rx.orders[n]) ?? nu);
if (rx.reversible) for (const [n, nu] of Object.entries(rx.products)) rr *= Math.pow(Math.max(C[mech.index[n]], 0), nu);
let mult = 1;
if (rx.efficiencies) { mult = 0; mech.names.forEach((n, i) => { mult += (rx.efficiencies[n] ?? rx.efficiencies.default) * Math.max(C[i], 0); }); }
const rate = (rf - rr) * mult;
for (const [n, nu] of Object.entries(rx.reactants)) w[mech.index[n]] -= nu * rate;
for (const [n, nu] of Object.entries(rx.products)) w[mech.index[n]] += nu * rate;
}
return w;
}
Closed-vessel reactor
64-bit
Concentrations, and temperature when adiabatic, over time.
Integrated by the stiff solver with rtol 1e-8 and atol 1e-12 by default. The points returned are the solver's accepted steps.
process/mechanism.js
lines 171 to 186
CadCraft 0.8.2
/** spec: { T (K), C0 { name: mol/m3 }, t (s), adiabatic?, samples? } → { t[], T[], C[][], names, problems } */
export function batchReactor(mech, spec) {
const n = mech.names.length, C0 = mech.names.map((nm) => spec.C0[nm] || 0), adiabatic = !!spec.adiabatic;
const f = (t, y) => {
const C = y.slice(0, n), T = adiabatic ? y[n] : spec.T, w = production(mech, C, T), d = w.slice();
if (adiabatic) { // constant volume: sum(C cv) dT/dt = -sum(u_i w_i), u = h - RT
let num = 0, den = 0; mech.species.forEach((sp, i) => { const th = speciesThermo(sp, T), u = RGAS * T * (th.hRT - 1), cv = RGAS * (th.cpR - 1); num += u * w[i]; den += Math.max(C[i], 0) * cv; });
d.push(-num / Math.max(den, 1e-30));
}
return d;
};
const y0 = adiabatic ? [...C0, spec.T] : C0;
const r = stiff(f, 0, y0, spec.t, { rtol: spec.rtol || 1e-8, atol: spec.atol || 1e-12 });
const pts = r.points;
return { names: mech.names, t: pts.map((p) => p[0]), C: pts.map((p) => p[1].slice(0, n)), T: pts.map((p) => (adiabatic ? p[1][n] : spec.T)), final: r.y.slice(0, n), problems: mech.problems.slice(), steps: r.steps };
}
In detail
Method
NASA 7-coefficient polynomials; modified Arrhenius rate constants; reverse rates from the equilibrium constant; implicit Euler with step-doubling extrapolation (dynamics.js).
Number types
64-bit numbers. R = 8.31446261815324 J/(mol K). Numbers in the file are read with Number().
Tolerances
Integration: rtol 1e-8, atol 1e-12 unless the caller gives others. The heat-capacity sum in the adiabatic balance is floored at 1e-30.
Limits
Falloff, pressure-dependent, Chebyshev and surface reactions are listed as problems and left out. The electron-volt factor is typed to ten digits.
Approach: 64-bit, stated limit
The arithmetic is 64-bit, but the method or the data sets the accuracy: a solver tolerance, a mesh, a step size, a datum shift, a table. The limit is stated where the result is shown.