Skip to main content

pm6-rs

Rust-native implementation of the PM6 semiempirical NDDO method (J. J. P. Stewart, J. Mol. Model. 13, 1173 (2007)). Heats of formation, fully analytic nuclear gradients and Hessians, L-BFGS geometry optimization, P-RFO transition-state search, and the PM6-D3 / PM6-D3H4 / PM6-D3H4X post-SCF corrections, with Rust, Python-native, ASE and command-line interfaces.

  • Author: ss0832
  • License: GPL-3.0-or-later
  • Pure Rust linear algebra (faer + rayon); no LAPACK/BLAS.

Scope and validation status

Validated against the MOPAC v23.2.5 oracle (openmopac/mopac, Apache-2.0); parameters are extracted from the same release.

Capability Status
SCF energy (RHF + UHF), heats of formation ✅ frozen molecular set; s/p ≤1e-6, hypervalent/TM ≤1e-4 kcal/mol vs MOPAC; see limitations below
d-orbital two-center integrals (S, P, Cl, transition metals, …) ✅ e.g. TiCl₄ ≤2e-5, HCl ≤1e-8 kcal/mol
Analytic gradient (dual-number, Hellmann–Feynman) ✅ s/p and d, matches MOPAC & FD
Analytic Hessian + frequencies ✅ closed-shell s/p/d CPHF; open-shell d uses FD-of-analytic-gradient
L-BFGS geometry optimization ✅ all elements, matches MOPAC minima
Mulliken charges ✅ (≤1e-4 e across s/p and d)
Dipole (full vector, s/p and d) ✅ vs MOPAC ≤3e-4 D; the p–d channel was missing before 0.2.1 and cost H₂S 1.39 D
External electric field: energy, analytic gradient, analytic Hessian ✅ RHF + UHF + DC, vs MOPAC FIELD= both signs; periodic refused
Dipole derivatives (atomic polar tensor), IR intensities ✅ three independent routes agree to 4e-8 e
Molecular orbitals, molden export, first-order orbital response ✅ round-tripped; the expensive products are lazy
SCF stability analysis (Seeger–Pople: singlet-real, triplet-real, UHF internal) + instability removal ✅ all three operators vs FD of the energy ≤1e-6; square H₄ saddle → minimum 0.58 eV lower; stretched H₂ singlet-stable / triplet-unstable, UHF switch 3.56 eV lower. Off by default; real→complex not implemented
⟨S²⟩ and spin contamination (UHF) ✅ CH₃· 0.7541 vs exact 0.75; broken-symmetry H₂ 0.9889 vs exact 0
Lanthanide sparkles (Ce…Yb) ⚠️ functional (0-orbital cores, oracle-derived ΔH_f); energy ~10 kcal/mol from MOPAC
D3 dispersion (PM6-D3) ✅ matches MOPAC ≤1e-5 kcal/mol (incl. 102 atoms)
H4 / X corrections (PM6-D3H4 / -D3H4X) ✅ matches MOPAC ≤1e-5 kcal/mol; see the H4 gate note below
PM6-D3/H4/X under PBC (1D/2D/3D lattice sums) ✅ supercell-exact; energy, gradient, stress, Hessian
Python native + ASE APIs (energy/grad/forces/Hessian/opt/freq)
PBC — periodic SCF at Γ and k, RHF + UHF, 1D/2D/3D, s/p and d ✅ supercell Γ ⇔ k mesh ≤1e-8 eV/atom
PBC — analytic gradient and analytic stress (6 Voigt components) ✅ vs E(ε) finite difference ≤1e-6
PBC — analytic Hessian (periodic CPHF at Γ; complex CPHF at k, q = 0) ✅ vs FD of analytic forces ≤1e-6
PBC — phonons at any q of the k-mesh, band structure, ZPE, free energy ✅ supercell force constants; ASR imposed
PBC — perturbation theory (DFPT) at any q of the k-mesh, primitive cell ✅ vs 2× supercell spectrum 0.0018 cm⁻¹; LO–TO dipole term pinned
PBC — charged cells (background / Makov–Payne), 1D/2D/3D ✅ NaCl Madelung matches; E(L) follows q²α_M/2L
Divide-and-conquer (molecular, PBC Γ, PBC k): energy, gradient, stress ✅ exponent 1.13 vs dense 1.81; crossover ~98 atoms

