mopac7-wasm
MOPAC 7, the 1993 public-domain semi-empirical molecular orbital program by James J. P. Stewart, compiled to WebAssembly. MNDO, MINDO/3, AM1 and PM3, with heats of formation, orbital energies, symmetry labels, molecular orbital coefficients and Mulliken charges, in node and in the browser.
This is not a reimplementation. It is MOPAC 7.00 unchanged in its physics: the same Fortran, translated to C and compiled. Against a native build of that very C it reproduces every energy, charge, dipole and coordinate exactly; Provenance says what "exactly" covers, and names the one place it does not.
Why it exists
Drawing a molecular orbital in a browser needs orbital energies that are close to reality and a wavefunction to draw. A study of the methods that a browser can run, scored against 50 experimental photoelectron bands over 14 molecules, put MOPAC's NDDO hamiltonians ahead of everything else available:
| method | mean absolute error | download |
|---|---|---|
| MNDO, this package | 0.69 eV | 0.3 MB |
| AM1 / PM3, this package | 0.78 eV | 0.3 MB |
Extended Hückel, as lcao.cheminfo.org ships it |
1.45 eV | ~9 KB |
GFN2-xTB, through @peterspackman/occjs |
1.57 eV | 5.4 MB |
(50 bands over 14 molecules, from the cheminfo lcao photoelectron reference set;
the mean absolute error is taken over the bands without any fitted shift. The
two comparators are the same study's rows for the model lcao.cheminfo.org
currently ships and for GFN2-xTB through @peterspackman/occjs.)
Cost, on node 26 / arm64, 8 cores. The embedded module is decoded and compiled
once per JavaScript realm — 1.7 ms to compile, 3.2 ms for the first
instantiation and 0.5 ms for every one after — so a calculation is an
instantiation plus MOPAC's own run. node scripts/bench.mjs --repeats 21 clocks
that run at 0.43 ms for water, 1.61 ms for benzene, 3.24 ms for naphthalene,
6.57 ms for caffeine and 9.63 ms for ibuprofen (best of 21; medians 0.62,
3.40, 3.46, 7.66 and 10.61 ms). End to end, npm run benchmark puts a whole
mopac7() call on water at 1.71–1.97 ms over three runs of ~390 samples
each (±3 %), and the same call with nothing cached at 10.6–11.2 ms, so
caching the compiled module is worth 5.6–6.6x.
Those were measured on a machine that was not idle: the one-minute load average ran between 4.2 and 7.6 on its 8 cores while the benchmark was running. Every absolute timing above is therefore an upper bound, and the ratio is the figure to rely on.
Installation
npm i mopac7-wasm
Usage
import { mopac7 } from 'mopac7-wasm';
const result = await mopac7({
elements: ['O', 'H', 'H'],
coordinates: [
[0, 0.066772, 0],
[0.763466, -0.529952, 0],
[-0.763466, -0.529952, 0],
],
method: 'AM1',
});
result.heatOfFormation; // -59.17072 kcal/mol
result.ionizationPotential; // 12.44576 eV
result.pointGroup; // 'C2V'
result.orbitals[3]; // { index: 3, energy: -12.446, occupancy: 2, symmetry: '1B1' }
result.basis[3]; // { atomIndex: 0, element: 'O', type: 'Pz' }
// The coefficient of atomic orbital `ao` in molecular orbital `mo`.
const { basis, coefficients } = result;
const homo = 3;
coefficients[homo * basis.length + 3]; // 1, the out-of-plane oxygen lone pair
Coordinates are in ångström, energies in eV and the heat of formation in kcal/mol, exactly as MOPAC prints them.
Orbital phases are pinned, and the listing's are not used
An eigenvector is only defined up to its sign: c and -c are the same orbital,
and which one a diagonaliser lands on depends on rounding inside its sweeps. Two
builds of the same MOPAC really do disagree — on water at AM1, a native build of
this repository's own translated C and the WebAssembly build print opposite signs
for three of the six columns, every magnitude identical. Anything that colours a
lobe by phase would flip with the build, so result.coefficients does not carry
the listing's signs:
every molecular orbital is turned so that its largest-magnitude coefficient is positive, and when several coefficients tie at that magnitude the earliest atomic orbital decides.
Water's 1B1 lone pair therefore comes back as +1 on O 2pz whichever
sign the listing happened to print, and this package's water coefficients are
byte-identical to an independent MOPAC 7.01's under the same convention.
A degenerate set is not covered by this, and cannot be: its members are only defined up to a rotation of the set. See What it does not do.
Bundlers and workers
There is nothing to configure, anywhere. The WebAssembly binary lives inside the
JavaScript — gzipped, base64-encoded and decoded with DecompressionStream —
so there is no .wasm file to serve, no locateFile, no vite-plugin-wasm, no
experiments.asyncWebAssembly and no asset rule. The cost is the payload and
the glue — 473 kB of bundle, 327 kB gzipped over the wire — plus a few
milliseconds of decoding and compiling, once per JavaScript realm.
compileMopac7() returns the cached WebAssembly.Module, which is
structured-cloneable: compile it once on the main thread, postMessage it to
every worker, and each one instantiates it in well under a millisecond.
API
| export | what it does |
|---|---|
mopac7(options) |
atoms in, a parsed Mopac7Result out; throws a Mopac7Error on failure |
buildMopac7Input(options) |
atoms in, a MOPAC deck out; no WebAssembly involved |
runMopac7Job(input) |
a deck in, { input, listing, diagnostics, exitCode } out; never throws for MOPAC's own failures |
parseMopac7Output(listing) |
a listing in, a Mopac7Result out; parses a native MOPAC 7 run too, and pins the orbital phases |
compileMopac7() |
the cached WebAssembly.Module, for warming and for workers |
MOPAC7_ELEMENTS |
the elements each hamiltonian is parameterised for |
MOPAC7_LIMITS |
the array bounds this build was compiled with |
Mopac7Error |
carries code, and the deck and listing that produced the failure |
Mopac7Error.code is one of 'input', 'keywords', 'geometry',
'parameters', 'scf', 'aborted' or 'parse'. A run that did not reach
self-consistency throws rather than returning orbital energies nobody should
use.
Every instance is discarded after one calculation, and that is not a choice:
MOPAC 7 is Fortran 77 whose whole state lives in SAVEd COMMON blocks, so a
second run in the same instance inherits the first one's converged density. The
module is compiled once and reused, which is where all the speed is — the
benchmark measures the difference at 5.6–6.6x.
What it does not do
- At most 30 non-hydrogen atoms and 30 hydrogens (150 atomic orbitals), the
bounds MOPAC's own
SIZESfile was compiled with. Larger molecules are refused before the module is touched;MOPAC7_LIMITScarries the numbers. - Restricted (RHF) only.
spinwrites MOPAC's multiplicity keyword and MOPAC then runs its RHF half-electron treatment, which is a real open-shell calculation: a triplet O2 comes out 27.4 kcal/mol below the singlet. There is no unrestricted option —keywords: ['UHF']is refused withcode: 'input', because a UHF listing carries separate alpha and beta eigenvector blocks and noNO. OF FILLED LEVELS, which is not a shapeMopac7Resulthas. - Inside a degenerate set the individual orbitals are not reproducible. Two
builds may print different mixtures of, say, a π pair, because only the set is
defined and not its members; the phase convention above cannot help. Over the
42 verification decks the energies, charges and non-degenerate coefficients
agree exactly between the WebAssembly and native builds while degenerate
coefficients differ by as much as 1.3, and on one deck — AM1 hydrogen fluoride
— the WebAssembly build leaves one vector of the 1PI pair all zero and
symtrz.fcannot label the pair. Read a degenerate level as a set (sum its densities), never as two named orbitals. keywordsentries are checked, not passed through. One keyword per array entry, printable ASCII, no whitespace;+(MOPAC's second-keyword-line marker),SETUPandUHFare refused withcode: 'input'. Without that, a keyword holding a newline would shift every line of the deck below it and MOPAC would silently read the wrong geometry.- The elements MOPAC 7 carries, and no more.
MOPAC7_ELEMENTSlists them per method — PM3 is the widest at 30 elements, MINDO/3 the narrowest at 10, and only PM3 has magnesium while only MNDO and AM1 have lithium.NaandKare sparkles in AM1 and PM3, not parameterised atoms; MOPAC says so itself in the listing. - No transition metals beyond MNDO chromium and zinc, no dispersion, no solvation, no excited states through this API.
- No wall-clock budget. MOPAC's own
T=limit reads a CPU clock that returns zero under WebAssembly, so a long optimisation cannot be cut short from inside. Run it in a worker if that matters. - Geometry optimisation is available (
optimize: true) but every number in this README is a single point, which is what the reference set scored.
MOPAC rebuilds the geometry through internal coordinates, so result.coordinates
is MOPAC's frame and not the one handed in: atom 1 at the origin, atom 2 on +x.
For one to three atoms the deck is a Z-matrix, because getgeo.f reads three
atoms or fewer that way whatever XYZ says; and when the first three atoms are
collinear a dummy XX atom is inserted to break the line, which MOPAC then
drops from every printed block.
Provenance
The 156 Fortran-77 files come from the 1993_MOPAC7 directory of
openmopac/MOPAC-archive at commit
ed31531, checked by file count, by a tree digest and by the presence of its own
public-domain notice. They are translated to C with
f2c 20240504, built from source, and compiled with
Emscripten against netlib libf2c. Eight small patches
are applied, each a file in patches/ whose header quotes the exact compiler,
linker or sanitizer message it fixes: five are portability fixes and three are
genuine COMMON-block defects in MOPAC 7.00 that MOPAC 7.01 also fixed, found
here with AddressSanitizer and a static size check the build runs on every
block. Nothing in the hamiltonians, the parameters or the SCF is touched.
MOPAC 7.00 was written at the Frank J. Seiler Research Laboratory, United States Air Force Academy, and distributed through the Quantum Chemistry Program Exchange as QCPE program #688. It is the last open-source release of MOPAC: every later version is commercial, and today's openmopac/mopac is Apache-2.0. MOPAC 7 carries its own notice of public-domain status under 17 U.S.C. § 105 and prints it on every run; it is reproduced verbatim in LICENSE as that notice requires.
MOPAC 7 also incorporates work by A. Klamt (COSMO solvation), David Danovich (Green's-function ionisation potentials), W. Thiel (a routine from QCPE 438, MNDOC), Peter L. Cummins, Daniel Liotard and S. Olivella, together with fifteen netlib LAPACK 1.0b and reference BLAS routines.
Every build is checked against a native build of the same translated C on the
same input. node scripts/verify.mjs runs 42 decks — 14 molecules × 3 methods —
twice over, once through the WebAssembly module and once through
build/native/mopac7, parses both with this package's own parser and compares
every block it returns. Against verification/reference-mopac7.json, an
independent MOPAC 7.01 (the Ghemical packaging), the worst difference over all
306 occupied valence levels is 0.000000 eV. Against the native build of the very
same C, the worst absolute difference is exactly zero for each of: the heat
of formation, the ionisation potential, the total, electronic and core–core
energies, every orbital energy, every Mulliken charge, every electron density,
the dipole, the geometry, and every coefficient of a non-degenerate orbital.
The listings themselves are not identical, and the claim is deliberately not that
they are: for water at AM1 the two differ on 55 lines, all of them eigenvector
rows whose columns carry the opposite sign, and 3 of the 6 molecular orbitals are
affected with every magnitude equal. The phase convention removes that difference
from the parsed result. What it cannot remove is the mixture inside a degenerate
set, which verify.mjs prints as a note rather than a failure; see What it does
not do for the consequence and for the one deck where it
matters.
wasm/BUILD.json records the pins, the patches, the sizes and the digests, and
src/__tests__/wasmPayload.test.ts hashes what the committed payload
decompresses to on every test run.
License
MIT AND BSD-3-Clause AND HPND. The TypeScript, the build scripts and the tests
are MIT; MOPAC itself is public domain, taken from an archive under
BSD-3-Clause; the bundled LAPACK and BLAS are BSD-3-Clause; libf2c carries the
AT&T / Lucent / Bellcore f2c notice. No copyright is claimed over any of them.
See LICENSE for the full notices.
How to cite
Cite the program and the method you used, not this package. A wrapper is not a citable scientific contribution, and referees want the hamiltonian.
The program
Stewart, J. J. P. MOPAC: A semiempirical molecular orbital program. J. Comput.-Aided Mol. Des. 1990, 4 (1), 1–103. doi:10.1007/BF00128336
MOPAC 7.00 (1993), QCPE program #688.
The method
| keyword | citation |
|---|---|
MNDO |
Dewar, M. J. S.; Thiel, W. Ground states of molecules. 38. The MNDO method. Approximations and parameters. J. Am. Chem. Soc. 1977, 99, 4899–4907. doi:10.1021/ja00457a004 |
AM1 |
Dewar, M. J. S.; Zoebisch, E. G.; Healy, E. F.; Stewart, J. J. P. Development and use of quantum mechanical molecular models. 76. AM1: a new general purpose quantum mechanical molecular model. J. Am. Chem. Soc. 1985, 107, 3902–3909. doi:10.1021/ja00299a024 |
PM3 |
Stewart, J. J. P. Optimization of parameters for semiempirical methods. I. Method. J. Comput. Chem. 1989, 10, 209–220. doi:10.1002/jcc.540100208 — and II. Applications, ibid. 221–264. |
MINDO3 |
Bingham, R. C.; Dewar, M. J. S.; Lo, D. H. Ground states of molecules. XXV. MINDO/3. J. Am. Chem. Soc. 1975, 97, 1285–1293 (and 1294, 1302, 1307). doi:10.1021/ja00839a001 |
MOPAC prints the parameter reference for every element in the run, and several elements were parameterised in later papers than the four above — MNDO aluminium credits L. P. Davis et al., MNDO chlorine credits Dewar and Rzepa, and AM1 lithium is "TAKEN FROM MNDOC BY W. THIEL". Quote the lines your own output printed, not a static table.
If you want to say where the binary came from, mention this package in the
methods text ("MOPAC 7.00 compiled to WebAssembly, mopac7-wasm vX.Y.Z") rather
than in the bibliography.
Rebuilding the WebAssembly
The binary is committed, because building it needs f2c built from source, emscripten, and a translation of 156 Fortran files — an hour of toolchain, not a postinstall step. To rebuild it:
npm run build-wasm # -Os by default; --opt O3 for the fast build
npm run verify-wasm # 42 decks against the reference and the native build
node scripts/bench.mjs # cold init and per-deck timings
npm run verify-wasm needs no build: when the raw linker output (wasm/mopac7.mjs,
wasm/mopac7.wasm, both gitignored) is absent it runs the committed wasm/glue.js
and embedded payload instead, which is what the published package runs. The
wasm-against-native check needs build/native/mopac7 and says so when it is
missing.
BUILD.md documents the pipeline, the pins, the patches and the
three silent failure modes the script turns into hard errors. --native also
builds the same translated C with the host compiler, which is what the A/B check
compares against.