Force-field IR¶
molrs holds every force field in one intermediate representation, the force-field IR: one set of styles, each with a fixed energy expression, parameter meanings and parameter units. The IR adopts LAMMPS's definitions as its standard — for every style molrs registers, the expression, the factors (no hidden ½), the parameter meanings and the units are those of the LAMMPS style of the same name, or of the LAMMPS style it corresponds to. Reading a LAMMPS force field and writing it back is therefore the identity on coefficients, and every other engine — GROMACS, OpenMM, AMBER, the GAFF / OPLS-AA / MMFF / UFF tables — converts to and from the IR at its reader, writer or typifier, never in a kernel.
The IR is data — molrs.ff.forcefield.ForceField (Rust
molrs::ff::forcefield::ForceField) — and reads no file. Every engine's
reader and writer is molrs.io's, as every other file reader and writer is:
one class per format and direction, its settings the constructor's keywords
(Rust: one constructor and with_* setters), reader.read(source) ->
ForceField and writer.write(sink, forcefield), plus what else the format
reads or writes as methods of the same class —
molrs.io.lammps.LammpsForcefieldReader / LammpsForcefieldWriter
(read_data_coeffs, read_cmap; write_data_coeffs, write_cmap),
molrs.io.gromacs.GromacsTopForcefieldReader / GromacsTopForcefieldWriter
(read_system, write_system), molrs.io.amber.AmberPrmtopForcefieldReader
(read_system) / AmberFrcmodWriter,
molrs.io.openmm_xml.OpenmmXmlReader / OpenmmXmlWriter, and
molrs.io.xml.XmlForcefieldReader / XmlForcefieldWriter. The same classes in
Rust (molrs::io::lammps, molrs::io::gromacs, molrs::io::amber,
molrs::io::openmm_xml, molrs::io::XmlForcefieldReader) read through
ForceFieldReader::read(src) and write through ForceFieldWriter::write(sink, &ff),
a source a path or a whole file's bytes and a sink a path or a Vec<u8>.
This page is the reference for the IR: what each style computes, what its parameters mean, which engine form maps onto it and how, and how Urey–Bradley, explicit 1-4 pairs and CMAP are represented. The IR is also a protocol: a new style or category, from Rust, Python or molpy, registers into it with nothing rebuilt — see Extending the force-field IR.
Units¶
A ForceField declares a LAMMPS unit preset (ForceField.units: real,
metal, lj; real when undeclared). Every parameter is a number in that
preset. Two rules are LAMMPS's own and hold in every preset:
Angles are degrees
Every angle-valued parameter — an equilibrium angle (theta0,
chi0), a dihedral or improper phase (phase, phase<m>,
phi1..phi3) — is stored in degrees, exactly as a LAMMPS
*_coeff line writes it. A force constant is per radianⁿ
(angle harmonic k is energy/rad²), as in LAMMPS. The kernels convert
the angle to radians once, when they are built.
This makes "the force-field IR stores exactly what LAMMPS writes" literally true for the
stored values: angle_coeff c3-c3-oh 76.79 109.66 is the type
{k: 76.79, theta0: 109.66}, with no conversion in either direction.
The forcefield section of a record states "angle": "degree" beside its
preset. A section built in memory with "angle": "radian" beside a preset is refused
(its preset and its stated angle unit disagree) rather than read in the wrong
units.
The LAMMPS reader keeps the file's units (a metal file is a metal force
field, coul/cut taking LAMMPS's metal Coulomb constant); the LAMMPS writer
converts energies and lengths only when the target units differs from the
force field's. Every other reader produces real.
Style reference¶
k, r0, theta0, … are the parameter names. They are LAMMPS's symbols,
lower-cased, with one exception: LAMMPS spells two different things d — a
phase (dihedral charmm, fourier) and a sign (dihedral harmonic,
improper cvff) — and its multiplicity n, so molrs names those slots
phase, sign and periodicity. The values and their order are LAMMPS's.
A parameter with a default may be left out: the compile fills the spec's
default (StyleSpec::gather, the one place a default is applied) before any
kernel, built-in or expression, sees the row, so leaving it out and stating
the default price alike. A parameter a kernel needs that has no default is
MissingParam; a value of the wrong kind or out of its domain (text for a
number, a mixing outside its choices) is BadValue.
Bonds¶
| Style | Energy | Parameters (units) | LAMMPS |
|---|---|---|---|
harmonic |
k (r − r0)² | k (E/L²), r0 (L) |
bond_style harmonic K r0 |
morse |
d0 [1 − e^(−alpha (r − r0))]² | d0 (E), alpha (1/L), r0 (L) |
bond_style morse D0 alpha r0 |
class2 |
k2 Δ² + k3 Δ³ + k4 Δ⁴, Δ = r − r0 | r0, k2, k3, k4 |
bond_style class2 r0 K2 K3 K4 |
mmff_bond |
MMFF94 cubic-quartic stretch | per-instance kb, r0 |
none |
uff_bond |
½ kb (r − r0)² (RDKit) | per-instance kb, r0 |
none |
Angles¶
| Style | Energy | Parameters (units) | LAMMPS |
|---|---|---|---|
harmonic |
k (θ − theta0)² | k (E/rad²), theta0 (deg) |
angle_style harmonic K theta0 |
charmm |
k (θ − theta0)² + k_ub (r₁₃ − r_ub)² | k (E/rad²), theta0 (deg), k_ub (E/L²), r_ub (L) |
angle_style charmm K theta0 K_ub r_ub |
class2 |
k2 Δ² + k3 Δ³ + k4 Δ⁴, Δ = θ − theta0 | theta0 (deg), k2, k3, k4 (E/radⁿ) |
angle_style class2 theta0 K2 K3 K4, its bb / ba lines written at zero (a non-zero cross term is refused: the IR has none) |
mmff_angle, mmff_stbn |
MMFF94 bend / stretch-bend | per-instance ka, theta0 (deg), kba_* |
none |
uff_angle |
UFF Fourier / order-n bend (RDKit) | per-instance ka, order, c0..c2 (theta0 kept as metadata, deg) |
none |
Dihedrals¶
| Style | Energy | Parameters (units) | LAMMPS |
|---|---|---|---|
periodic |
Σₘ kₘ [1 + cos(nₘ φ − γₘ)] | k<m> (E), periodicity<m>, phase<m> (deg); or one term k, periodicity, phase |
dihedral_style fourier m K1 n1 d1 … |
charmm |
k [1 + cos(n φ − d)], plus w·(its end atoms' 1-4 pair) |
k (E), periodicity, phase (deg), w |
dihedral_style charmm K n d w |
opls |
½[k1(1 + cos φ) + k2(1 − cos 2φ) + k3(1 + cos 3φ) + k4(1 − cos 4φ)] | k1..k4 (E) |
dihedral_style opls |
multi/harmonic |
Σₙ₌₁⁵ aₙ cosⁿ⁻¹ φ | a1..a5 (E) |
dihedral_style multi/harmonic |
nharmonic |
Σᵢ₌₁ᴺ aᵢ cosⁱ⁻¹ φ | a1..aN (E), contiguous, N ≥ 1 |
dihedral_style nharmonic N A1 … AN |
harmonic |
k [1 + sign cos(n φ)] | k (E), sign (±1), periodicity |
dihedral_style harmonic K d n |
class2 |
Σₙ₌₁³ kₙ [1 − cos(n φ − phiₙ)] | k1, phi1, k2, phi2, k3, phi3 (E, deg) |
dihedral_style class2 K1 phi1 K2 phi2 K3 phi3, its mbt/ebt/at/aat/bb13 lines written at zero (a non-zero one is refused) |
mmff_torsion |
½[V1(1 + cos φ) + V2(1 − cos 2φ) + V3(1 + cos 3φ)] | per-instance v1..v3 |
none |
uff_torsion |
V/2 [1 − cosTerm cos(n φ)] (RDKit) | per-instance V, order, cosTerm |
none |
Impropers¶
| Style | Energy | Parameters (units) | Atom order | LAMMPS |
|---|---|---|---|---|
harmonic |
k (χ − chi0)², χ = |φ(I,J,K,L)| | k (E/rad²), chi0 (deg) |
I is the centre | improper_style harmonic K chi0 |
cvff |
k [1 + sign cos(n φ(I,J,K,L))] | k (E), sign (±1), periodicity |
I is the centre | improper_style cvff K d n |
periodic |
k [1 + cos(n φ(I,J,K,L) − γ)] | k (E), periodicity, phase (deg) |
AMBER's: K is the centre | improper_style cvff (one term, γ ∈ {0°, 180°}: d = cos γ) |
mmff_oop |
½·143.9325 koop χ², χ the Wilson angle of bond I→L to plane (I,J,K) | per-instance koop |
I is the centre | none (geometry of improper_style umbrella / fourier) |
uff_inversion |
K [c0 + c1 cos ω + c2 cos 2ω], ω of bond I→L to plane (I,J,K) | per-instance K, c0..c2 |
I is the centre | improper_style fourier |
Pair styles¶
| Style | Energy | Parameters (units) | LAMMPS |
|---|---|---|---|
lj/cut |
C ε [(σ/r)ⁿ − (σ/r)ᵐ], C = n/(n−m)·(n/m)^(m/(n−m)); 4ε[(σ/r)¹² − (σ/r)⁶] at n = 12, m = 6 | epsilon (E), sigma (L); style cutoff, mixing, n, m, shift |
pair_style lj/cut (shift: pair_modify shift yes; mixing: pair_modify mix; n ≠ 12 or m ≠ 6, LAMMPS's mie/cut, is neither read nor written) |
lj/class2 |
ε [2(σ/r)⁹ − 3(σ/r)⁶] | epsilon, sigma; style cutoff, mixing (default sixthpower, LAMMPS's) |
pair_style lj/class2 (LAMMPS mixes it sixthpower whatever pair_modify says: another mixing is refused, and it reads as sixthpower) |
buck |
a e^(−r/rho) − c/r⁶ | a (E), rho (L), c (E·L⁶) |
pair_style buck A rho C |
morse |
d0 [(1 − e^(−alpha (r − r0)))² − 1] | d0 (E), alpha (1/L), r0 (L) |
pair_style morse D0 alpha r0 |
coul/cut |
coulomb qᵢqⱼ / (dielectric (r + delta)) | style coulomb (E·L/e²), dielectric (default 1), delta (L, default 0), cutoff (default ∞) |
pair_style coul/cut with delta = 0 (the buffer is molrs's, for MMFF; the LAMMPS writer refuses delta ≠ 0 and dielectric ≠ 1). LAMMPS fixes the constant (qqr2e) per units |
lj/charmm |
4ε[(σ/r)¹² − (σ/r)⁶]·S®, S CHARMM's switch from inner to cutoff |
epsilon, sigma, epsilon14, sigma14 (absent → epsilon, sigma); style inner, cutoff, mixing (default arithmetic), one_four ("regular", the default, or "epsilon14": what a special_bonds 1-4 pair is priced at, see 1-4) |
pair_style lj/charmm/coul/charmm, van-der-Waals half; pair_coeff i j ε σ ε₁₄ σ₁₄ (one_four = "epsilon14" has no LAMMPS form) |
coul/charmm |
coulomb qᵢqⱼ/(dielectric r)·S®; force (C qᵢqⱼ/r²)·S®, LAMMPS's switched force, not the gradient | style coulomb, dielectric (default 1), inner, cutoff |
pair_style lj/charmm/coul/charmm, Coulomb half (inner2 outer2 when its cutoffs differ) |
coul/long/pme |
Ewald-summed coulomb qᵢqⱼ/r over the frame's periodic box | style coulomb, cutoff, alpha, order, grid_*; the cell is the frame's box (frame.box), as LAMMPS's kspace reads its simulation box |
pair_style lj/cut/coul/long (the real-space half; kspace_style states an accuracy, not alpha, so the Ewald parameters are neither read nor written, and a LAMMPS-read style is refused (MissingParam) until they are stated) |
thole |
T® qᵢqⱼ/r, T = 1 − (1 + s r/2) e^(−s r), s = ½(aᵢ + aⱼ)/(αᵢαⱼ)^(⅙) | per type charge, alpha (L³), damp |
pair_style thole alpha damp (LAMMPS damps the Drude charges of the atoms; molrs's per-type charge is its own) |
coul/tt |
fₙ® qᵢqⱼ/r (Tang–Toennies) | per type charge; style b, c, order |
pair_style coul/tt (n = order) |
uff_lj, mmff_vdw |
UFF x/D LJ; MMFF buffered 14-7 | per-instance / per-type | none |
special_bonds (the force field's [1-2, 1-3, 1-4] weights for van der
Waals and Coulomb) is LAMMPS's special_bonds lj … coul ….
Cutoffs. Every pair style prices a pair only at r < cutoff, its own
cutoff, as LAMMPS's pair styles do (rsq < cutsq) — and the CHARMM
styles switch between inner and cutoff — at both compile doors:
PotentialCompiler.compile prices the rows of the frame's pairs list that
are inside the cutoff, exactly as compile_typed prices the pairs a
neighbour search finds. A special_bonds 1-4 pair is a pair-style pair and
is truncated too (LAMMPS applies special_lj / special_coul inside the
rsq < cutsq branch). This holds for built-in, run-time (Tier 1 and 2,
Python callables included) and expression styles alike. A style that
states no cutoff has cutoff ∞: the declared default of lj/cut,
lj/class2, buck, morse and coul/cut, and every style whose spec
declares none (thole, coul/tt, uff_lj, mmff_vdw), so a field read
from an engine that priced it untruncated (OpenMM NoCutoff, a prmtop)
prices every listed pair. A neighbour-driven evaluation (MD) still needs a
finite cutoff and refuses ∞ (BadValue). The 1-4 exceptions kernel — a
dihedral charmm w, per-pair overrides (1-4) — is
no pair style and is never truncated, as dihedral_charmm.cpp prices its
1-4 pair at any distance.
The periodic box is frame data. coul/long/pme reads its cell from the
frame being compiled (frame.box), never from a style parameter. A frame
without a box, with a box not periodic in x, y and z, or with a cell
outside LAMMPS's restricted triclinic form (a along x, b in the xy
plane) is refused by name (Rust CompileError::NoBox; Python
ValueError). Its spec declares exactly the parameters its kernel reads.
Improper atom order¶
Every LAMMPS improper style that is a dihedral (harmonic, cvff) prices
the dihedral I-J-K-L of the atoms in the order the data file lists them,
and so does every molrs kernel of such a style. LAMMPS names the first atom
the centre ("atom of symmetry") of harmonic and cvff; CHARMM writes its
impropers that way. The out-of-plane styles (fourier, umbrella; molrs's
uff_inversion, mmff_oop) take the centre first too.
AMBER prices its improper over the dihedral with the centre third. No
order with the centre first has that dihedral — an AMBER improper's axis runs
through its centre — so an AMBER improper cannot be re-ordered centre-first
and keep its energy. molrs therefore stores improper periodic (the AMBER /
GAFF / OpenMM improper) in AMBER's order: the order whose dihedral is the
improper angle, which is also the order a LAMMPS data file lists it in for
cvff to reproduce AMBER (verified with LAMMPS: the AMBER improper of the
hand molecule below gives 0.00338479168892827 kcal/mol as cvff in AMBER
order; molrs gives 0.003384791688934619).
| Source | File order | Stored order |
|---|---|---|
LAMMPS data / improper_coeff |
I, J, K, L | as written |
| GROMACS funct 4 (periodic), funct 2 (harmonic) | i, j, k, l (GROMACS prices φ(i,j,k,l); pdb2gmx lists a CHARMM improper centre first, an AMBER one centre third) | as written |
| AMBER prmtop, frcmod, GAFF typifier | i, j, K, l (centre third) | as written |
chamber prmtop CHARMM_IMPROPERS (improper harmonic) |
I (centre), J, K, L | as written |
OpenMM <Improper class1 … class4>, ordering default / amber |
c1 = centre; OpenMM prices φ(c2, c3, c1, c4) | (c2, c3, c1, c4); the writer writes the inverse |
OpenMM, ordering="charmm", no wildcard |
OpenMM prices φ(c1, c2, c3, c4) | as written |
OpenMM, ordering="smirnoff" |
three permutations averaged | refused |
OpenMM <CustomTorsionForce> harmonic improper (default ordering charmm) |
OpenMM prices φ(c1, c2, c3, c4) without a wildcard, φ(c2, c3, c1, c4) with one | as OpenMM prices; the writer refuses a wildcard |
OpenMM <RBTorsionForce><Improper> |
— | refused (no improper style is a cosine polynomial) |
molrs topology perception (generate_topology, trivalent_impropers) |
centre first | a force field that wants AMBER's order re-orders (GAFF does) |
| UFF, MMFF typifiers | — | centre first |
A frame's impropers row lists its atoms in the order of its type's
endpoints. Where OpenMM picks the order of two peripherals of equal type by
element and index, a caller that builds frame rows from an OpenMM-read field
decides the same way.
The OpenMM reader re-orders each improper row so the kernel prices
φ(c2, c3, c1, c4), the dihedral OpenMM prices. The OpenMM writer refuses
cvff (OpenMM cannot price a dihedral that starts at the centre).
1-4 interactions¶
LAMMPS has three mechanisms, and molrs represents each with LAMMPS's parameters:
- Global weights —
special_bonds lj w12 w13 w14 coul w12 w13 w14:ForceField.special_bonds. AMBER islj 0 0 ½ coul 0 0 5/6, OPLS-AA0 0 ½, CHARMM0 0 0(its 1-4 pairs are priced by the dihedral, below). The pair styles price a 1-4 pair at their own parameters (and switch), times the weight. - Per-type 1-4 Lennard-Jones —
pair_style lj/charmm/coul/charmm inner outer [inner2 outer2],pair_coeff I J epsilon sigma [epsilon14 sigma14]. In molrs, a pair stylelj/charmm(van der Waals half, aslj/cutis oflj/cut/coul/cut) with columnsepsilon,sigma,epsilon14,sigma14and style paramsinner,cutoff,mixing(LAMMPS's default for these styles isarithmetic;epsilon14/sigma14mix likeepsilon/sigma, as LAMMPS'sinit_onemixes them); self rows per type, explicit cross rows per type pair. Its Coulomb half iscoul/charmm(coulomb,dielectric,inner,cutoff). Both switch with CHARMM's S® = (r_c² − r²)²(r_c² + 2r² − 3r_in²)/(r_c² − r_in²)³ betweeninnerandcutoff, at both compile doors; the Lennard-Jones force is the gradient, the Coulomb force is LAMMPS's switched force C qᵢqⱼ S/r², which is not (pair_lj_charmm_coul_charmm.cpp).lj/charmm/coul/longis not read. GROMACS[ pairtypes ]land inepsilon14/sigma14(self rows and cross rows, see GROMACS topologies).
LAMMPS prices a special_bonds 1-4 pair under this style at the
regular epsilon / sigma; epsilon14 / sigma14 reach only the
w pairs of mechanism 3. Engines that price every bond-graph 1-4 pair
once with its own 1-4 parameters (OpenMM's <LennardJonesForce>
sigma14/epsilon14, GROMACS [ pairtypes ], a chamber prmtop's 1-4
table) mean it at epsilon14 / sigma14. The style param one_four
says which:
one_four |
A special_bonds 1-4 pair (no w, no override) is priced at |
|---|---|
absent or "regular" |
the regular epsilon / sigma — LAMMPS |
"epsilon14" |
epsilon14 / sigma14 (the cross row's for an explicit pair, else the two types' mixed by mixing) |
Any other value is refused (by the compile doors, ForceFieldSection.from_forcefield /
to_forcefield and every reader of a record). LAMMPS's pair style has no
"epsilon14" form, so the IR holds those pairs as per-pair override
rows (below): ForceField.materialize_one_four(frame) (Rust
ForceField::materialize_one_four) writes, on every is_14 row of the
frame's pairs (built when absent) that no w > 0 dihedral covers,
epsilon, sigma = the pair's 1-4 parameters and lj_scale,
coul_scale = the special_bonds 1-4 weights, keeping a cell already
set; under "regular" (or lj/cut) it writes the regular parameters, so
the energy does not change. A field declaring "epsilon14" needs it on
its frames before compiling: both compile doors refuse a 1-4 pair whose
1-4 parameters differ from its regular ones and that neither an override
nor w covers, naming materialize_one_four, and the LAMMPS writer
refuses the param (its deck would price the regular parameters).
3. Per-dihedral weight — dihedral_style charmm w: each dihedral
prices the pair of its own end atoms,
E₁₄ = w·[4ε₁₄((σ₁₄/r)¹² − (σ₁₄/r)⁶) + C qᵢqⱼ/r],
with the lj/charmm 1-4 parameters of the two types, the Coulomb style's
C = coulomb/dielectric, no cutoff and no switch
(dihedral_charmm.cpp; LAMMPS tallies it into evdwl and ecoul). w
is 1, ½ in six-membered rings, 0 in four- and five-membered rings; a pair
at the ends of several dihedrals takes the sum of their w. As in LAMMPS,
a field with any w > 0 is refused unless its special_bonds 1-4 weights
are both 0 (else the pair would be priced twice) and it has a lj/charmm
style and a Coulomb style; w outside [0, 1] is refused. w = 0 (AMBER's
use of the style) prices LAMMPS's K[1 + cos(nφ − d)] alone.
Per-pair exceptions LAMMPS cannot express — a GROMACS [ pairs ] row
with explicit parameters, an OpenMM NonbondedForce exception, an AMBER
dihedral whose SCEE/SCNB differ from the field's (the force-field
reader's read_system writes them, see AMBER prmtop) — are per-instance
float columns on the
Frame's pairs block, the rows the pair kernels already price (atomi,
atomj, is_14):
| Column | Meaning |
|---|---|
epsilon, sigma |
the LJ parameters of this pair, in place of the pair style's row or mixing (or the dihedral's epsilon14 / sigma14) |
lj_scale |
the van-der-Waals weight of this pair, in place of special_bonds.lj for its class (or of w) |
charge_product |
qᵢqⱼ (e²) of this pair, in place of the atoms' product |
coul_scale |
the Coulomb weight of this pair, in place of special_bonds.coul (or of w) |
A null cell (the column's validity mask) takes the value the pair has
without the row. OpenMM's exception (chargeProd, sigma, epsilon) is
charge_product, sigma, epsilon with both scales 1; a GROMACS funct-1
[ pairs ] row with parameters is sigma, epsilon (lj_scale 1,
coul_scale fudgeQQ), a funct-2 row adds charge_product and its own
fudgeQQ; AMBER's
per-dihedral divisors are lj_scale = 1/SCNB, coul_scale = 1/SCEE. A pair
style other than a 12-6 lj/cut or lj/charmm and a plain Coulomb
(coul/cut with delta = 0, coul/charmm) has no exception form, and a
field with one beside an exception is refused. LAMMPS can express none of
these per pair, so the LAMMPS writers refuse a frame that carries them,
naming the columns. (A pair14 table in a record is an unknown category,
kept as such.)
Precedence, per pair and per quantity: per-pair override > dihedral
charmm w > special_bonds. Every pair that carries an override cell or
sits at the ends of a w > 0 dihedral is priced once, by one exceptions
kernel at both compile doors,
E = lj_w·4ε[(σ/r)¹² − (σ/r)⁶] + coul_w·C qᵢqⱼ/r (no cutoff, no switch),
where each of ε, σ, qᵢqⱼ, lj_w, coul_w is the override cell if there is one,
else the dihedral's (ε₁₄, σ₁₄, qᵢqⱼ, Σw, Σw) when w > 0, else the pair
style's (ε, σ, qᵢqⱼ, special_bonds weight of the pair's bond-distance
class). The regular pair kernels price an override pair at weight 0: the
compiled door leaves its pairs row out, the neighbour-driven door zeroes
its weight (PairWeights). A w pair needs no such step — its
special_bonds 1-4 weight is 0. A cell is priced only by the style it
belongs to: epsilon, sigma, lj_scale under a Lennard-Jones style,
charge_product, coul_scale under a Coulomb style. A field without that
style ignores them, so a bonded-only (or Coulomb-only) field compiled on a
frame with materialized 1-4 cells prices no Lennard-Jones. Converting an exception table to a global
weight is not exact (r₁₄ depends on φ and on the other coordinates), so no
reader or writer does it.
Urey–Bradley¶
LAMMPS carries Urey–Bradley in one angle style, and so does molrs:
angle charmm, E = k(θ − theta0)² + k_ub(r₁₃ − r_ub)², parameters k
(E/rad²), theta0 (deg), k_ub (E/L²), r_ub (L) — angle_coeff t K theta0
K_ub r_ub, r₁₃ the distance between the angle's end atoms. It adds no
exclusion (the 1-3 pair is excluded by special_bonds). There is no separate
Urey–Bradley category. A field that mixes it with other angle styles is
LAMMPS's angle_style hybrid harmonic charmm (angle_coeff t charmm K theta0
K_ub r_ub), which the LAMMPS reader and writer read and write; molrs prices
each angle row under the style that defines its type. The LAMMPS, OpenMM
and GROMACS readers and writers and the prmtop reader (a chamber file) hold
it; the maps below are how each source writes it and maps onto the IR
(molrs has no reader of CHARMM's own .prm):
| Source | angle charmm |
|---|---|
CHARMM .prm ANGLES Ktheta Theta0 Kub S0 |
as written (CHARMM has no ½) |
GROMACS [ angletypes ] funct 5 θ₀ k_θ r13 k_UB (½k forms, nm, kJ/mol) |
k = k_θ/(2·4.184), theta0 = θ₀, k_ub = k_UB/(2·418.4), r_ub = 10·r13 |
OpenMM <AmoebaUreyBradleyForce><UreyBradley … k d> (AmoebaUreyBradleyForceBuilder.addUreyBradleys adds HarmonicBondForce.addBond(a1, a3, d, 2*k), energy ½·2k(r − d)², so k is un-halved) |
k_ub = k/418.4, r_ub = 10·d, joined with the HarmonicAngleForce row of the same three labels in either direction; without one, k = 0. Checked against OpenMM's energy (OpenMM XML) |
chamber prmtop CHARMM_UREY_BRADLEY (i, k, type), …_FORCE_CONSTANT K_ub, …_EQUIL_VALUE |
every angle of the file angle charmm: k = TK, theta0, and k_ub = K_ub, r_ub of the term on its end atoms (0, 0 without one) |
CMAP¶
The IR follows LAMMPS fix cmap (CHARMM's correction map), and the
kernel is cmap charmm, a step-for-step port of LAMMPS's
src/MOLECULE/fix_cmap.cpp:
- a crossterm names five atoms
(atomi, atomj, atomk, atoml, atomm); φ = dihedral(atomi, atomj, atomk, atoml), ψ = dihedral(atomj, atomk, atoml, atomm) — LAMMPS'satan2dihedrals, in degrees, the IUPAC sign (molrs'scompute_dihedral), in [−180°, 180°) with 180° read as −180°; - a map is an N×N grid (CHARMM and LAMMPS: N = 24, 15° spacing) of energies
in the force field's energy unit, stored φ-major: element
[i][j](flat indexi·N + j) is the energy at φ = −180° + i·Δ, ψ = −180° + j·Δ, Δ = 360°/N — the order of a CHARMM / LAMMPS.cmapfile (each# phiblock is one φ row of N ψ values); - the energy between grid points is LAMMPS's bicubic interpolation, with the derivatives LAMMPS precomputes from cubic splines.
A cmap style's row holds one map as its grid array parameter (f64
N×N, the layout above; the forcefield section's grid column is
f64[T, N, N]), and a frame's cmaps block lists the crossterms
(atomi … atomm, type).
The interpolation, exactly¶
Node derivatives (set_map_derivatives). With x_m = ⌊N/2⌋, the map is
extended periodically to 2N × 2N: T[p][q] = E[(p − x_m) mod N][(q − x_m) mod N],
so index p is the angle −180° + (p − x_m)·Δ and the extension covers
[−360°, 360°). spline(y) is LAMMPS's natural cubic spline on spacing Δ:
second derivatives y'' with y''₀ = y''_{n−1} = 0 and, for i = 1 … n−2,
p = 1/(y''_{i−1} + 4), y''_i = −p,
u_i = ((6y_{i+1} − 12y_i + 6y_{i−1})/Δ² − u_{i−1})·p, u₀ = 0
y''_j = y''_j·y''_{j+1} + u_j for j = n−2 … 0.
Every row T[k] is splined along ψ. For a node (φ index i, ψ index j), the
row splines give, down all 2N rows k, the value Y_k = T[k][j'] and the slope
D_k = (T[k][j'+1] − T[k][j'])/Δ − (Δ/3)·T''[k][j'] − (Δ/6)·T''[k][j'+1]
(j' = j + x_m); Y and D are splined along φ, and with i' = i + x_m
∂E/∂φ = (Y_{i'+1} − Y_{i'})/Δ − (Δ/3)·Y''_{i'} − (Δ/6)·Y''_{i'+1}
∂E/∂ψ = D_{i'}
∂²E/∂φ∂ψ = (D_{i'+1} − D_{i'})/Δ − (Δ/3)·D''_{i'} − (Δ/6)·D''_{i'+1}
per degree (per degree² for the cross term). The doubled map keeps the natural end conditions half a period from every node they are read at, so the slopes are those of a periodic spline to within its decay.
Patch (bc_coeff, bc_interpol). The cell is
c_φ = ⌊(φ + 180°)/Δ⌋, c_ψ = ⌊(ψ + 180°)/Δ⌋; its corners, counter-clockwise
from (c_φ, c_ψ), wrap mod N. The 16 coefficients c_ab are Numerical Recipes'
bcucof weight matrix applied to the corner values, Δ·∂E/∂φ, Δ·∂E/∂ψ and
Δ²·∂²E/∂φ∂ψ, and with t = (φ − φ_{c_φ})/Δ, u = (ψ − ψ_{c_ψ})/Δ
E = Σ_{a,b=0..3} c_ab tᵃ uᵇ,
∂E/∂φ = (180/π)/Δ · Σ a·c_ab tᵃ⁻¹ uᵇ, ∂E/∂ψ = (180/π)/Δ · Σ b·c_ab tᵃ uᵇ⁻¹
(per radian). At a node E is the grid value; E and its slopes are continuous across every cell edge and across ±180°.
Forces (post_force). F = −(∂E/∂φ)·∇φ − (∂E/∂ψ)·∇ψ with LAMMPS's own
∇φ, ∇ψ expressions, so the crossterm is distributed onto the five atoms
exactly as LAMMPS distributes it: atomi feels φ only, atomm ψ only, and the
three shared atoms both; the forces sum to zero and exert no torque.
Two LAMMPS behaviours are kept because they change energies: a crossterm with a degenerate dihedral plane (any of the four cross products |r_ij × r_jk|² below 10⁻⁴ Å⁴) contributes nothing; and the map is a true 2-D function — no sum of 1-D torsions reproduces it. One is generalised: LAMMPS fixes N = 24 and at most six maps, the kernel takes any N ≥ 2 and any number of maps (LAMMPS stores the derivative grids rotated by N/2 and finds their cell from φ wrapped to [0°, 360°); molrs stores them unrotated and reads them at the value cell — the same numbers but for float ties on a cell edge).
Crossterms and LAMMPS files¶
assign_cmaps(frame, ff)(Rustmolrs::ff::typifier::cmap::assign_cmaps) builds thecmapsblock: five atoms whose dihedrals(a, b, c, d)and(b, c, d, e)are both rows ofdihedrals(either stored direction) and whose atom types equal a cmap row'sitom … mtomforward — never reversed, since reading the five atoms backwards swaps φ and ψ.- A LAMMPS
fix cmapfile reads (LammpsForcefieldReader.read_cmap, RustLammpsForcefieldReader::read_cmap) into rows named"1"…"K"— maptis crossterm typet— and writes (LammpsForcefieldWriter.write_cmap, RustLammpsForcefieldWriter::write_cmap) thecmapslabels' grids in label id order, in CHARMM's layout: CHARMM's own file comes back line for line. - The data file's
N crosstermsheader line andCMAPsection (index type a1 … a5) are the frame'scmapsblock (type_id= map index), both ways. - The include writer emits
fix cmap all cmap <cmap_file>andfix_modify cmap energy yes; that fix must reach LAMMPS beforeread_data <data> fix cmap crossterm CMAP. The include reader reads afix cmapline's file relative to the include.
OpenMM's CMAPTorsionForce stores energy[i + N·j] at φ = 2πi/N, ψ = 2πj/N
(origin 0, φ fastest — CMAPTorsionForce::addMap): its reader maps element
(i, j) to molrs [(i + N/2) mod N][(j + N/2) mod N] (each index shifted by
N/2, the axes swapped into φ-major), and refuses an odd N, which puts no
OpenMM node on −180°; the writer is the inverse. OpenMM takes its node slopes
from periodic splines where LAMMPS splines the doubled map with natural ends,
so energies off the grid points differ at the spline's end effect: 3·10⁻¹²
relative on CHARMM36's alanine map at an ACE-ALA-NME conformer
(OpenMM XML). OpenMM's generator matches a crossterm's five
types forward or backward; assign_cmaps matches forward only. GROMACS [ cmaptypes ] lists CHARMM's grid in
the IR's layout (φ-major from −180°, GROMACS's cmap_setup_grid_index), so
its reader keeps element i·N + j at [i][j]; GROMACS's own interpolation
is CHARMM's too, and the CHARMM dipeptide's two crossterms price as GROMACS
2025.3 prices them to 6 × 10⁻¹⁶ (see How this is checked).
A prmtop stores a map as ParmEd's CmapType.grid — CHARMM's parameter-file
order, φ-major from −180° — in CHARMM_CMAP_PARAMETER_nn (a chamber file)
or CMAP_PARAMETER_nn (ff19SB), and the prmtop reader takes it as written.
sander's CMAP energy on a CHARMM36 alanine dipeptide and on an ff19SB one
off the grid points equals molrs's and LAMMPS's to 1e-14 relative (see
AMBER prmtop): sander interpolates as LAMMPS does.
Torsion forms and their exact conversions¶
Every Class-I torsion and dihedral-angle improper style above is a finite Fourier series in φ — the signed dihedral of the atoms as stored, the angle every molrs kernel and LAMMPS compute:
E(φ) = Σₙ₌₀ aₙ cos nφ + bₙ sin nφ
That series is the intermediate of every conversion between forms
(molrs::ff::ir::torsion). Each form embeds exactly (the series
is the same function of φ, constant included) and projects back exactly
— every coefficient, a₀ included — or refuses, naming the condition that
prevents it. Rows of several styles on one quadruple are one torsion: their
series add.
| Form | aₙ, bₙ of the form (n ≥ 1) | constant a₀ | image condition |
|---|---|---|---|
dihedral periodic (LAMMPS fourier), Σₘ kₘ[1 + cos(nₘφ − γₘ)] |
aₙ += kₘ cos γₘ, bₙ += kₘ sin γₘ | Σₘ kₘ; a periodicity-0 term k₀[1 + cos 0] = 2k₀ holds any other | none — every series; back: kₙ = √(aₙ² + bₙ²), γₙ = atan2(bₙ, aₙ), k₀ = (a₀ − Σkₙ)/2 |
dihedral charmm, k[1 + cos(nφ − d)] |
as one periodic term; w is no torsion parameter: a row with w ≠ 0 does not embed, a projection gives w = 0 |
k | one order, √(aₙ² + bₙ²) = |a₀|; back: k = a₀ |
improper periodic, k[1 + cos(nφ − γ)] |
as one periodic term |
k | as charmm |
dihedral harmonic, improper cvff, k[1 + d cos nφ] |
aₙ = k d | k | one order, bₙ = 0, |aₙ| = |a₀|; back: k = a₀, d = aₙ/a₀ |
dihedral opls, ½Σ kₙ[1 ± cos nφ] |
a₁ = k₁/2, a₂ = −k₂/2, a₃ = k₃/2, a₄ = −k₄/2 | ½Σkₙ | bₙ = 0, n ≤ 4, a₀ = a₁ − a₂ + a₃ − a₄ (E(180°) = 0) |
dihedral class2 (torsion part), Σₙ₌₁³ kₙ[1 − cos(nφ − φₙ)] |
aₙ = −kₙ cos φₙ, bₙ = −kₙ sin φₙ | Σkₙ | n ≤ 3, a₀ = Σ ±√(aₙ² + bₙ²) for some signs |
dihedral multi/harmonic, Σₙ₌₁⁵ Aₙ cosⁿ⁻¹φ |
cosᵏφ = 2⁻ᵏ Σⱼ C(k, j) cos((k − 2j)φ) | free | bₙ = 0, n ≤ 4; back: cos nφ = Tₙ(cos φ) |
dihedral nharmonic, Σᵢ₌₁ᴺ Aᵢ cosⁱ⁻¹φ |
as multi/harmonic |
free | bₙ = 0 |
dihedral rb (GROMACS funct 3, OpenMM RBTorsionForce), Σₙ₌₀⁵ Cₙ cosⁿ(φ − 180°) |
nharmonic with Aₙ₊₁ = (−1)ⁿ Cₙ (cos(φ − 180°) = −cos φ) |
free | bₙ = 0, n ≤ 5 |
Every periodicity must be an integer, as LAMMPS requires of every style here. A "sine term" (bₙ ≠ 0) is a phase other than 0° or 180°; phases on multiples of 90° are evaluated exactly, so a 180° phase is bₙ = 0, not 1.2·10⁻¹⁶. The n ≥ 1 coefficients are compared exactly; the constant, a sum of products, to 10⁻¹² of the series' scale Σ(|aₙ| + |bₙ|).
The constant term. a₀ is part of the energy, so an exact conversion
keeps it: the polynomial forms (multi/harmonic, nharmonic, RB) and
periodic carry any constant; every other form fixes its constant by its
other parameters, which is part of its image condition (a single-term form
takes k = a₀, so a negative k survives the round trip). A constant shifts no
force — two torsions are the same physics iff their series agree for n ≥ 1,
which FourierSeries::canonical compares — and a conversion up to a
constant is a fit with a free offset (Converting between
forms), which reports the offset. In particular
ΣCₙ = 0 is the condition of RB → OPLS beyond C₅ = 0. The GROMACS reader
([ dihedraltypes ] funct 3) and the OpenMM XML reader (<RBTorsionForce>)
read RB as the polynomial it is (multi/harmonic, or nharmonic when
C₅ ≠ 0), which holds every RB row exactly, constant included; neither
refuses ΣCₙ ≠ 0 or C₅ ≠ 0.
The familiar chains are instances:
- RB ↔
multi/harmonic: A₁ = C₀, A₂ = −C₁, A₃ = C₂, A₄ = −C₃, A₅ = C₄ (C₅ = 0); with C₅ ≠ 0 the exact target isnharmonic(N = 6). - RB ↔ OPLS (GROMACS manual Eqs. 200–201): k₁ = −2C₁ − 3C₃/2, k₂ = −C₂ − C₄, k₃ = −C₃/2, k₄ = −C₄/4; back C₀ = k₂ + (k₁ + k₃)/2, C₁ = (−k₁ + 3k₃)/2, C₂ = −k₂ + 4k₄, C₃ = −2k₃, C₄ = −4k₄, C₅ = 0.
- OPLS ↔
periodic: one term per non-zero kₙ,k = |kₙ|/2, phase 0° at odd n and 180° at even n for kₙ > 0 (the other for kₙ < 0), and a periodicity-0 term for the constant those leave over.
Outside the image.
improper harmonic, K(|φ| − chi0)², and LAMMPSdihedral quadratic, K(φ − φ0)², are not finite Fourier series and are refused (improper harmonicregisters no form codec;fit_formprojects onto it under a metric, with a residual). A periodic improper k[1 + cos(nφ − γ)] and a harmonic one agree only to second order about the minimum: K = n²k/2 in LAMMPS's un-halved K (K = 2k for AMBER's n = 2, γ = 180°; thek_h = n²ksometimes quoted is the ½-form harmonic ½k_h χ²), with chi0 the minimum nearest 0 and, back, γ = n·chi0 + 180°. The quartic terms differ by −k n⁴ δ⁴/24.dihedral class2's cross terms (mbt,ebt,at,aat,bb13) couple the torsion to bonds and angles: outside the Class-I IR.improper fourier(molrsuff_inversion) andmmff_oopprice an out-of-plane (Wilson) angle, not a dihedral.
In Rust:
use molrs::ff::ir::torsion::{FourierSeries, MultiHarmonicForm, OplsForm, TorsionRefusal};
use molrs::ff::style_registry::torsion_series;
// A stored row of any torsion style → its series (exact, constant included),
// through the style's registered form codec.
let series = torsion_series("dihedral", "opls", style.params(), &row)?;
// Several rows on one quadruple → one series; compare without the constant.
let total: FourierSeries = rows.iter().map(|r| r.to_series()).sum();
assert_eq!(total.canonical(), other.canonical());
// Series → a form, or the reason it cannot be.
match MultiHarmonicForm::from_series(&series) {
Ok(form) => form.to_params(),
Err(TorsionRefusal::SineTerm { n, .. }) => todo!("phase off 0/180° at order {n}"),
Err(other) => todo!("{other}"),
};
The per-form types (PeriodicForm, CharmmForm, CosineTermForm,
SignedCosineForm, OplsForm, Class2Form, MultiHarmonicForm,
NHarmonicForm, RyckaertBellemansForm, ImproperHarmonicForm; the Form
suffix tells a form from the kernel of the same style, such as
ff::potential::ImproperHarmonic) carry the maps one form at a time (from_params,
to_params, to_series, from_series, and nearest — the closest member
of the form's image, the start of a fit). FourierSeries::chopped(tol)
zeroes coefficients below a tolerance first, for input rounded on print. A
test evaluates every registered kernel on random geometries against the
series its codec embeds the row as, so the algebra, the codecs and the
kernels cannot drift.
Converting between forms¶
A form family is a set of styles that are one function space
parametrised differently. Each member registers a form codec beside its
kernel: its family, and two exact maps between its parameters and those of
the family's one canonical style — embed (this style → canonical, the
same energy, constant included, or a refusal) and project (canonical →
this style, exact on its image, else a refusal naming the condition) — and
optionally a seed, the nearest member of its image, where a fit starts.
| Family | Canonical style | Members (exact image in the canonical style) |
|---|---|---|
torsion |
dihedral periodic |
dihedral charmm (w = 0), opls, multi/harmonic, nharmonic, harmonic, class2, rb; improper cvff, periodic |
bond |
bond harmonic |
bond class2 (k3 = k4 = 0) |
angle |
angle harmonic |
angle class2 (k3 = k4 = 0), angle charmm (k_ub = 0) |
lj |
pair lj/cut |
pair lj/class2 = lj/cut with n = 9, m = 6, σ′ = (⅔)^⅓ σ (σ of lj/class2 is the minimum, of lj/cut the zero) |
No codec, by design: improper harmonic and morse are in no family's
function space; pair lj/charmm always switches (its kernel requires
inner < cutoff), so it is never exactly lj/cut. The canonical torsion keeps
the constant as a periodicity-0 term, so canonical() is exact on energy,
not only on forces. Impropers share the torsion algebra (their φ is the same
signed dihedral) but stay impropers: the canonical style is a dihedral style,
and moving them would change which block prices them; to_form("improper",
"periodic") is the improper conversion.
Three operations, one job each, each returning a new force field:
ForceField.canonical()maps every style of a family in the canonical style's category onto it, rows in their canonical spelling (forperiodic: one term per order, k > 0, γ ∈ (−180°, 180°], the constant term first). Styles of no family stay, in their place; idempotent.ForceField.to_form(category, style)converts every style ofcategoryinstyle's family tostyle, through the canonical parameters, exactly or not at all. A refusal names the source style, the type and the condition:dihedral periodic type 'CT-CT-CT-CT' has no exactdihedral rbform: sin(2φ) coefficient 0.5 ≠ 0 ….ForceField.fit_form(category, style, metric)is the projection that always answers: each row of every other style ofcategoryis fitted tostyleby least squares under a declared metric — sample points q of the coordinate (r; θ, φ in radians) with weights w, optionally Boltzmann factors exp(−(E − E_min)/kT) of the row's own energy, optionally a free constant offset — minimising Σᵢ wᵢ (E_fit(qᵢ) + c − E_src(qᵢ))². It sees every style only through its energy as the registry's kernel prices it, so it works for expression and Python styles as for native ones. A row in the target's image is converted exactly (exact); otherwise the fit starts at the codec's seed (else the source's same-named values) and varies the target's dimensioned parameters — a dimensionless one (a periodicity, a sign, a 1-4 weight) is a choice of form and stays. The residual is reported per row (sum_sq= the minimised Σ w r²,rms,max_abs, the offset);sum_sqis monotone in the metric: pointwise larger weights, or more points, never lower it.
import numpy as np
canonical = ff.canonical() # exact
rb = ff.to_form("dihedral", "rb") # exact, or ValueError naming why
phi = np.linspace(-np.pi, np.pi, 72, endpoint=False)
opls, residual = ff.fit_form("dihedral", "opls", phi, offset=True)
residual["rms"], residual["types"][0]["offset"]
A third party's style joins a family by registering a codec
(molrs::ff::style_registry::register_form(category, style, FormCodec::new(family,
embed, project))); from then on canonical, to_form and fit_form treat
it as a built-in. fit_form refuses a pair category (its unlike pairs follow
the style's mixing rule, which a per-row fit cannot hold).
OpenMM XML¶
OpenmmXmlReader (OpenmmXmlReader().read(source)) reads OpenMM's
<ForceField> — its own CHARMM36, AMBER and OPLS-AA ports and the foyer /
molpy packs — and OpenmmXmlWriter (OpenmmXmlWriter().write(sink, ff)) writes
the inverse, each
number in the shortest form that reads back to the same f64 unless a
precision is given. The IR's definitions are LAMMPS's; OpenMM's are
converted at the boundary:
| OpenMM | IR | Conversion |
|---|---|---|
<HarmonicBondForce><Bond length k> |
bond harmonic |
r0 = 10·length, k = k/(2·418.4) (OpenMM's ½k) |
<HarmonicAngleForce><Angle angle k> |
angle harmonic |
theta0 in degrees, k = k/(2·4.184) |
<AmoebaUreyBradleyForce><UreyBradley k d> |
angle charmm, joined with its angle row |
k_ub = k/418.4, r_ub = 10·d (Urey–Bradley) |
<PeriodicTorsionForce><Proper k_m periodicity_m phase_m> |
dihedral periodic |
k_m/4.184, phases in degrees; the writer also writes dihedral charmm (w = 0), harmonic and class2 here, term for term, constant included |
<PeriodicTorsionForce><Proper c0..c3> (CL&P / foyer) |
dihedral opls |
k_n = c_{n−1}/4.184 |
<PeriodicTorsionForce><Improper> |
improper periodic (one term) |
stored in the order OpenMM prices (Improper atom order) |
<RBTorsionForce><Proper c0..c5> |
dihedral multi/harmonic (C₅ = 0) or nharmonic (N = 6) |
Aₙ₊₁ = (−1)ⁿ Cₙ/4.184, constant included; the writer writes multi/harmonic, nharmonic (N ≤ 6) and opls here |
<CustomTorsionForce energy="k*(theta-theta0)^2"> <Improper> |
improper harmonic |
k/4.184; OpenMM's θ is signed and LAMMPS's χ = |φ|, which agree at theta0 = 0 only — another theta0 is refused (CHARMM36's two theta0 = π rows among them) |
<CustomTorsionForce energy="k*(abs(theta)-theta0)^2"> |
improper harmonic |
chi0 = theta0 in degrees (the writer's form when some chi0 ≠ 0) |
<CMAPTorsionForce><Map>, <Torsion map> |
cmap charmm |
CMAP |
<NonbondedForce coulomb14scale lj14scale><Atom charge sigma epsilon> |
pair lj/cut (mixing = the root's foyer combining_rule, else arithmetic) + pair coul/cut; charge on atom full; the writer writes coul/long/pme here too |
sigma × 10, epsilon ÷ 4.184; special_bonds [0, 0, scale] |
<LennardJonesForce lj14scale><Atom sigma epsilon [sigma14 epsilon14]>, <NBFixPair> |
pair lj/charmm (arithmetic, NBFIX as cross rows) + pair coul/charmm, the <NonbondedForce> beside it (its epsilon 0) giving the charges |
1-4: special_bonds [0, 0, lj14scale] / [0, 0, coulomb14scale], and one_four = "epsilon14" when a type's 1-4 parameters differ (1-4); an NBFIX row is OpenMM's 1-4 parameters for its pair too, LAMMPS's two-number cross row |
Rows key on class{n} or type{n} as written; OpenMM's wildcard (an empty
attribute) is "", and a row naming neither, which OpenMM ignores, is
refused. Every Coulomb style states OpenMM's constant (ONE_4PI_EPS0 =
332.06371329919216 kcal·Å/(mol·e²), 9.9·10⁻⁹ above LAMMPS real's
qqr2e); the writers do not carry it (each engine fixes its own). OpenMM's
cutoffs and switching are createSystem arguments, so no style read from a
file has a cutoff (or inner): lj/cut and coul/cut then price every
pair untruncated, as NoCutoff does (Cutoffs), and the
caller states inner and cutoff of lj/charmm / coul/charmm, which
have no default.
The Coulomb style is written as the charges alone, the long-range
coul/long/pme as coul/cut is: whether OpenMM sums them with NoCutoff, a
cutoff, Ewald or PME is createSystem's nonbondedMethod (with
nonbondedCutoff and ewaldErrorTolerance), chosen when the System is
built, as LAMMPS's kspace_style and GROMACS's .mdp coulombtype are. A
coul/long/pme that states its Ewald parameters (alpha, order,
grid_*) is refused: the XML has no place for them.
OpenMM's createSystem mixes every <NonbondedForce> pair
Lorentz-Berthelot, 1-4 exceptions included, and ignores foyer's
combining_rule; no tag of its schema states a geometric σ for both the
pairs and the 1-4 exceptions (a <CustomNonbondedForce> excludes whole
bonded neighbours and scales none; a <LennardJonesForce> mixes arithmetic
but for one <NBFixPair> per type pair). So a geometric lj/cut (OPLS-AA)
is written with the root's combining_rule="geometric" and a <Script>,
which createSystem runs last: it prices the Lennard-Jones in a
CustomNonbondedForce named GeometricLennardJones (√(σᵢσⱼ), √(εᵢεⱼ), the
NonbondedForce's cutoff, switch and dispersion correction, periodic under
CutoffPeriodic, Ewald and PME; LJPME is refused), keeps the charges
in the NonbondedForce at ε = 0, and gives each 1-4 exception σ = √(σᵢσⱼ)
at its lj14scale·√(εᵢεⱼ) — foyer's patch of the System. The reader reads
that script, and only that one, as mixing geometric. Such an XML is for
OpenMM's app.ForceField: a tool that applies combining_rule itself
(foyer) is given the source file.
Refused, by name: <Script> (but the writer's geometric-mixing one) /
<InitializationScript>, every
Custom*Force other than the harmonic improper, a <Proper> under it,
an RB <Improper>, ordering="smirnoff", a multi-term periodic improper,
an odd CMAP size, a wildcard Urey–Bradley row, an <NBFixPair> of a type
with itself, a <NonbondedForce> with non-zero epsilon beside a
<LennardJonesForce> (OpenMM prices both), and every other force (AMOEBA
multipoles, GBSA, Drude, …). A style outside the table that has an
expression is written as its category's Custom*Force (Engine
codecs). The writer refuses what OpenMM's tags cannot
hold: a style outside the table without an expression (dihedral
mmff_torsion, …),
dihedral charmm w ≠ 0, special_bonds other than [0, 0, s],
sixthpower mixing or geometric with cross rows, a cross row with its own
1-4 parameters, lj/charmm under one_four = "regular" with 1-4 parameters
of its own at a non-zero weight (OpenMM would use them), a Coulomb style
with dielectric ≠ 1 or delta ≠ 0, a coul/long/pme stating Ewald
parameters, charges on some atom types only, a
shifted or Mie lj/cut, a force field in units other than real, and two
types OpenMM's generator would match on the same labels with other
parameters (bonds, angles, propers and crossterms either way round,
impropers by their centre and the other three in any order; a proper's
periodic and RB rows on one quartet count, since OpenMM adds both) — the
same row twice is written once. A type without a class (a prmtop's, a
LAMMPS file's) is written as its own class, which OpenMM requires of every
<Type>. <Residues>, <Patches> and <Info> carry no parameters and are
skipped; placeholder atom types the reader makes for classes are not written.
A force-field XML is typed: OpenMM's generators find each term from the
atom types of a residue template. A system written for OpenMM therefore
needs residue templates whose charges are the atoms' (the field written
without type charges, so <UseAttributeFromResidue name="charge"/>), and
its rows must be what the generators find: a dihedral row on four atoms
that are no bonded chain (OPLS-AA's impropers as GROMACS funct 1) is the
same function as improper periodic over the same atoms and goes to OpenMM
as one; an improper periodic row's two outer atoms must be in the order
OpenMM's AMBER rule puts them (by element, then index). The equivalence
check (Cross-engine equivalence) builds the
templates from the frame and holds OpenMM's energies to molrs's.
write_openmm_system(path, ff, frame) (write_openmm_system_str; Rust
OpenmmXmlWriter::write_system_str) writes those templates: the field as
write_openmm_forcefield writes it, plus one <Residue> per distinct
residue of the frame. The residues and atom names are the ones OpenMM's
PDBFile reads from write_pdb of the same frame (one rule, shared by
both writers): runs of consecutive atoms with the same chain, res_id,
icode and res_name; without res_id, each bonded molecule is a residue
named MOL; an atom without a name is its element numbered within the
residue (C1, C2, H1, …). Each template lists <Atom name type
charge>, a <Bond> per bond inside the residue and an <ExternalBond> per
bond leaving it. Charges are the frame's charge column, under
<UseAttributeFromResidue name="charge"/> (without the column, the types'
own charges, which every type must then carry), and every atom type the
frame uses gets an element, which OpenMM matches residues by. So
ForceField(xml).createSystem(PDBFile(pdb).topology) builds the System
directly. Refused, by name: a type the force field lacks, one type on two
elements, two atoms of one residue with the same name, no charges anywhere.
Two residues with the same bond graph and elements but other types or
charges become two templates OpenMM's matcher cannot choose between
(createSystem then needs residueTemplates).
AMBER prmtop¶
AMBER is read, never written (no prmtop writer). The prmtop force-field
reader (AmberPrmtopForcefieldReader) and frame reader (read_prmtop) name
every type and row alike; a chamber prmtop (ParmEd's chamber, %FLAG
CTITLE) reads through the same pair.
| prmtop | IR |
|---|---|
BOND_* RK, ANGLE_* TK (no ½), radians |
bond harmonic, angle harmonic (angle charmm in a chamber file, see Urey–Bradley) |
DIHEDRAL_* PK, PN, phase; the rows of one quartet and each negative-PN chain are one torsion |
dihedral periodic k<m>, periodicity<m>, phase<m> (degrees), terms sorted by periodicity; a second distinct set of terms on one type quartet (tleap reuses a quartet's first match, so two torsions of one quartet can differ) is the type <quartet>@<n> |
| an improper (negative 4th pointer) | improper periodic in AMBER's order; a multi-term one (several rows, or a chain) is one frame row and one type <quartet>@<n> per term, as LAMMPS cvff holds one term |
| a phase within 0.004 rad of ±π | ±180° exactly, as sander's rdparm (tleap writes π as 3.14159400) |
CHARMM_IMPROPERS K_ψ (ψ − ψ₀)² |
improper harmonic k = K_ψ, chi0 = ψ₀, centre first; ψ₀ other than 0° / 180° refused (LAMMPS prices |ψ|) |
CHARMM_CMAP_* / CMAP_* |
cmap charmm, a type per map named by the five atom types (qualified @<residue> of the Cα when one name stands for two maps, as ff19SB's do); the frame's cmaps block |
LENNARD_JONES_ACOEF/BCOEF via ICO |
lj/cut (lj/charmm in a chamber file) self rows; a cross row where the entry is not Lorentz–Berthelot (NBFIX) |
LENNARD_JONES_14_ACOEF/BCOEF (chamber) |
lj/charmm epsilon14 / sigma14 (cross rows where not Lorentz–Berthelot) and one_four = "epsilon14" when the table differs from the regular one |
CHARGE |
÷ 18.2223, coul/cut at 332.0522173; ÷ √332.0716 and coul/charmm at 332.0716 in a chamber file |
SCEE_SCALE_FACTOR / SCNB_SCALE_FACTOR per torsion type |
special_bonds 1-4 = 1/divisor most 1-4 rows carry; the frame AmberPrmtopForcefieldReader::read_system (Python AmberPrmtopForcefieldReader().read_system(source)) returns has pairs giving every 1-4 pair weighted otherwise its coul_scale / lj_scale |
AMBER_ATOM_TYPE |
the type name; <name>~<class> where one name stands for two LJ classes or masses (a chamber file cuts CHARMM's types to four characters) |
sander prices a 1-4 pair once per proper row whose 3rd pointer is not
negative, at that row's 1/SCEE, 1/SCNB (never an improper's, whatever its
pointer). The field's weights are the divisors most such rows carry; the
frame reader's pairs block lists the pairs whose summed weight differs —
another divisor (GLYCAM's 1.0 beside ff14SB's 1.2 / 2.0), a pair two rows
list, a 1-4 pair of the topology no row lists (weight 0, or 1 if the
exclusion list leaves it out) — with only the differing cells set.
intramolecular_pairs keeps those cells when it builds the full pair list.
A chamber file's 1-4 Lennard-Jones (one_four = "epsilon14") reaches a
frame through ForceField.materialize_one_four. Build the full list first
(intramolecular_pairs, which keeps the frame reader's cells), then
materialize: materialize_one_four builds a list only when the frame has
no pairs, and the frame reader's pairs hold only the odd 1-4 pairs.
sander clamps the cosine of a harmonic angle to ±0.999 before taking its
arccosine, so it prices an angle beyond 177.44° as 177.44°; LAMMPS and molrs
price the IR's k (θ − theta0)² at the angle itself. A prmtop with a
near-linear angle (an sp carbon, θ₀ ≈ 180°) therefore prices its angle term
differently in sander, and only there.
Refused by name: polarizable (IPOL > 0), 12-6-4 (LENNARD_JONES_CCOEF),
non-zero 10-12 (HBOND_ACOEF/BCOEF, a negative ICO), perturbed, solvent-cap
and IFBOX = 3 files; a 1-4 row on a negative-PN chain (sander prices the
pair once per chained term, and its Coulomb at a factor unlike any other 1-4
pair's); a 1-4 row on a bonded or angle-end pair; two terms of one improper
with one periodicity; a Urey–Bradley term on no angle or on several.
GROMACS topologies¶
GromacsTopForcefieldReader reads a topology's directives into a force field
(read) or a whole .top into the force field and a typed frame
(read_system; Python molrs.io.gromacs.GromacsTopForcefieldReader(include=True).read_system(source));
the writer (GromacsTopForcefieldWriter) is the inverse of the directive map
(write) and of read_system (write_system). Every row is
exact; GROMACS's ½k forms are halved into LAMMPS's K, nm → Å, kJ → kcal,
degrees stay degrees. Every Coulomb style states GROMACS's own constant
(molrs.core.constants.GROMACS_ONE_4PI_EPS0, its ONE_4PI_EPS0 from
CODATA 2018, 138.93545764438196 kJ·nm/(mol·e²), which the unit registry
converts to 332.06371329919205 kcal·Å/(mol·e²): 9.9·10⁻⁹ above LAMMPS
real's).
| GROMACS | IR |
|---|---|
[ defaults ] comb-rule 2 / 3 |
mixing arithmetic / geometric on the Lennard-Jones style (comb-rule 1, C6/C12, refused) |
[ defaults ] gen-pairs, fudgeLJ, fudgeQQ |
special_bonds lj [0, 0, fudgeLJ], coul [0, 0, fudgeQQ] (gen-pairs no: lj 1-4 = 1, no pair is generated) |
[ atomtypes ] V W |
atom full type + Lennard-Jones self row sigma = 10·V, epsilon = W/4.184 |
[ nonbond_params ] funct 1 |
explicit cross row |
[ pairtypes ] funct 1 |
lj/charmm + coul/charmm, one_four = "epsilon14": epsilon14 = ε/fudgeLJ, sigma14 = σ on the self rows, and cross rows where the mix of the self rows would not give the pair GROMACS's 1-4 parameters (to 10⁻¹²) — so special_bonds × LJ(ε₁₄, σ₁₄) is GROMACS's 1-4 energy for every type pair. A pairtype equal to the generated pair changes nothing (lj/cut stays) |
[ bondtypes ] funct 1 / 3 |
bond harmonic (k = k_b/2) / bond morse |
[ angletypes ] funct 1 / 5 |
angle harmonic / angle charmm (Urey–Bradley) |
[ dihedraltypes ] funct 1 |
dihedral periodic, one term |
[ dihedraltypes ] funct 9 |
dihedral periodic: the consecutive rows on equal labels are the terms k<m>, periodicity<m>, phase<m> of one type, in file order |
[ dihedraltypes ] funct 3 (RB) |
dihedral multi/harmonic aₙ₊₁ = (−1)ⁿ Cₙ, constant included; dihedral nharmonic (N = 6) when C₅ ≠ 0 |
[ dihedraltypes ] funct 5 (Fourier) |
dihedral opls, kₙ = Cₙ |
[ dihedraltypes ] funct 2 |
improper harmonic, K = k_ξ/2, chi0 = ξ₀ ∈ {0°, 180°} (the signed GROMACS form equals K(|φ| − chi0)² there and nowhere else, so other ξ₀ are refused); atoms as written |
[ dihedraltypes ] funct 4 |
improper periodic; atoms as written (AMBER order) |
[ cmaptypes ] funct 1 |
cmap charmm, the grid unchanged in layout (CMAP) |
X |
the empty wildcard |
The writer writes dihedral periodic with several terms as consecutive
funct-9 rows, multi/harmonic and nharmonic (N ≤ 6) as funct 3, opls as
funct 5, dihedral harmonic / improper cvff and dihedral charmm with
w = 0 as periodic rows at phase 0° / 180°, cmap charmm as [ cmaptypes ],
and the 1-4 parameters of an lj/charmm declared one_four = "epsilon14"
as [ pairtypes ] exactly where GROMACS would generate other ones. It
refuses dihedral charmm with w > 0 (GROMACS prices a 1-4 pair by
[ pairs ], never by a dihedral), epsilon14 / sigma14 on an lj/charmm
that does not price 1-4 pairs with them, two types GROMACS would read as
one (same labels, same function-code table), and a force field in units
other than real. dihedral class2's torsion goes out as funct-9 rows at
phase φₙ + 180° (k[1 − cos(nφ − φₙ)] = k[1 + cos(nφ − φₙ − 180°)]). No
Coulomb constant is written: GROMACS prices at its own, as LAMMPS and OpenMM
do at theirs. No Coulomb style has a directive either — coul/cut,
coul/charmm and the long-range coul/long/pme alike: the .top holds the
charges, and how GROMACS sums them (coulombtype = PME, Ewald, cut-off,
with rcoulomb, fourier-spacing, pme-order, ewald-rtol) is the
.mdp's, chosen at run time. A coul/long/pme stating its Ewald
parameters (alpha, order, grid_*) is refused.
Systems. read_system types each molecule row as GROMACS's own lookup
does — bonds, angles, Fourier dihedrals and cmaps by exact bond types
(cmaps forward only), the other dihedrals by the first row with the most
non-wildcard matches, either way, with all of a funct-9 row's terms; a row
with parameters of its own (an OPLS-AA improper_* macro, expanded) gets a
type of its own, <labels>@gmx_<n>. Its frame holds atoms, bonds,
angles, dihedrals (funct 1, 9, 3, 5), impropers (funct 2, 4), cmaps,
constraints ([ constraints ], [ settles ]; r0 in Å), exclusions
and pairs: every pair GROMACS prices — per molecule, and every pair of
two molecules (up to MAX_ATOMS_FOR_A_FULL_PAIR_LIST atoms; above it a
neighbour list, compile_typed, finds those) — the [ pairs ] rows
flagged is_14. A [ pairs ] row with parameters carries them as per-pair
overrides — funct 1 sigma, epsilon, lj_scale 1, coul_scale fudgeQQ;
funct 2 also charge_product and its own fudgeQQ — which LAMMPS cannot hold.
Virtual sites, restraints, polarization and free-energy B states have no IR
form and are refused by name. A system whose field reads with
one_four = "epsilon14" (CHARMM's pairtypes) compiles once its 1-4 pairs
are written out as per-pair rows, ForceField.materialize_one_four(frame).
write_system(sink, ff, frame) writes the directives (no bonded
[ *types ] tables: each row carries its type's parameters on its line,
one funct-9 line per periodic term, so no lookup can pick another type;
[ cmaptypes ] stays, and a crossterm must be one GROMACS's lookup finds)
and one [ moleculetype ] (nrexcl 3) per molecule — a bond-graph
component, a run of consecutive atoms — with [ atoms ] (each atom's
charge and mass), [ bonds ], [ pairs ], [ angles ], [ dihedrals ]
(propers and impropers), [ cmap ], [ exclusions ] and [ constraints ],
then [ molecules ]. [ pairs ] lists the frame's 1-4 pairs: funct 1, with
σ ε when only the override cells epsilon / sigma differ from the
generated pair, else funct 2 fudgeQQ qᵢqⱼ 1 σ lj_scale·ε — so every
override cell holds. [ exclusions ] holds each pair of a molecule beyond
three bonds the frame does not price, so GROMACS prices exactly the
frame's pairs (built by intramolecular_pairs when absent). Refused by
name: a priced pair within three bonds that is no 1-4 pair (or a 1-4 pair
beyond them), override cells without epsilon and sigma, a crossterm
GROMACS would give another grid, a molecule whose atoms are not
consecutive or a row across two molecules, and every force-field refusal
above. A topology read_system reads from it is the system: written again,
it is the same file.
GAFF and GAFF2¶
GaffTypifier (Python molrs.ff.typifier.GaffTypifier(parameter_set=
"gaff" | "gaff2")) matches a molecule whose atoms carry GAFF types —
AtdTypifier with the same parameter_set stamps them — against the
gaff.dat / gaff2.dat tables compiled into molrs (GAFF 1.81, GAFF2 2.2.30,
AmberTools 26.1), and writes the IR: atom full (mass), pair lj/cut
(σ = 2·R*/2^(⅙), ε), pair coul/cut at AMBER's 332.0522173,
bond harmonic and angle harmonic (k = K, θ₀ in degrees),
dihedral periodic (k<m> = PK/IDIVF), improper periodic (AMBER order,
centre third) and special_bonds ½ / ⅚ (SCNB 2, SCEE 1.2). Charges are not
a GAFF parameter: a charge model writes atoms.charge.
What the table lacks is estimated as AmberTools estimates it, and each
estimate carries estimated, estimate_penalty, estimate_method and
estimate_analog:
- Torsions follow parmchk2's
chk_torsion: equivalent-type rows, the wildcard rowX-j-k-X(a parameter, not an estimate), equivalent-type wildcard rows, then the cheapest corresponding-type row and wildcard row, scored as parmchk2 scores them. - Impropers follow parmchk2's
chk_improper(estimates at the centresPARMCHK.DATflags as planar) and then tleap: an improper wherever tleap finds a row for a triple of an atom's neighbours, its atoms in the order tleap gives them. - Bonds and angles follow parmchk2's
chk_bond/chk_angle: equivalent-type rows, the cheapest corresponding-type row (an angle withinTHRESHOLD_BA), then, for an angle, Wang's empiricalK_θand the mean θ₀ of theA-B-AandC-B-Crows, for its own, equivalent and corresponding types. A corresponding type scores parmchk2's columns:bl+blfat a bond end,ba+bafat an angle end, (cba+cbaf)·WEIGHT_BA_CTRat its vertex (caffeine'sc-cc-naisc2-cc-naat 2.6).
An estimate's numbers are the frcmod's, at the precision parmchk2 prints
them (a bond's r₀ to three decimals: ne-s6 copies ne-sy's 1.6723 and
prices 1.672), since that is what tleap reads.
Every term is named by its types alone, as the prmtop reader names it: a
bond, angle or torsion in the orientation that reads smaller, type by type
(c3-c3-oh-ho, where gaff.dat writes ho-oh-c3-c3), its cosine terms
numbered by periodicity, then phase; an improper in AMBER's order. An
estimate is named the same way (f-c3-sy) and says what it is in its
params. So the native force field of a molecule and the one
read_prmtop_forcefield reads from tleap's prmtop of it merge
(ForceField.merge) into one, type for type.
parmchk2 searches the molecule in atom and bond order and reuses a name's
first estimate (an improper estimate is itself a row later impropers can
copy), so a name can be estimated differently in two molecules (aspirin's
ester carbon takes c3-o -c -oh, ethyl acetate's the X -X -c -o amide
term). Typed into one output, the first keeps the name and each further
estimate of it is <types>@<n> (c3-o-c-os@2), as the prmtop reader
numbers a further term set on one quartet.
Which bond orders the types follow. antechamber (its default -j 4)
ignores the bond orders of its input: bondtype -j full judges new ones from
the connectivity — each atom's penalty per valence (APS.DAT), valence states
tried in order of total penalty, and for each a search that settles forced
bonds and tries the first undecided bond in file order single, double, then
triple — and the atom types follow that structure. AtdTypifier does the
same by default (bond_orders=BondOrderSource.Connectivity; Rust
BondOrderSource::Connectivity):
it types a molecule as antechamber types the mol2 file holding the same atoms
and bonds in the same order, whatever orders the molrs graph carries. Where a
molecule has more than one Kekulé structure (azulene, cyclooctatetraene,
polycyclic aromatics) the structure — and with it the cc / cd, ce /
cf, nc / nd, cp / cq colouring — is antechamber's, and is decided
by that order, as antechamber's is. Two things still read the input's bond
types, because bondtype reads them from its file: which rings count as
aromatic for the 7 / 8 bond types (a quinone drawn with its C=O is not; read
from a connectivity-only file, it is), and a molecule no valence state closes
(tropylium), where antechamber keeps the file's bonds. Every hydrogen must be
drawn. bond_orders=BondOrderSource.Stated keeps the graph's own orders instead (aromatic
bonds kekulized): a cyclooctatetraene drawn C1=CC=CC=CC=C1 then types cc
cd cd cc …, the structure it was drawn with, where antechamber answers cc cc
cd cd …. assign_bond_orders writes the judged structure onto a
molecule, in place. (BondOrderSource is molrs.perceive.BondOrderSource;
assign_bcc_bond_types(mol, source) takes the same choice.)
The rest follows antechamber as well: rings and ring classes (AR1 …
AR5, RG*, NR) are its own (ring.c: every chordless ring of up to ten
atoms, the AM1-BCC indole rule under bcc / abcg2), not a smallest set of
smallest rings, and the colouring is its two post-typing passes (atadjust
for cc / ce / cg / nc / ne / pc / pe, swept in bond order;
cpadjust for the biphenyl bridge carbon, so o-terphenyl's middle ring is
cp … cq).
Checked against AmberTools 26.1:
- antechamber + parmchk2 + tleap + sander on 127 molecules (neutral and
charged; aromatic, heteroaromatic, conjugated, strained, S / P / B /
halogen chemistry) under both sets, with antechamber's atom types: every
bond, angle, torsion and improper row (atoms, atom order, every parameter),
every estimate's analog and penalty (all 634 frcmod rows), every atom's
σ / ε, and every energy term at perturbed coordinates — bond, angle,
dihedral with impropers, 1-4 and other van der Waals and Coulomb — agree.
The angle energy agrees to the 4·10⁻⁷ by which tleap's π (3.141594) moves
θ₀, except at a near-linear angle: sander clamps cos θ to ±0.999 (θ never
above 177.44°), LAMMPS and molrs do not (phenylacetylene's
c1/cgangles, 0.055 kcal/mol). - parmchk2 on 800 type graphs built to need bond and angle estimates: every
bond and angle it writes — parameters, analog, penalty, and every
ATTN(a missing term here) — agrees. AtdTypifieragainst antechamber's atom types, on the mol2 files antechamber read (connectivity only): identical on all 127 (and on phenylboronic acid, which antechamber types but cannot charge) under all seven-attables (gaff,gaff2,bcc,abcg2,amber,sybyl,gas), with the bond typesbondtype -j fullwrites identical bond for bond — and likewise on 173 further molecules built to stress the Kekulé search and the ring perception (polycyclic and non-alternant aromatics, annulenes, terphenyls, heteroaromatics, cations, anions and zwitterions, N-oxides, azides, molecules no valence state closes, drug-like molecules).
A term neither the table nor parmchk2's search reaches is an error where
parmchk2 writes a zero marked ATTN, need revision.
Parameters as frame columns¶
ForceField.materialize_params(frame, prefix=…) (Rust
ForceField::materialize_params) writes the parameters this force field
gives each row of a typed frame next to the row, as columns
<prefix><parameter>:
| Block | Rows priced by | Columns (lj/cut + harmonic + periodic field) |
|---|---|---|
bonds, angles, dihedrals, impropers, cmaps |
the type its type names, under the one style of the category that defines it |
k, r0; k, theta0; k<m>, periodicity<m>, phase<m>; k, periodicity, phase |
atoms |
atoms.type: every atom style's type, every pair style's self row |
mass; epsilon, sigma |
Nothing in it is per style: the columns are the numeric parameters the
field stores for each type, in the IR's units (degrees, K without a ½, the
field's units), so a style added to the IR is written by the same call. A
row whose type lacks a parameter another row's type has — a two-term torsion
beside a three-term one, estimate_penalty on an estimated term only — is a
null cell (Block.validity(column)), not a zero. String and array
parameters, a pair style without per-type rows (coul/cut) and cross rows
(NBFIX, which have no per-atom form; the style's mixing combines the self
rows) are not written; charges are frame data unless the field stores them
per type. A column already present under a written name is replaced. It
returns block → columns written, and refuses a row whose type no style (or
two styles) of its category defines.
The use it is for is a reference field: type a molecule with one field and read another's parameters beside the same rows — e.g. GAFF2's bonded terms and Lennard-Jones as the reference of a learned Class-I field,
labelled = molrs.ff.typifier.AtdTypifier(parameter_set="gaff2").typify(mol)
gaff2 = molrs.ff.typifier.GaffTypifier(parameter_set="gaff2")
frame = gaff2.typify(labelled).to_frame()
gaff2.forcefield().materialize_params(frame, prefix="gaff2_")
frame["bonds"]["gaff2_k"], frame["atoms"]["gaff2_sigma"]
Engine maps at a glance¶
| Engine | bond k |
angle k, theta0 |
phases | impropers |
|---|---|---|---|---|
| LAMMPS | K |
K, deg |
deg | as written |
GROMACS (.top/.itp) |
k_b/2, kJ→kcal, nm→Å |
k_θ/2, deg |
deg | as written |
| OpenMM XML | k/2, kJ→kcal, nm→Å |
k/2, rad→deg |
rad→deg | (c1..c4) ↔ (c2, c3, c1, c4); see OpenMM XML |
| AMBER prmtop | RK |
TK, rad→deg |
rad→deg (±π snapped) | AMBER order |
| chamber prmtop | RK |
TK, rad→deg (angle charmm) |
rad→deg | AMBER order; CHARMM_IMPROPERS centre first |
| AMBER frcmod (writer) | RK = k |
TK = k, deg |
deg | AMBER order |
| GAFF / GAFF2 tables | K |
K, deg |
deg | AMBER order |
OPLS-AA table (GROMACS oplsaa.ff) |
k_b/2 |
k_θ/2, deg |
— | — |
Each engine fixes its Coulomb constant, and each reader states its engine's
on the Coulomb style (coulomb, kcal·Å/(mol·e²)); no writer writes one, so
a field is priced by an engine at that engine's:
| Engine | Coulomb constant | Relative to LAMMPS real |
|---|---|---|
LAMMPS real (qqr2e) |
332.06371 | — |
OpenMM (ONE_4PI_EPS0, CODATA 2018) |
332.06371329919216 | + 9.9·10⁻⁹ |
GROMACS 2025 (ONE_4PI_EPS0, CODATA 2018, its own expression) |
332.06371329919205 | + 9.9·10⁻⁹ (one ulp below OpenMM's) |
| AMBER (charges × 18.2223) | 332.05221729 | − 3.5·10⁻⁵ |
| CHARMM (chamber prmtop) | 332.0716 | + 2.4·10⁻⁵ |
Engine codecs¶
Engine I/O follows the IR's protocol (molrs::ff::ir::{Engine, EngineCodec}), not a table
per writer: a style registered with a spec reads and writes in every engine
that can hold it with nothing else written, and is refused by name —
IrError::NoEngineForm { engine, category, style, reason } — by every
engine that cannot.
LAMMPS: the style's LammpsForm¶
A StyleSpec carries lammps: LammpsForm:
Positional(LammpsForm::positional(), orpositional_named("fene")for another LAMMPS name) is derived from the spec:<category>_style <name>(pair_style <name> <cutoff>),<category>_coeff <type> v₁ … vₙinparamsorder,mixingaspair_modify mix. Each value is converted by itsParamDimensionfrom the force field's units to the file's —E/L^2by energy/length²,E*L^6by energy·length⁶, an angle value (exactlyA) and a per-radian constant not at all — and a dimensionless integral value is written as an integer (LAMMPS reads multiplicities and signs withinumeric). Reading is the inverse: exactlynnumbers, else an error naming the line and the missing parameter. A positional form refuses, at registration, a Text, Array or indexed parameter, a style parameter other thancutoff/mixing, and a category with no*_stylecommand; at write, a row without a value and no default.Custom(Arc<dyn LammpsCodec>): a codec of its own where the LAMMPS line is not positional.None: refused by name; an expression-only style because the installed LAMMPS has noLEPTONpackage.
| Built-in | LAMMPS form |
|---|---|
bond harmonic, morse, class2; angle harmonic, charmm; dihedral opls, multi/harmonic; improper harmonic; pair buck, morse |
positional |
dihedral periodic |
fourier: m K1 n1 d1 … (the term count, indexed parameters) |
dihedral nharmonic |
N A1 … AN |
dihedral charmm |
K n d w, n and d integers |
dihedral harmonic, improper cvff |
K d n, d = ±1 and n integers |
improper periodic |
cvff K d n, φ₀ = 0° / 180° as d = ±1 (it reads back as improper cvff, the same energy) |
angle class2, dihedral class2 |
class2 and its cross-term lines (bb, ba; mbt, ebt, at, aat, bb13) at zero; a data file's BondBond Coeffs, … sections; a non-zero cross term is refused on read |
pair lj/cut |
epsilon sigma; shift as pair_modify shift yes; only 12-6 |
pair lj/charmm + coul/charmm |
lj/charmm/coul/charmm inner outer [inner2 outer2], epsilon sigma epsilon14 sigma14 |
pair lj/class2 |
epsilon sigma, mixing always sixthpower (LAMMPS's init_one) |
pair coul/cut, coul/long/pme |
the Coulomb half of lj/cut/coul/cut / lj/cut/coul/long (or of a hybrid/overlay with another style, the Coulomb on * *); delta ≠ 0, dielectric ≠ 1 and stated Ewald parameters refused |
cmap charmm |
fix cmap and its grid file |
drude harmonic |
the DRUDE package's core-shell spring: bond_style harmonic K 0 (K = k, both unhalved, r0 = 0); the data writer writes the drudes rows as Bonds rows of the same labels. alpha and thole go to a comment of the include, for the input's pair_style thole / fix drude |
dihedral rb, MMFF, UFF, thole, coul/tt |
none |
The reader resolves a LAMMPS style name through the registry
(Registry::lammps_style): the style of that name when its form writes it,
else the one style whose form does (fourier is dihedral periodic). A
run-time category, which LAMMPS has no *_style for, is refused by the
writer rather than dropped. Readers and writers read the process-wide
registry, or the one with_registry gives them.
Registering a LAMMPS form:
use molrs::ff::ir::{ParamDimension, LammpsForm, ParamSpec, StyleSpec};
use molrs::ff::style_registry::register_style;
let fene = StyleSpec::new("bond", "fene")
.params(vec![
ParamSpec::new("k", "E/L^2".parse().unwrap()),
ParamSpec::new("r0", ParamDimension::LENGTH),
ParamSpec::new("epsilon", ParamDimension::ENERGY),
ParamSpec::new("sigma", ParamDimension::LENGTH),
])
.expression("-0.5*k*r0^2*log(1-(r/r0)^2)+step(2^(1/6)*sigma-r)*(4*epsilon*((sigma/r)^12-(sigma/r)^6)+epsilon)")
.lammps(LammpsForm::positional());
register_style(fene, None)?;
from molrs.ff import ir, style_registry
FENE = ("-0.5*k*r0^2*log(1-(r/r0)^2)"
"+step(2^(1/6)*sigma-r)*(4*epsilon*((sigma/r)^12-(sigma/r)^6)+epsilon)")
style_registry.register_style("bond", "fene", params={"k": "E/L^2", "r0": "L",
"epsilon": "E", "sigma": "L"}, expression=FENE, lammps="positional")
style_registry.register_engine_form("lammps", "bond", "other", "positional:fene") # afterwards
[s.lammps for s in style_registry.styles("dihedral") if s.name == "periodic"] # ['custom:fourier']
register_engine_form(engine, category, style, form) gives a style
registered without one its form; another engine is NoEngineForm (OpenMM
writes an expression style from its expression, GROMACS and AMBER hold
built-in styles only), a built-in Sealed.
OpenMM XML: Custom*Force of the expression¶
A style with no native tag but an expression — registered, built in
(bond morse, bond class2, angle class2, improper cvff, …), or an
unregistered instance's own — is written as its category's custom force.
The parameters stay in IR units (PerBondParameter, …, style parameters
as GlobalParameters); the expression is rewritten, exactly, through the
expression printer:
| Category | Force | Rewrite |
|---|---|---|
bond (r) |
CustomBondForce |
4.184*(E[r → 10*r]) |
angle (theta) |
CustomAngleForce |
4.184*(E) |
dihedral (phi) |
CustomTorsionForce <Proper> |
phi → theta |
improper (phi, chi) |
CustomTorsionForce ordering="charmm" <Improper>, the row's order (its centre first) |
phi → theta, chi → abs(theta) |
| compound (a run-time category, or points in the expression) | CustomCompoundBondForce, built by a <Script> over OpenMM's bonds, angles or propers whose types match a row either way round |
distance(…) → 10*distance(…); r, theta, phi as their functions of the points |
| pair | CustomNonbondedForce bondCutoff from special_bonds ([0,0,0] → 3, [0,0,1] → 2, …) |
r → 10*r; a bare parameter → its mixing rule of x1, x2; q1, q2 → per-particle charge1, charge2 |
OpenMM's ForceField XML has no compound tag (an unknown tag is ignored, its
energy lost), so the compound force is a <Script> OpenMM's
createSystem runs. Refused: a style with no expression, an indexed
parameter, a wildcard where OpenMM would re-order an improper or a
compound row, a compound category that is no chain of 2 to 4 atoms, two
custom forces stating one global parameter at different values, and for a
pair a cross row, a parameter that does not mix (buck, morse), an atom
type without a row, and 1-4 weights a bondCutoff cannot state. Reading a
Custom*Force is refused, but for the two harmonic-improper forms.
GROMACS, AMBER¶
The GROMACS topology writer and the frcmod writer hold the built-in styles
they have directives (sections) for; every other style — built in, or
registered at run time, or an instance's expression — is NoEngineForm
("not a built-in style: GROMACS holds the built-in styles it has
directives for"). The GROMACS and prmtop readers produce built-in styles
only (GROMACS topologies, AMBER prmtop).
Checked against the engines¶
ff::engine_codec_check (scripts/ff_engine_codec_check.sh, pinned in
molrs/src/ff/testdata/engine_codecs/engines.tsv): files molrs writes,
priced by LAMMPS run 0 and OpenMM 8.6.1 (Reference, NoCutoff, one
force group per term) at two configurations each, against molrs's energy:
| Case | Engine | What | Worst relative difference |
|---|---|---|---|
fene |
LAMMPS | a run-time bond fene, positional |
4·10⁻¹⁶ |
smooth |
LAMMPS | a run-time pair lj/smooth/linear, positional, pair_modify mix arithmetic |
1·10⁻¹⁶ |
metal |
LAMMPS | built-in bond morse + pair buck of a real field, written in metal (conversion per dimension) |
2·10⁻¹⁶ |
morse |
LAMMPS | built-in pair morse, a row per pair |
0 |
openmm |
OpenMM | bond fene, bond morse, angle class2, improper cvff, a run-time compound urey_bradley (<Script>), pair lj/smooth/linear (bondCutoff="3") |
2·10⁻¹⁵ |
The installed LAMMPS has no CLASS2 package: the class2 codecs are held by
read → write → read identity, molrs's pricing of both sides, and the
cross-term refusals instead (every_builtin_round_trips_through_an_include_at_the_same_energy,
class2_cross_terms_are_written_at_zero_and_refused_otherwise).
Cross-engine equivalence¶
One molecule per force-field family, read from its native format into the
IR, written by molrs to every engine format that can hold it, and priced by
every engine — the source's own included — at three configurations
(ff::equivalence_check, scripts/ff_equivalence_check.sh):
| Source | Family, molecule | Native format (engine) |
|---|---|---|
ff14sb |
AMBER ff14SB, ACE-PHE-NME | prmtop (sander) |
gaff2 |
GAFF2, the modXNA DMA fragment | prmtop (sander) |
chamber |
CHARMM36 (Urey–Bradley, CHARMM impropers, CMAP, 1-4 table), alanine dipeptide + α-D-glucose | chamber prmtop (sander) |
charmm36 |
CHARMM36 (Urey–Bradley, harmonic impropers, CMAP, sigma14/epsilon14, an NBFIX row), ACE-ALA-NME |
OpenMM XML (OpenMM) |
oplsaa |
OPLS-AA (RB, geometric mixing, funct-1 impropers), ACE-ALA-ALA-NME | GROMACS .top (GROMACS) |
Each source's IR is written as a LAMMPS data file and include, an OpenMM
<ForceField> XML (with residue templates built from the frame) and a
GROMACS topology (write_system); each engine prices each file and the
source itself — LAMMPS run 0; OpenMM 8.6.1 Reference, NoCutoff, one
force group per term; GROMACS 2025.3 double precision, mdrun -rerun,
plain cut-off past every pair; pysander (AmberTools 26.1, cut = 999) —
at the source's coordinates plus a seeded 0.05 Å Gaussian, on the 0.01 Å
grid a .gro holds exactly. Terms: bond, angle (with Urey–Bradley),
dihedral, improper, CMAP, van der Waals and Coulomb (each with its 1-4
pairs), the total, and the forces as ΣF·v (v a seeded Gaussian) and Σ|F|².
Every number is held to molrs's energy of the IR form that engine read,
its Coulomb at that engine's own constant (the energy is linear in it; the
constants are in Engine maps at a glance).
Where an engine reads a field in another exact form, molrs writes that
form, and prices it as the source to 10⁻¹² (each_engine_form_prices_as_the_source):
CHARMM's 1-4 table goes to LAMMPS as special_bonds 0 and one zero-K
dihedral charmm row of w = 1 per 1-4 pair; to OpenMM, OPLS-AA's funct-1
impropers (dihedral rows on atoms that are no bonded chain) go as the
improper periodic they are, and charges per atom in the residue
templates.
The worst error over the three configurations and every term — relative to the term, or to 1 kcal/mol for a term below it — and of the forces (ΣF·v to |F||v|, Σ|F|² relative), each engine against molrs:
| Source | native | LAMMPS | OpenMM | GROMACS |
|---|---|---|---|---|
ff14sb |
E 2·10⁻⁹ ᵃ, F 5·10⁻⁹ | E 1·10⁻¹⁴, F 2·10⁻¹⁵ | E 6·10⁻¹⁵, F 9·10⁻¹⁵ | E 1·10⁻¹¹, F 2·10⁻¹¹ |
gaff2 |
E 2·10⁻¹⁰ ᵃ, F 8·10⁻¹² | E 4·10⁻¹⁵, F 5·10⁻¹⁶ | E 5·10⁻¹⁵, F 6·10⁻¹⁶ | E 1·10⁻⁹ ᵇ, F 4·10⁻¹⁰ |
chamber |
E 6·10⁻¹⁴, F 4·10⁻¹⁵ | E 9·10⁻¹⁴, F 6·10⁻¹⁶ | E 8·10⁻⁸ ᶜ, F 5·10⁻¹¹ | E 1·10⁻⁹ ᵇ, F 5·10⁻⁹ |
charmm36 |
E 2·10⁻¹², F 1·10⁻¹³ | E 9·10⁻¹⁴, F 2·10⁻⁵ ᵈ | E 2·10⁻¹², F 1·10⁻¹³ | E 5·10⁻⁹ ᵇ, F 6·10⁻¹⁰ |
oplsaa |
E 1·10⁻⁹ ᵇ, F 2·10⁻⁹ | E 1·10⁻¹⁵, F 2·10⁻¹⁷ | E 1·10⁻¹⁴, F 9·10⁻¹⁵ | E 1·10⁻⁹ ᵇ, F 2·10⁻⁹ |
Every exact path is within 10⁻¹¹ but where the engine computes something else, each below the acceptance bar of 10⁻⁶:
- ᵃ sander reads the prmtop's eight-digit
LENNARD_JONES_ACOEF/BCOEFentries; molrs and the other engines mix the self terms (van der Waals). - ᵇ GROMACS prices 1-4 pairs from cubic-spline tables (≈10⁻⁹ kcal/mol on the 1-4 van der Waals and Coulomb).
- ᶜ OpenMM takes its CMAP node slopes from periodic splines, the IR (LAMMPS
fix cmap) from natural splines over the doubled map: 8·10⁻⁸ kcal/mol on a CMAP term of −0.026 kcal/mol. - ᵈ LAMMPS's
improper_style harmonicclamps sin χ at 0.001 in its force (improper_harmonic.cpp,SMALL), so its forces are not the gradient of its energy within 0.057° of planar; one configuration holds a CHARMM improper at χ = 4.7·10⁻⁴ rad. Its energies, and every other configuration's forces, are molrs's to 10⁻¹³.
The Coulomb constants differ by up to 3.5·10⁻⁵ (AMBER's against the others'),
which is not an error of any engine: each prices at its own, and the table
holds each to molrs at that constant. Every file written also reads back
into the IR it was written from — written again it is the same file, and
molrs prices it the same (every_written_file_reads_back_as_written) — and
every source's IR, and each engine's form of it, persists through a record.
The engines' numbers are pinned in molrs/src/ff/testdata/equivalence/
engines.tsv, so the check runs without the engines;
scripts/ff_equivalence_check.sh --pin reruns them on a compute node.
Completeness¶
Every style molrs registers a kernel for, and every field-level setting,
against every engine format molrs reads or writes and the record (molrec
v2): ✓ exact — priced, read or written as the IR means it, held by the
tests ff::completeness names (a numbered note bounds a ✓ or states the
condition it holds under); refused — an error naming the style or
setting, never a silent drop or an approximation; — the format has no such
thing. The table is generated: ff::completeness holds it with the tests
behind every cell, and fails when a registered kernel has no row, a row names
no kernel, a cited test does not exist, or this table differs from its
rendering (MOLRS_WRITE_COMPLETENESS=1 cargo mrs-test --
ff::completeness::the_guide_holds_the_generated_table rewrites it).
| Style or setting | kernel | LAMMPS read | LAMMPS write | OpenMM read | OpenMM write | GROMACS read | GROMACS write | prmtop read | molrec v2 |
|---|---|---|---|---|---|---|---|---|---|
bond harmonic |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
bond morse |
✓ | ✓ | ✓ | refused [1] | ✓ | ✓ | ✓ | — | ✓ |
bond class2 |
✓ | ✓ [2] | ✓ [2] | — | ✓ [3] | — | refused [4] | — | ✓ |
bond mmff_bond |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
bond uff_bond |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
angle harmonic |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
angle charmm |
✓ | ✓ | ✓ | ✓ [6] | ✓ | ✓ | ✓ | ✓ | ✓ |
angle class2 |
✓ | ✓ [2] | ✓ [2] | — | ✓ [3] | — | refused [4] | — | ✓ |
angle mmff_angle |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
angle mmff_stbn |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
angle uff_angle |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
dihedral periodic |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
dihedral charmm |
✓ | ✓ | ✓ | — | ✓ [7] | — | ✓ [8] | — | ✓ |
dihedral opls |
✓ | ✓ | ✓ | ✓ | ✓ [9] | ✓ | ✓ | — | ✓ |
dihedral multi/harmonic |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | — | ✓ |
dihedral nharmonic |
✓ | ✓ | ✓ | ✓ | ✓ [10] | ✓ | ✓ [11] | — | ✓ |
dihedral harmonic |
✓ | ✓ | ✓ | — | ✓ | — | ✓ | — | ✓ |
dihedral class2 |
✓ | ✓ [2] | ✓ [2] | — | ✓ | — | ✓ | — | ✓ |
dihedral mmff_torsion |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
dihedral uff_torsion |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
improper harmonic |
✓ | ✓ | ✓ [12] | ✓ [13] | ✓ [14] | ✓ [15] | ✓ [16] | ✓ | ✓ |
improper cvff |
✓ | ✓ | ✓ | — | ✓ [17] | — | ✓ | — | ✓ |
improper periodic |
✓ | — | ✓ [18] | ✓ | ✓ [19] | ✓ | ✓ | ✓ | ✓ |
improper mmff_oop |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
improper uff_inversion |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
cmap charmm |
✓ | ✓ | ✓ | ✓ [20] | ✓ [21] | ✓ | ✓ | ✓ | ✓ |
pair lj/cut |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
pair lj/charmm |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
pair coul/cut |
✓ | ✓ | ✓ [22] | ✓ | ✓ [23] | ✓ | ✓ [24] | ✓ | ✓ |
pair coul/charmm |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
pair coul/long/pme |
✓ | ✓ [25] | ✓ [26] | — | ✓ [27] | — | ✓ [28] | — | ✓ |
pair lj/class2 |
✓ | ✓ [29] | ✓ [29] | — | refused [30] | — | refused [4] | — | ✓ |
pair buck |
✓ | ✓ | ✓ | — | refused [31] | — | refused [4] | — | ✓ |
pair morse |
✓ | ✓ | ✓ | — | refused [31] | — | refused [4] | — | ✓ |
pair thole |
✓ | refused [32] | refused [32] | — | refused [32] | — | refused [32] | — | ✓ |
pair coul/tt |
✓ | refused [32] | refused [32] | — | refused [32] | — | refused [32] | — | ✓ |
pair mmff_vdw |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
pair uff_lj |
✓ | — | refused [5] | — | refused [5] | — | refused [5] | — | ✓ |
special_bonds |
✓ | ✓ | ✓ | ✓ [33] | ✓ [34] | ✓ | ✓ [34] | ✓ | ✓ |
per-pair overrides (pairs epsilon, sigma, lj_scale, charge_product, coul_scale) |
✓ | — | refused [35] | — | — | ✓ | ✓ | ✓ | ✓ |
lj/charmm one_four = "epsilon14" |
✓ | — | ✓ [36] | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
mixing arithmetic |
✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
mixing geometric |
✓ | ✓ | ✓ | ✓ [37] | ✓ [38] | ✓ | ✓ | — | ✓ |
mixing sixthpower |
✓ | ✓ | ✓ | — | refused [39] | — | refused [40] | — | ✓ |
cross rows (NBFIX) |
✓ | ✓ | ✓ | ✓ | ✓ [41] | ✓ | ✓ | ✓ | ✓ |
lj/cut shift |
✓ | ✓ | ✓ | — | refused [42] | — | refused [43] | — | ✓ |
lj/cut n, m (Mie) |
✓ | refused [44] | refused [45] | — | refused [46] | — | refused [47] | — | ✓ |
Coulomb constant |
✓ | ✓ | — | ✓ | — | ✓ | — | ✓ | ✓ |
cutoff, inner (switch) |
✓ | ✓ | ✓ | — | — | — | — | — | ✓ |
units presets (real, metal, lj) |
✓ | ✓ | ✓ | — | refused [48] | — | refused [48] | — | ✓ |
run-time style with a positional LAMMPS form |
✓ | ✓ | ✓ | refused [49] | ✓ | — | refused [50] | — | ✓ |
expression style without a LAMMPS form |
✓ | refused [51] | refused [52] | refused [49] | ✓ | — | refused [50] | — | ✓ |
run-time compound category (Urey-Bradley) |
✓ | — | refused [53] | — | ✓ [54] | — | refused [50] | — | ✓ |
- OpenMM has a Morse bond only as a CustomBondForce, which the reader refuses
- through its codec (cross-term lines at zero, a non-zero one refused); not run by LAMMPS: the installed lmp has no CLASS2 package
- the CustomBondForce / CustomAngleForce of its expression, rewritten exactly (held by OpenMM on bond fene, bond morse, angle class2)
- GROMACS has no directive of its form (NoEngineForm)
- molrs's own definition (a typifier's), no engine style
- a wildcard Urey–Bradley row is refused
- w = 0 (a periodic term); w ≠ 0 refused
- w = 0 (funct 9); w > 0 refused
- as RB, which reads back as multi/harmonic: the same series, constant included
- N ≤ 6 (RB's C0 … C5); above refused
- N ≤ 6; above refused
- LAMMPS clamps sin χ at 0.001 in its force: within 0.057° of planar its forces are not its energy's gradient
- CustomTorsionForce k(θ−θ0)² at θ0 = 0, or k(|θ|−θ0)²; another signed θ0 refused
- a wildcard endpoint refused (OpenMM would re-order the atoms)
- funct 2 at ξ0 ∈ {0°, 180°}; another ξ0 refused
- chi0 ∈ {0°, 180°}; another refused
- the CustomTorsionForce of its expression with ordering="charmm", which prices the row's dihedral, its centre first
- as cvff, at a phase of 0° or 180°; another refused
- OpenMM orders the two outer atoms it finds first by element and index (its AMBER rule); a system whose stored order differs prices another dihedral
- odd N refused; OpenMM takes its node slopes from periodic splines (≤ 10⁻⁷ kcal/mol off the IR's)
- as OpenMM read: its interpolation ≤ 10⁻⁷ kcal/mol off the IR's
- delta = 0 and dielectric = 1; the Coulomb constant is LAMMPS's own
- the Coulomb constant is OpenMM's own
- the Coulomb constant is GROMACS's own
- lj/cut/coul/long: cutoff and constant; its kspace_style states an accuracy, not an alpha, so pricing is refused until the Ewald parameters are stated
- the real-space lj/cut/coul/long; stated Ewald parameters refused (molrs's smooth PME is not LAMMPS's PPPM)
- the charges; PME / Ewald is a createSystem argument; stated Ewald parameters refused
- the charges; coulombtype = PME is an .mdp setting; stated Ewald parameters refused
- through its codec, mixing sixthpower as LAMMPS's always does; not run by LAMMPS: the installed lmp has no CLASS2 package
- its kernel prices a row per pair (cross rows), which a CustomNonbondedForce has not
- its parameters do not mix, so a pair's are a cross row's, which a CustomNonbondedForce has not
- polarizable (Drude) screening, outside the Class-I IR
- [0, 0, s] (OpenMM's 14scale)
- [0, 0, s]; another refused
- LAMMPS has no per-pair 1-4 parameters
- refused as such; its exact LAMMPS form is special_bonds 0 and one zero-K dihedral charmm row of w = 1 per 1-4 pair
- foyer's combining_rule, which OpenMM's own app.ForceField ignores
- foyer's combining_rule and the