✅ = validated to the stated tolerance; ⚠️ = implemented and functional, not yet bit-exact. Because MOPAC has no PM6 periodic path, the periodic code is validated against internal identities — supercell Γ ⇔ k mesh, analytic ⇔ finite difference, D(q) ⇔ supercell Γ — each a statement that two independently-coded routes must agree. See docs/scope.md for the full table of capabilities and limitations, and THIRD_PARTY_NOTICES.md.

The H4 covalent gate is discontinuous

PM6-D3H4 selects a donor–H–acceptor triple only while the donor–H bond is covalent (rDH < 1.15 Å), and drops the whole triple the instant that cutoff is crossed. That is a genuine jump discontinuity in the potential energy surface — 2.3–3.7 kcal/mol on an ammonium/water pair, a water dimer and a formic acid dimer — and MOPAC behaves identically, so it belongs to the H4 model rather than to this implementation. Where the energy jumps the gradient contains a delta function and the Hessian does not exist, but automatic differentiation cannot see a branch it did not take and returns a plausible number regardless.

  • Default (H4Smoothing::Mopac): bit-exact MOPAC energies, discontinuous surface.
  • H4Smoothing::C2 / CLI --h4-smoothing c2: tapers the gate over the last 0.05 Å with the C² switch the model already uses elsewhere, giving a differentiable surface for optimization, MD, Hessians and phonons. Outside that window the energy is bit-identical to MOPAC.

pm6_rs::h4_discontinuity_warning reports proximity to the gate; the CLI warns automatically for gradient, optimize, saddle, frequencies and hessian.

Units

Internal computation uses eV and Bohr with MOPAC's 2018-CODATA model constants (src/constants.rs). At the boundaries:

  • Rust and Python-native APIs return atomic units (Hartree, Bohr), plus convenience eV fields and ΔH_f in kcal/mol.
  • The ASE calculator uses eV / Å (PM6 is natively eV, so no round-trip).

Build

cargo build --release        # library + pm6_rs_cli
cargo test                   # unit + MOPAC-oracle regression tests

CLI

pm6_rs_cli energy      water.xyz
pm6_rs_cli gradient    water.xyz
pm6_rs_cli optimize    water.xyz            # writes water.pm6opt.xyz
pm6_rs_cli frequencies water.pm6opt.xyz
pm6_rs_cli charges     water.xyz --charge 0 --multiplicity 1
pm6_rs_cli energy      dimer.xyz --method PM6-D3H4X   # PM6 | PM6-D3 | PM6-D3H4 | PM6-D3H4X
pm6_rs_cli orbitals    water.xyz --coefficients --window 3
pm6_rs_cli molden      h2s.xyz                       # [GTO]; d elements included
pm6_rs_cli molden      water.xyz --molden-basis sto  # the exact Slater primitives

pip install pm6-rs-python installs the same interface as pm6-rs. Periodic runs read the cell from an extended-XYZ Lattice="..." header, or from --cell:

pm6-rs energy   examples/polyethylene.xyz --kpts 1,1,8
pm6-rs stress   examples/polyethylene.xyz --kpts 1,1,8
pm6-rs bands    examples/polyethylene.xyz --kpts 1,1,8 --path "0,0,0;0,0,0.5"
pm6-rs phonons  examples/polyethylene.xyz --kpts 1,1,8 --supercell 1,1,7
pm6-rs phonons-dfpt examples/polyethylene.xyz --kpts 1,1,8 --qpoints "0,0,0"
pm6-rs response examples/nacl.xyz --properties born,dielectric
pm6-rs energy   big.xyz --dc --dc-buffer 12.0        # divide-and-conquer

Relaxing a cell (new in 0.2.6) — ions, lattice, or both:

pm6-rs optimize examples/polyethylene.xyz --kpts 1,1,8 --relax ions
pm6-rs optimize examples/polyethylene.xyz --kpts 1,1,8 \
       --relax all --cell-mask 0,0,1,0,0,0     # the c axis only

Which axes are periodic can be said in whichever way reads best, and a single --kpts N means N points per periodic axis:

