Imported from Acellera/moleculekit (
AGENTS.md). Install upstream withnpx skills add Acellera/moleculekit. Copyright stays with the author.
AGENTS.md — moleculekit in-repo orientation for LLMs
This file is the first stop for an AI agent (or any contributor) picking up the moleculekit repository cold. It covers what the library is, where things live, canonical recipes, known pitfalls, and how the documentation is organized.
What moleculekit is
moleculekit is a Python library for reading, writing, and manipulating
biomolecular structures and trajectories. Its primary data class is
Molecule — which represents proteins, nucleic acids, lipids, ions,
waters, ligands, and full simulation boxes. Around it the library provides
VMD-style atom-selection syntax, MD trajectory I/O across a wide range
of formats (PDB, mmCIF, MOL2, PRMTOP, PSF, XTC, DCD, …), automated system
preparation for force-field MD (protonation, gap closing, disulfide
detection, custom-residue templating), projection-based per-frame feature
extraction for machine learning, and conversions to RDKit / OpenFF
toolkits via Molecule.toRDKitMol() and Molecule.toOpenFFMolecule().
Where things live
moleculekit/molecule.py # Molecule class — the central data structure
moleculekit/rdkittools.py # Molecule <-> RDKit conversion helpers
moleculekit/tools/
preparation.py # systemPrepare — protonation / FF prep
mutate.py # residue mutation with rotamer packing
modelling.py # gap closing and loop modelling
autosegment.py # automatic segment assignment
nonstandard_residues.py # detectNonStandardResidues
preparation_customres.py # templateResidueFromSmiles helpers
moleculekit/projections/ # Metric* classes for per-frame feature extraction
metricdistance.py # MetricDistance
metricrmsd.py # MetricRmsd
metricsasa.py # MetricSasa
(and others…)
moleculekit/interactions/ # protein–ligand interaction detection
# (hbonds, π–π, cation–π, σ-hole)
moleculekit/readers.py # format-specific I/O readers
moleculekit/writers.py # format-specific I/O writers
moleculekit/atomselect/ # VMD-style selection parser
moleculekit/bondguesser.py # distance-based and RDKit-based bond inference
moleculekit/viewer/ # viewer integrations (VMD, PyMOL, Mol* browser)
tests/ # pytest suite (function prefix pattern: _test*)
doc/source/ # Sphinx documentation source
tutorials/ # Diátaxis: learning-oriented sequences
howto/ # Diátaxis: task-oriented recipes
explanation/ # Diátaxis: concepts and mental models
reference/ # Diátaxis: API reference (autogenerated)
Canonical recipes
Read a structure by RCSB ID
Passing a four-character code fetches the structure directly from RCSB PDB.
from moleculekit.molecule import Molecule
mol = Molecule("3PTB") # fetches from RCSB
Atom selection — string, index array, or boolean mask
atomselect returns a boolean mask by default; pass indexes=True for integer
indices. For repeated operations on the same field, an array comparison skips
the parser entirely and returns a mask that any moleculekit API accepts.
protein = mol.atomselect("protein") # boolean mask
protein_idx = mol.atomselect("protein", indexes=True) # uint32 indices
benz = mol.resname == "BEN" # array-comparison mask, skips parser
Prepare a protein at chosen pH
systemPrepare returns a 3-tuple when return_details=True; without it, a
2-tuple (prepared_mol, specs).
from moleculekit.tools.preparation import systemPrepare
pmol, specs, details = systemPrepare(mol, pH=7.4, return_details=True)
Mutate a residue with Dunbrack rotamers
rotamer_mode accepts "best", "first", or "random".
mol.mutateResidue("chain A and resid 100", "ALA", rotamer_mode="best")
Template a custom residue from SMILES
Always strip hydrogens first so the function starts from a clean heavy-atom
graph, then let addHs=True rebuild them according to the SMILES. Prefer a
boolean mask over an atomselect string when you already have one.
mol.remove("hydrogen") # normalize first
mask = mol.resname == "NAG"
mol.templateResidueFromSmiles(mask, smiles="CC(=O)NC1C(O)C(O)C(CO)OC1O", addHs=True)
To template from a reference structure (e.g. an RCSB chemical-component CIF)
instead of a SMILES string, use templateResidueFromMolecule(mask, refmol, addHs=True). It matches the residue to the reference by atom name (same
heavy-atom names required) and copies the reference's bond orders and formal
charges verbatim, so those must already be correct in refmol.
Compute a per-frame projection
metric.project(mol) returns a NumPy array of shape (numFrames, numFeatures).
from moleculekit.projections.metricdistance import MetricDistance
metric = MetricDistance("protein and name CA", "resname BEN", periodic="selections")
data = metric.project(mol) # shape (numFrames, numFeatures)
View a molecule in the browser
The built-in Mol* viewer opens a local browser tab with no external dependency.
mol.view(viewer="molstar") # built-in browser viewer
Units
Moleculekit uses Ångström (Å) as its distance unit throughout —
mol.coords, mol.box, every reader/writer (formats like GROMACS that use
nanometres on disk are converted on load and on write), and every distance
parameter in the library (coldist, spatialgap, within X of, etc.).
Box angles (mol.boxangles) are in degrees; dihedral angles returned
by mol.getDihedral / passed to mol.setDihedral are in radians.
Pitfalls
-
Use
Molecule.templateResidueFromSmiles, notsystemPrepare'sresidue_smiles=parameter. Theresidue_smiles=kwarg is being deprecated. The canonical pattern is: strip Hs (mol.remove("hydrogen")) → calltemplateResidueFromSmiles(mask, smiles=..., addHs=True). -
Prefer boolean masks over atomselect strings when you already have a mask. Array comparisons like
mol.resname == "BEN"skip the parser and are accepted everywhereMolecule.atomselectdispatches:remove,filter,get,set,copy,templateResidueFromSmiles,Metric*.sel, and each per-residue entry insystemPrepare'sno_opt/no_prot/no_titr/force_protonationlists. -
systemPrepare(return_details=True)returns a 3-tuple(prepared_mol, specs, details_df). Withoutreturn_details=Trueit returns a 2-tuple(prepared_mol, specs). Unpacking the wrong arity raises at runtime. -
No defensive checks for impossible conditions. Don't guard in-range bond indices, H–H bonds, or other invariants the library already enforces. Trust internal state.
-
Raise, don't
logger.warning, on structurally wrong output. If a reachable code path would produce a broken or unbuildableMolecule, raise an exception — don't emit a warning and continue. -
Use
uvfor Python. Run tests withuv run pytest, add dependencies withuv add <pkg>, sync the environment withuv sync. Don't use barepythonorpip. -
Tests use the
_test*prefix.pyproject.tomlsetspython_functions = "_test*"— pytest only collects functions whose names start with an underscore. A function namedtest_foowill be silently skipped. -
Don't expect
detectNonStandardResiduesto surface Cys–Cys disulfides. Disulfide bonds are handled internally bysystemPrepare's CYS→CYX rename step; they are not returned as non-standard residue specs. -
rotamer_modevalues are"best","first","random"— not"fast"or any other string. -
Don't
git add uv.lock. The lock file is intentionally left untracked; never stage it. -
Don't reference private (
_-prefixed) functions, methods, or attributes in user-facing docs. Code can call them; documentation cannot name them.
Doc map
The documentation follows the Diátaxis framework, split
into four quadrants under doc/source/:
| Quadrant | Purpose | Entry point |
|---|---|---|
| Tutorials | Learning-oriented, sequential walkthroughs | doc/source/tutorials/index.md |
| How-to guides | Task-oriented recipes for specific goals | doc/source/howto/index.md |
| Explanation | Concepts and mental models | doc/source/explanation/index.md |
| Reference | API docs, autogenerated from docstrings | doc/source/reference/index.md |
For LLM consumers, a site-map of all documentation pages is available at
doc/source/llms.txt. This file is generated as part of the documentation
build; it may not yet be present on all branches (it lands with Task 10.2 of the
docs overhaul).