Works out a molecule's formula and molar mass from its structure and estimates boiling point, critical constants, formation energies and heat capacity by Joback's method.
How the precision is kept
The arithmetic is 64-bit sums of table values. The atomic weights are abridged to four to six significant digits and the Joback group values, the file says, were typed without the paper open and are not confirmed. A group-contribution estimate is good to a few percent at best, which the result states in its notes.
Calculations under this policy
4 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.
A sum over the elements present, each a whole-number count times a table weight.
process/env/cheminfo.js
lines 100 to 101
CadCraft 0.8.2
/** Molar mass, g/mol. */
export function molarMass(mol) { const c = composition(mol); let m = 0; for (const [k, n] of Object.entries(c)) { if (!(k in WEIGHT)) throw new Error('No weight for ' + k); m += n * WEIGHT[k]; } return m; }
Joback estimate
64-bit
Boiling point, critical temperature, pressure and volume, melting point, formation enthalpy and Gibbs energy, heat capacity and acentric factor of a molecule.
Sums of group values of three or four digits, then closed forms. The acentric factor subtracts 1 from a product of similar size and so has fewer digits than the estimates it is built from.
process/env/cheminfo.js
lines 197 to 210
CadCraft 0.8.2
/** Joback estimate for a molecule. → { ok, Tb, Tc, Pc (bar), Vc, Tf, Hf (kJ/mol), Gf, cp: (T) => J/mol/K | null, omega, groups, problems, notes } */
export function joback(mol) {
const { groups, missing } = groupsOf(mol), problems = [];
if (missing.length) { problems.push(problem('joback.missing', 'warning', 'No Joback estimate: this molecule has groups the table does not hold (' + missing.join('; ') + ').', [{ kind: 'molecule', id: formula(mol) }])); return { ok: false, groups, missing, problems }; }
const S = new Array(11).fill(0); let hasCp = true;
for (const [g, n] of Object.entries(groups)) { const row = GROUPS[g]; for (let k = 0; k < 7; k++) S[k] += n * row[k]; if (row[7] == null) hasCp = false; else for (let k = 7; k < 11; k++) S[k] += n * row[k]; }
const nAtoms = mol.atoms.length + mol.atoms.reduce((s, a) => s + a.h, 0);
const Tb = 198 + S[3], Tc = Tb / (0.584 + 0.965 * S[0] - S[0] * S[0]), Pc = Math.pow(0.113 + 0.0032 * nAtoms - S[1], -2), Vc = 17.5 + S[2];
const th = Tb / Tc, omega = 3 / 7 * th / (1 - th) * Math.log10(Pc / 1.01325) - 1;
const cp = hasCp ? (T) => (S[7] - 37.93) + (S[8] + 0.210) * T + (S[9] - 3.91e-4) * T * T + (S[10] + 2.06e-7) * T ** 3 : null;
const notes = ['Group-contribution estimate: a few percent for small hydrocarbons, worse for polar molecules. Not a measured value.'];
if (!hasCp) notes.push('The table holds no Cp terms for one of the groups, so no heat capacity was estimated.');
return { ok: true, groups, Tb, Tc, Pc, Vc, Tf: 122 + S[4], Hf: 68.29 + S[5], Gf: 53.88 + S[6], cp, omega, problems, notes };
}
Component from a structure
64-bit
A component for the property package from the Joback estimate, with a quadratic heat-capacity fit.
C_p ÷ R ≈ A + B T + C T² fitted at 11 temperatures from 300 to 800 K; h_f (J/mol) = H_f (kJ/mol) × 1000
The normal equations are scaled by 1, 1e3 and 1e6 before the linear solve, which keeps the three unknowns of similar size. The gas constant is typed here as 8.314462618. The largest relative misfit of the quadratic is returned with the component.
process/env/cheminfo.js
lines 212 to 227
CadCraft 0.8.2
/** A component spec for lib/process (`defineComponent`) from SMILES: MW, Tc, Pc, omega, Tb, hf, gf and a quadratic Cp/R fitted to the Joback cubic over 300-800 K.
* opts { id, name?, define?: true (register it), TcRange? } Throws when the estimate or the Cp is not available (give measured values instead). */
export function toComponent(smiles, { id, name, define = false } = {}) {
if (!id || !/^[a-z][a-z0-9_]{0,30}$/.test(id)) throw new Error('Give a component id of lower-case letters, digits and underscores.');
const mol = parse(smiles), j = joback(mol);
if (!j.ok) throw new Error(j.problems[0].text);
if (!j.cp) throw new Error('No heat-capacity estimate for this molecule; give Cp yourself.');
const T = Array.from({ length: 11 }, (_, i) => 300 + 50 * i), ata = [[0, 0, 0], [0, 0, 0], [0, 0, 0]], atb = [0, 0, 0];
for (const t of T) { const row = [1, t, t * t], y = j.cp(t) / 8.314462618; for (let a = 0; a < 3; a++) { atb[a] += row[a] * y; for (let b = 0; b < 3; b++) ata[a][b] += row[a] * row[b]; } }
const k = [1, 1e3, 1e6]; for (let a = 0; a < 3; a++) { atb[a] /= k[a]; for (let b = 0; b < 3; b++) ata[a][b] /= k[a] * k[b]; }
const co = solveLinear(ata, atb).map((v, i) => v / k[i]);
const maxErr = Math.max(...T.map((t) => Math.abs((co[0] + co[1] * t + co[2] * t * t) * 8.314462618 - j.cp(t)) / j.cp(t)));
const spec = { id, name: name || id, formula: formula(mol), MW: molarMass(mol), Tc: j.Tc, Pc: j.Pc, omega: j.omega, Tb: j.Tb, hf: j.Hf * 1000, gf: j.Gf * 1000, cp: { A: co[0], B: co[1], C: co[2], D: 0 }, src: 'Joback estimate (' + smiles + '), NOT confirmed', estimated: true, cpFitMaxError: maxErr };
if (define) defineComponent(spec);
return spec;
}
In detail
Method
Joback and Reid (1987) group sums and Edmister's acentric factor, with the formulas quoted in the file's SOURCES; a quadratic heat-capacity fit by scaled normal equations.
Number types
64-bit numbers. Atomic weights of four to six significant digits.
Limits
A molecule with a group that is not in the table gets no estimate and a problem naming the group. The review state of the table is 'not confirmed'.
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.