pm6-rs energy chain.xyz --cell "20 20 2.55" --pbc z       --kpts 8
pm6-rs energy chain.xyz --cell "20 20 2.55" --pbc FFT     --kpts 1,1,8
pm6-rs energy slab.xyz  --cell "4 4 30"     --pbc-z false --kpts 6

Every SCF control the library has is now reachable too — --max-scf, --e-tol, --p-tol, --no-diis, --level-shift, --damping, --h4-smoothing, --projected, --strict, --sanity.

Rust API

use pm6_rs::{Molecule, Pm6Parameters, Pm6Options, run_pm6};

let mol = Molecule::from_xyz_file("water.xyz", 0.0)?;
let params = Pm6Parameters::standard()?;
let r = run_pm6(&mol, &params, &Pm6Options::default())?;
println!("ΔHf = {} kcal/mol", r.heat_of_formation_kcal);

Python

pip install maturin
maturin develop --release --features python   # or: pip install pm6-rs-python
import pm6_rs
import numpy as np

numbers = [8, 1, 1]
positions = np.array([[0.0, 0.0, 0.0], [0.9584, 0.0, 0.0], [-0.24, 0.9278, 0.0]])

# charge, multiplicity and reference ("auto"/"rhf"/"uhf") are per-call arguments.
out = pm6_rs.single_point(numbers, positions, charge=0.0, multiplicity=1, reference="auto")
print(out["heat_of_formation_kcal"], out["charges"])

g = pm6_rs.gradient(numbers, positions)          # dE/dR (Hartree/Bohr and eV/Å)
f = pm6_rs.forces(numbers, positions)            # −dE/dR
h = pm6_rs.hessian(numbers, positions)           # Hartree/Bohr², 3N×3N
w = pm6_rs.frequencies(numbers, positions)       # cm⁻¹ (evaluate at a minimum)

Periodic

cell = [[20.0, 0.0, 0.0], [0.0, 20.0, 0.0], [0.0, 0.0, 2.55]]   # Å
pbc = (False, False, True)                                       # a 1D chain

r = pm6_rs.calculate(numbers, positions, cell=cell, pbc=pbc, kpts=(1, 1, 8))
print(r["energy_ev"], r["stress_voigt_ev_per_angstrom3"])   # Voigt: xx yy zz yz xz xy

ph = pm6_rs.phonons(numbers, positions, cell, pbc=pbc,
                    supercell=(1, 1, 7), kpts=(1, 1, 8))
print(ph["frequencies_cm"][0], ph["decay_ratio"])

# Relax the ions, or the lattice, or both (new in 0.2.6).
out = pm6_rs.relax_cell(numbers, positions, cell, pbc=pbc, kpts=(1, 1, 8),
                        relax="all", cell_mask=[0, 0, 1, 0, 0, 0])
print(out["cell_angstrom"][2][2], out["max_stress_ev_per_angstrom3"])
print(out["cutoff_shell_crossed"])   # did it walk over the lattice-parameter step?

An explicitly-given kpts is now checked the way the Γ path has always been: a mesh whose Born–von Kármán supercell does not clear the integral cutoff is refused rather than answered. That is not conservatism — methane in a 6 Å cell at kpts=(2,2,2) returns −205.86 eV against a converged −177.17. allow_undersampled_cell=True overrides it, which is the right choice when the mesh is deliberately coarse (comparing an analytic derivative against a finite difference of its own energy, say).

charge is the net charge per cell, and heat_of_formation_kcal is None — ΔH_f is not defined per cell. kpts=None picks a mesh from the cell rather than defaulting to Γ, which is wrong (not merely coarse) on a small cell.

ASE

from ase.build import molecule
from pm6_rs.ase import PM6

atoms = molecule("H2O")
atoms.calc = PM6(charge=0, multiplicity=1, reference="auto")
print(atoms.get_potential_energy())   # eV
print(atoms.get_forces())             # eV/Å
print(atoms.calc.get_gradient())      # eV/Å  (= −forces)
print(atoms.calc.get_hessian())       # eV/Ų, 3N×3N

The calculator reads atoms.cell and atoms.pbc, so a periodic system needs no extra arguments, and get_stress() returns ASE's six-component Voigt vector in eV/ų — enough for StrainFilter, FrechetCellFilter and ase.md.npt.NPT:

