Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Python Bindings

Python bindings are available when molex is built with the python feature. The module is exposed as import molex via PyO3.

Installation

cd crates/molex
maturin develop --release --features python

The bindings are a directory module (src/python/), not a single file.

Object graph (Assembly / Entity / Residue)

The documented default for a normal caller is the object API: parse a structure, then walk entities and residues.

import molex

asm = molex.Assembly.from_pdb(pdb_string)          # default Completion.Heavy
asm = molex.Assembly.from_mmcif(cif_string)
asm = molex.Assembly.from_bcif(bcif_bytes)

for entity in asm:                                  # __len__ / __getitem__
    print(entity.id, entity.kind, entity.chain_id, entity.residue_count)
    for residue in entity.residues():
        print(residue.name, residue.seq_id, residue.ins_code)

from_pdb / from_mmcif / from_bcif take an optional completion argument (Completion.Raw / Heavy / AllAtom, default Heavy). Entity exposes id, kind, molecule_type, chain_id, label, atom_count, residue_count, residues(), and phi_psi() (per-residue backbone torsions in degrees). Residue exposes name, seq_id, label_seq_id, and ins_code.

Completion and analysis

heavy = asm.normalize()         # Completion.Heavy projection
all_atom = asm.to_all_atom()    # Completion.AllAtom projection
custom = asm.complete(molex.Completion.Raw)

asm.recompute_ss()              # opt-in DSSP (ss starts empty)
ss = asm.secondary_structure()  # one 'H'/'E'/'C' string per entity

area = asm.sasa()               # total protein SASA (Angstrom^2)
bonds = asm.covalent_bonds()    # [(i, j), ...] intra-entity, flat atom indices
ss_bonds = asm.disulfides()     # [(i, j), ...] Cys SG-SG pairs

cif = asm.to_mmcif()            # mmCIF string (round-trips via from_mmcif)

The complete projections re-index atoms, so externally held atom indices do not carry across them. rmsd(a, b) is a free function: optimal-superposition RMSD between two (N, 3) coordinate sets.

Edits and deltas

EditList builds a typed edit batch (parallel to the C edit surface); Assembly.apply_edits applies it, and the delta bytes round-trip the coordinate/residue/variant edits (topology edits are not representable on the delta wire).

edits = molex.EditList()
edits.push_set_entity_coords(entity_id, new_coords)        # [(x, y, z), ...]
edits.push_set_residue_coords(entity_id, residue_idx, new_coords)
edits.push_set_variants(entity_id, residue_idx, [molex.Variant.protonation_his_delta()])
asm.apply_edits(edits)

delta = edits.to_delta()        # bytes
asm.apply_delta(delta)          # decode + apply in one call

Assembly-bytes IO helpers

These free functions operate on serialized assembly bytes (the entity-aware binary format); they are transport helpers for the wire protocol, not the default path.

import molex

# Parse PDB string to assembly bytes
assembly_bytes = molex.pdb_to_assembly_bytes(pdb_string)

# Parse mmCIF string to assembly bytes
assembly_bytes = molex.mmcif_to_assembly_bytes(cif_string)

# Convert assembly bytes to PDB string
pdb_string = molex.assembly_bytes_to_pdb(assembly_bytes)

# Round-trip validation
validated = molex.deserialize_assembly_bytes(assembly_bytes)

Biotite-agnostic columnar interchange (PyAtomTable)

PyAtomTable round-trips atom data through molex as per-atom numpy columns, with no Biotite import on either side. A plugin builds the Biotite (or any other) representation from these columns itself.

import molex

# From assembly bytes -> columns
t = molex.PyAtomTable.from_assembly_bytes(assembly_bytes)
coords = t.coords            # (N, 3) float32
atom_names = t.atom_names    # numpy 'S4' byte strings, trailing-space trimmed
res_names = t.res_names      # numpy 'S3' byte strings
res_ids = t.res_ids          # (N,) int32
observed = t.observed_mask   # (N,) bool
bonds = t.bonds()            # [(i, j, order), ...] inferred ligand/ion bonds

# Columns -> molex (after a model edits the coordinates, say)
t2 = molex.PyAtomTable.from_columns(
    coords, atom_names, t.elements, res_ids, res_names, t.chain_ids,
    t.occupancies, t.b_factors, t.mol_types,
    entity_ids=t.entity_ids, observed=observed,
)
assembly_bytes = t2.to_assembly_bytes()

atom_names/res_names/elements/mol_types accept either str or fixed-width bytes (a numpy 'S' array works). Only coords, atom_names, elements, res_ids, res_names, and chain_ids are required; absent occupancies/b_factors default to 1.0/0.0 and absent observed to True.

Each atom’s molecule type follows a three-tier precedence: chain_types (int32 codes) if given, else mol_types strings, else classification of the residue name. So annotation-free input (hand-built backbone frames, concatenated templates, folding-model outputs) classifies a protein backbone as protein rather than ligand. When entity_ids is omitted, entity ids are synthesized from each atom’s (chain_id, mol_type) pair in first-appearance order.

Crystallographic refinement (PyExperimentalData, feature = xtal)

When molex is built with the xtal feature, the Python module exposes PyExperimentalData for structure-factor-based density synthesis and B-factor refinement.

import molex

exp = molex.PyExperimentalData.from_sf_cif(sf_cif_string)
exp = molex.PyExperimentalData.from_sf_cif_with_spacegroup(sf_cif_string, 19)

density = exp.compute_density(atom_table)   # PyAtomTable -> density grid
refined = exp.refine_b_factors(atom_table)  # needs the `minimization` feature

# Metadata getters
exp.d_min                 # resolution limit (Angstrom)
exp.space_group_number
exp.num_reflections
exp.grid_dims             # (nx, ny, nz)