Skip to content

Typifier

Graph typification and force-field parameter assignment.

The contract

A typifier is Atomistic -> Atomistic: it takes a molecular graph and returns a typed copy whose atoms and links carry force-field types and parameters. Every typifier runs the same flow — copy, assign, write the annotations back and define the types — so typify() is written once, on the base class, and assign() is the single step a typifier implements.

class Typifier:
    def typify(self, mol) -> Atomistic                # final: copy, assign, write back
    def assign(self, graph) -> TypeAssignment         # the only thing that differs
    def source_forcefield(self) -> ForceField         # optional: what the output declares
    def forcefield(self) -> ForceField                # the union of every type typify assigned

typify is final — a subclass that defines it raises TypeError at class creation — and it is the only writer of forcefield().

Typifiers are named after the force field or the tool that decides the types.

Quick reference

Symbol Summary Preferred for
Typifier The contract: implement assign (and optionally source_forcefield) Writing your own
TypeAssignment What assign returns: node and link annotations, styles, pair rows Writing your own
OplsAaTypifier Full OPLS-AA typing pipeline (native) OPLS-AA all-atom force fields
Mmff94Typifier / Mmff94sTypifier Full MMFF94 / MMFF94s typing pipeline (native) MMFF all-atom force fields
ElementTypifier type labels from element symbols; defines no force field Writers that need labels on an untyped molecule
AtdTypifier AtdTypifier(parameter_set="gaff2"): antechamber's atom-type tables (gaff, gaff2, amber, bcc, …) evaluated natively — atom types only, no charges or parameters GAFF / GAFF2 or BCC atom types without running antechamber
GaffTypifier GaffTypifier(parameter_set="gaff2"): GAFF / GAFF2 bonded terms and parameters, natively, for atoms AtdTypifier already typed (exact gaff.dat rows, wildcards, or parmchk2-style estimates marked estimated) GAFF / GAFF2 parameters without AmberTools
AntechamberTypifier AntechamberTypifier(parameter_set="gaff2"): antechamber → parmchk2 → tleap for one complete molecule; needs AmberTools An antechamber charge method other than AM1-BCC / ABCG2; exporting an estimated GAFF term while molrs's LAMMPS / GROMACS writers refuse it
TleapTypifier TleapTypifier(parameter_set="gaff2"): tleap alone over a finished graph that already carries AMBER types and charges (a graph with ports is refused); needs AmberTools Extra frcmod parameters on top of the leaprc