from ase import Atoms
from ase.filters import FrechetCellFilter
from ase.optimize import BFGS

# Polyethylene: one C2H4 unit per cell, periodic along z only.
atoms = Atoms("C2H4",
              positions=[[0.000, 0.635, 0.000], [0.000, -0.635, 1.275],
                         [0.876, 1.235, 0.000], [-0.876, 1.235, 0.000],
                         [0.876, -1.235, 1.275], [-0.876, -1.235, 1.275]],
              cell=[20.0, 20.0, 2.55], pbc=(False, False, True))
atoms.calc = PM6(kpts=(1, 1, 8))

# mask relaxes the c axis only, which is the one that is periodic.
BFGS(FrechetCellFilter(atoms, mask=[0, 0, 1, 0, 0, 0])).run(fmax=0.05)
print(atoms.cell[2, 2])          # 2.529 A  (experiment: 2.53 A)
print(atoms.get_stress()[2])     # -2.6e-4 eV/A^3, i.e. relaxed

Ten BFGS steps, 7 s. The relaxed repeat lands on the experimental polyethylene c-axis, which nothing in the calculation was fitted to.

Attribution

pm6-rs is an independent Rust implementation, but the science in it is not original to this project. Four upstreams are involved, each with its license text and a notice naming the exact files here that came from it:

upstream license what came from it notice
MOPAC v23.2.5 Apache-2.0 the PM6 parameter tables in src/data/, the reference algorithms reimplemented across src/, and the oracle every numeric claim on this page is measured against third_party/mopac/NOTICE
PySEQM BSD-3-Clause the closed forms and frame convention for the s/p two-centre integrals in src/integrals.rs third_party/pyseqm/NOTICE
dftd3 (through MOPAC) see notice the D3 model and its C6 / r0ab / r2r4 reference tables third_party/dftd3/NOTICE
h_bonds4 (through MOPAC) see notice the H4 hydrogen-bond and X halogen-bond corrections third_party/h_bonds4/NOTICE

MOPAC is not vendored here and is not required to run pm6-rs; it is required to re-run the oracle in tools/oracle/. Its copyright notice — Molecular Orbital PACkage (MOPAC), Copyright 2021 Virginia Polytechnic Institute and State University — is retained in full at third_party/mopac/NOTICE, and every licence text and notice under third_party/ ships inside the wheel as well as the source distribution. See third_party/README.md for the index and THIRD_PARTY_NOTICES.md for the same material organised by what it is rather than where it came from.

References

  • J. J. P. Stewart, J. Mol. Model. 13, 1173 (2007) — PM6.
  • W. Thiel, A. A. Voityuk, J. Phys. Chem. 100, 616 (1996) — MNDO-d.
  • S. Grimme et al., J. Chem. Phys. 132, 154104 (2010) — D3.
  • J. Řezáč, P. Hobza, J. Chem. Theory Comput. 8, 141 (2012) — H4.
  • J. Řezáč, P. Hobza, Chem. Phys. Lett. 506, 286 (2011) — X (halogen).
  • G. Zhou et al., J. Chem. Theory Comput. 16, 4951 (2020) — PySEQM.
  • J. E. Moussa, J. J. P. Stewart, J. Open Source Softw. 11(119), 8025 (2026) — MOPAC.

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pm6_rs_python-0.2.7.tar.gz (1.3 MB view details)

Uploaded Source

File details

Details for the file pm6_rs_python-0.2.7.tar.gz.

File metadata

  • Download URL: pm6_rs_python-0.2.7.tar.gz
  • Upload date:
  • Size: 1.3 MB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for pm6_rs_python-0.2.7.tar.gz
Algorithm Hash digest
SHA256 f543384efd6d8edb20cb38dc25ef8701de95f57066e6a984d47be35bb7b9385d
MD5 bf826864ae7316d811abb2a49559bea9
BLAKE2b-256 98b48cbe25b650128fe4d29403bfc8e1440332f614d349b92ce716ffa2ed609a

See more details on using hashes here.

Release history Release notifications | RSS feed

This release

0.2.7 This release

1 file

0.2.5

1 file

0.1.2

1 file

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page