Every name is on mp.ff.typifier, which mirrors molrs.ff.typifier (the native typifiers are the molrs objects) and adds the two AmberTools typifiers. GAFF / GAFF2 is typed natively by AtdTypifier then GaffTypifier; that is the default route, and it assigns the types and parameter values tleap does for the same set. Its AM1-BCC charges come from mp.ff.charge.Am1BccModel: sqm's AM1 charges (through antechamber -c mul), corrected by the native BccModel exactly as antechamber -c bcc corrects them. A native GAFF force field merges with one read from tleap's prmtop, an AmberPolymerBuilder chain's included, so a small molecule mixed with a chain is typed natively too. The AmberTools typifiers read tleap's prmtop back; they are for what only that pipeline does: parameters of your own on top of the leaprc (TleapTypifier) and antechamber's other charge methods (AntechamberTypifier). molrs 0.18.0's LAMMPS and GROMACS writers refuse the estimated parameter GaffTypifier puts on a term it estimated, so such a molecule is exported through AntechamberTypifier for now. Every GAFF entry point takes the same required parameter_set ("gaff" or "gaff2", no default). The AmberTools typifiers shell out through molpy.wrapper; see Optional external tools. A GAFF polymer is not typed by joining typed monomers (each join folds the leaving group's charge onto its anchor): build it with mp.builder.AmberPolymerBuilder, which cuts one antechamber-typed oligomer with prepgen and joins the residues with tleap — see Builder and the AmberTools guide.

UFF typing is in the native core (Rust) only; it has no Python binding.

Canonical example

import molpy as mp
from molpy.ff.typifier import OplsAaTypifier

mol, _ = mp.conformer.Conformer(add_hydrogens=True, seed=42).generate(
    mp.io.smiles.read_smiles("CCO").to_atomistic()
)

typifier = OplsAaTypifier(strict=True)
typed_mol = typifier.typify(mol)  # returns a new Atomistic
ff = typifier.forcefield()  # the parameters of the types just assigned
frame = typed_mol.to_frame()

Key behavior

  • typify() returns a new graph — the original is not modified
  • forcefield() is the union of what every typify call assigned, as an independent copy; a definition that contradicts one already held raises
  • A term the force field does not parameterise is left undecided, never stamped with None
  • SMARTS matching is native; MolPy carries no matcher of its own

Writing a typifier

Implement assign and stop. It returns a TypeAssignment: one annotation mapping per node, positional against graph.atoms, and per link class one mapping per row, positional against graph.links.exact_bucket(cls). A type annotation is (style, name, endpoints, params): it stamps name and every param, and defines the type name on endpoints (atom-type names, empty for an atom type). The name is never parsed — the endpoints are required. styles declares each style used; pairs adds (style, name, endpoints, params) pair rows.

class Tip3pTypifier(mp.ff.typifier.Typifier):
    """TIP3P water: OW / HW atoms and one OW-HW bond type."""

    SITES = {"O": ("OW", 15.999, -0.834), "H": ("HW", 1.008, 0.417)}

    def source_forcefield(self):
        return mp.ff.forcefield.ForceField("tip3p", units="real")  # the output declares real units

    def assign(self, graph):
        nodes = []
        for atom in graph.atoms:
            name, mass, charge = self.SITES[atom["element"]]
            nodes.append({"type": ("full", name, (), {"mass": mass}), "charge": charge})
        bonds = [
            {"type": ("harmonic", "OW-HW", ["OW", "HW"], {"k": 450.0, "r0": 0.9572})}
            for _ in graph.links.exact_bucket(mp.Bond)
        ]
        return mp.ff.typifier.TypeAssignment(
            nodes,
            {mp.Bond: bonds},
            styles=[("atom", "full", {}), ("bond", "harmonic", {}), ("pair", "lj/cut", {})],
            pairs=[
                ("lj/cut", "OW", ["OW"], {"epsilon": 0.1521, "sigma": 3.1507}),
                ("lj/cut", "HW", ["HW"], {"epsilon": 0.0, "sigma": 0.0}),
            ],
        )


tip3p = Tip3pTypifier()
water = tip3p.typify(mp.io.smiles.read_smiles("[H]O[H]").to_atomistic())
assert [bond["type"] for bond in water.bonds] == ["OW-HW", "OW-HW"]
assert tip3p.forcefield().get_style("bond", "harmonic").get_type_by_name("OW-HW")["k"] == 450.0

Full API

Typifiers

typifier

Force-field typification — :mod:molrs.ff.typifier, plus the AmberTools typifiers.

The native typifiers are identity re-exports (mp.ff.typifier.OplsAaTypifier is molrs.ff.typifier.OplsAaTypifier). Typifier is the base every typifier extends: a subclass implements assign (and optionally source_forcefield) and the base owns typify and the accumulated forcefield(). :func:assign_cmaps builds a typed frame's cmaps block.

GAFF / GAFF2 is typed natively: AtdTypifier (antechamber's atom types) then GaffTypifier (the bonded terms), each taking molrs's required parameter_set; AM1-BCC charges come from :class:molpy.ff.charge.Am1BccModel (sqm, then the native correction). That force field merges with an :class:~molpy.builder.AmberPolymerBuilder chain's. molpy adds :class:AntechamberTypifier and :class:TleapTypifier, which run the whole AmberTools pipeline (:mod:molpy.wrapper) and read tleap's prmtop back, for antechamber's other charge methods and for parameters of your own on top of the leaprc. They take the same required parameter_set.

AntechamberTypifier

AntechamberTypifier(
    *,
    parameter_set,
    charge_method="bcc",
    work_dir="amber_work",
    config=None,
)

Bases: AmberTypifier

GAFF / GAFF2 atom types, charges and parameters for a complete molecule.

assign runs antechamber (-at parameter_set -c charge_method, net charge from the atoms' formal charges), parmchk2 and tleap in a directory under work_dir keyed by the input mol2 and every setting, and assigns the prmtop's types, charges and bonded terms. Every program reruns, and each step's output is deleted before it runs, so nothing an earlier run left behind is read. Ports are allowed; their leaving groups are real atoms.

A polymer chain is not typed here: joining typed monomers folds each leaving group's charge onto its anchor. Build it with :class:molpy.builder.AmberPolymerBuilder, which cuts one antechamber-typed oligomer with prepgen and sequences it with tleap.

The default GAFF route is native: :class:~molrs.ff.typifier.AtdTypifier and :class:~molrs.ff.typifier.GaffTypifier assign the same types and parameter values, and :class:Am1BccModel the same AM1-BCC charges, with sqm the only program; that force field merges with an :class:~molpy.builder.AmberPolymerBuilder chain's as this one does. This typifier is for an antechamber charge method other than AM1-BCC / ABCG2. Its prmtop force field also carries no estimate provenance, which molrs 0.18.0's LAMMPS and GROMACS writers refuse on a term GaffTypifier estimated.

Parameters:

Name Type Description Default
parameter_set ParameterSet

The GAFF generation, molrs's parameter_set ("gaff" or "gaff2"): antechamber's -at, parmchk2's -s and the leaprc.<parameter_set> tleap sources. Required, as in molrs.

required
charge_method str

The antechamber -c charge method.

'bcc'
work_dir str | Path

Where the per-run directories are created.

'amber_work'
config Config | None

molpy's configuration (:func:molpy.config.load_config): the AmberTools executables and environment ([wrapper], [wrapper.<tool>]); None loads it.

None
assign
assign(graph)

Type graph with antechamber and assign tleap's prmtop.

Raises:

Type Description
ValueError

The prmtop disagrees with the graph.

ToolError

An AmberTools step failed.

AtdTypifier

Bases: Typifier[Atomistic]

antechamber atom types — one rule engine over seven ATOMTYPE_*.DEF tables.

parameter_set is the antechamber -at flag and is required: seven tables exist, they disagree, and there is no default. An atom no rule matches comes back labelled "DU" — the table's own catch-all row, not a fallback.

bond_orders says which bond orders the types follow: BondOrderSource.Connectivity (the core default) judges them from the connectivity alone, as antechamber's bondtype -j full does, ignoring the molecule's own; BondOrderSource.Stated keeps the molecule's orders.

ElementTypifier

Bases: Typifier[Atomistic]

molrs.ff.typifier.ElementTypifier — type labels from atomic numbers alone, with no force field.

Atoms get type = the symbol their atomic_number names (e.g. "C"); bonds, angles and dihedrals get their endpoint symbols joined with - in the byte-wise smaller orientation (bond O–H is "H-O"). :meth:forcefield stays empty. Exported from :mod:molrs.ff.typifier only.

Raises

ValueError From :meth:typify, when an atom has no valid atomic_number or is the wildcard (0), or the molecule has impropers.

GaffTypifier

Bases: Typifier[Atomistic]

GAFF / GAFF2 bonded terms and parameters for a molecule whose atoms already carry GAFF types (AtdTypifier with the same parameter_set).

Angles and dihedrals are regenerated from the bond graph, impropers built as tleap builds them (wherever tleap finds a row, or parmchk2 an estimate at a centre PARMCHK.DAT flags as planar; tleap's atom order, centre third); each term is an exact gaff.dat / gaff2.dat row, a wildcard row, or a parmchk2-style estimate whose type carries estimated, estimate_penalty, estimate_method and estimate_analog. The four price nothing: every engine writer (LAMMPS, GROMACS, OpenMM, frcmod) passes them over, and ForceField.merge ignores them. parameter_set is required; there is no default.

Raises

ValueError An unknown parameter_set; from :meth:typify, an untyped atom, a type the table does not declare, or terms nothing covers (all listed).

Mmff94Typifier

Bases: Typifier[Atomistic]

MMFF94 (Halgren 1996) atom types, charges and bonded parameters.

The variant is the class, never a flag. See Mmff94sTypifier for the "static" parameter set.

typify labels the graph; PotentialCompiler(forcefield()).compile(frame) compiles it. There is no one-step build — MMFF walks the same route as every other force field.

Mmff94sTypifier

Bases: Typifier[Atomistic]

MMFF94s (Halgren 1999) — the "static" set, for energy minimization.

Differs from Mmff94Typifier only on delocalised trivalent nitrogen (MMFF numeric types 10 NC=O / 40 NC=C): 11 out-of-plane rows and 42 torsion rows are re-parameterised so the nitrogen minimizes planar. typify bakes koop = +0.015 (type 10) / +0.030 (type 40) mdArad^-2 on those centres, where the improper kernel reads it as E_oop = 0.5 * 143.9325 * koop * chi**2 (chi in radians). Every atom type and every bond / angle / stretch-bend / vdW / charge parameter is shared with MMFF94.

OplsAaTypifier

Bases: Typifier[Atomistic]

OPLS-AA atom types and bonded parameters; source is an OpenMM XML file (a path) or its content (bytes / molrs.io.Text; a plain str is refused), None for the embedded table.

TleapTypifier

TleapTypifier(
    *,
    parameter_set,
    forcefield=None,
    work_dir="amber_work",
    config=None,
)

Bases: AmberTypifier

Parameterise an already typed graph with tleap alone.

The graph's atoms must carry AMBER atom types and charges. assign writes them as mol2, writes forcefield (the templates' parameters) as a frcmod, runs tleap and assigns the prmtop's bonded terms, so junction terms come from the leaprc. The run directory is keyed by the mol2, the parameter set and the frcmod, and tleap's outputs are deleted before it runs. It never runs antechamber or parmchk2. Types and charges are left as they are; tleap changing one raises. A graph that still has ports is refused: joining those ports would fold the leaving-group charge onto the anchors. A polymer chain is built with :class:molpy.builder.AmberPolymerBuilder, whose prepgen step spreads that charge instead.

:class:~molrs.ff.typifier.GaffTypifier assigns the same bonded terms natively, and its force field merges with an :class:~molpy.builder.AmberPolymerBuilder chain's; this typifier is for parameters of your own on top of the leaprc (forcefield).

Parameters:

Name Type Description Default
parameter_set ParameterSet

The GAFF generation, molrs's parameter_set ("gaff" or "gaff2"): tleap sources leaprc.<parameter_set>. Required, as in molrs.

required
forcefield ForceField | None

Parameters tleap loads on top of the leaprc, written as a frcmod; None loads none.

None
work_dir str | Path

Where the per-run directories are created.

'amber_work'
config Config | None

molpy's configuration (:func:molpy.config.load_config): the AmberTools executables and environment ([wrapper], [wrapper.<tool>]); None loads it.

None
assign
assign(graph)

Run tleap over graph's own types and charges; assign its prmtop.

Raises:

Type Description
ValueError

An atom lacks a type or charge, tleap changed one, or the prmtop disagrees with the graph.

ToolError

tleap failed.

TypeAssignment

What a typifier's assign assigns to one graph.

nodes is positional against graph.atoms; links maps a relation kind to rows positional against that kind's own rows, so an improper never shifts a dihedral position. A key is a relation view class (Bond, Angle, Dihedral, Improper, Port; rows as graph.bonds … graph.ports) or a block name ("bonds", or a term block such as "urey_bradleys"; rows as graph.terms(block)). A type annotation under a kind defines a type of the category whose block the kind is (urey_bradleys → urey_bradley). Any other key raises TypeError, a kind named twice ValueError. styles are (category, style, params) to declare, in order; pairs are (style, name, endpoints, params) pair rows.

Typifier

Bases: Generic[_TGraph]

The base of every graph typifier: one assign hook plus the output force field its typing accumulates.

A subclass implements assign (and optionally source_forcefield) and nothing else; defining typify on a subclass raises TypeError at class creation. The native classes extend this base and only construct; they are subclassable, but a subclass of one that defines assign or source_forcefield raises TypeError (they run in Rust).

assign
assign(graph)

Type graph and return what it assigns.

assign may write intermediate results (generated topology, perceived bond types) onto the graph it is given; typify always gives it a private copy. The base raises NotImplementedError; a native class runs its Rust matcher.

forcefield
forcefield()

The accumulated output — exactly the definitions typify has assigned — returned as a copy.

Edits to the copy do not reach the typifier; typify is the only writer. Before the first typify it is the seeded empty output.

source_forcefield
source_forcefield()

The force field this typifier matches against, returned as a copy.

The output starts as its empty likeness (name, declared units and special_bonds). A Python subclass that does not override it raises NotImplementedError, and its output starts as an empty force field named after the class.

typify
typify(mol)

Do not override; the only writer of forcefield().

Copies mol (as copy.copy(mol)), calls assign on the copy, and writes the match onto the copy and the output. Returns the typed copy; mol is untouched. mol must be an Atomistic (anything else raises TypeError). Raises NotImplementedError without an assign and ValueError when the match does not fit the graph or contradicts the output (which is then unchanged).