molex
molex is a Rust library for parsing, transforming, and serializing molecular structure data. It provides a unified type system for working with proteins, nucleic acids, ligands, and other biomolecules across multiple file formats.
What it does
- Parse PDB, mmCIF, BinaryCIF, DCD trajectory, and MRC density files
- Convert between formats with a canonical intermediate representation
- Transform coordinates: alignment, superposition, filtering, interpolation
- Analyze secondary structure (DSSP), bond inference, validation
- Extract render-ready data (backbone chains, sidechain atoms, bonds)
- Serialize to a compact binary format for IPC and storage
Key types
| Type | Description |
|---|---|
Coords | Flat atom arrays: positions, names, chains, residues, elements |
MoleculeEntity | A classified molecule (protein, ligand, etc.) with its Coords |
RenderCoords | Backbone chains + sidechain atoms, ready for GPU consumption |
SSType | Secondary structure classification (helix, sheet, coil, turn) |
DensityMap | 3D electron density grid from MRC/CCP4 files |
Design principles
- Zero-copy where possible. Parsing produces owned data, but transforms operate on slices and iterators.
- Format-agnostic core.
CoordsandMoleculeEntitycarry no format-specific metadata — adapters handle the translation. - Embeddable. No filesystem, network, or GPU dependencies in the
core. Optional
pythonfeature adds PyO3 bindings.
Crate structure
molex/
├── types/ Core data structures (Coords, Entity, Density)
├── adapters/ Format I/O (PDB, mmCIF, BinaryCIF, DCD, MRC, AtomWorks)
├── cif/ CIF/STAR parser and typed extractors
├── ops/ Coordinate transforms, validation, bond inference
├── render/ Render-ready data extraction
├── secondary_structure/ DSSP and SS type classification
├── ffi/ C-compatible FFI layer
└── python/ PyO3 bindings (feature-gated)
API documentation
For the full Rust API reference, run:
cargo doc --open --document-private-items
Quick Start
Parse a PDB file
use molex::adapters::pdb::structure_file_to_coords;
use molex::types::entity::split_into_entities;
// Parse any PDB or mmCIF file
let coords = structure_file_to_coords("1ubq.pdb")?;
// Split into classified entities (protein, ligand, water, etc.)
let entities = split_into_entities(&coords);
for entity in &entities {
println!(
"Entity {}: {:?} ({} atoms)",
entity.entity_id,
entity.molecule_type,
entity.atom_count(),
);
}
Extract backbone chains
use molex::ops::transform::{protein_only, extract_backbone_chains};
let protein_coords = protein_only(&coords);
let chains = extract_backbone_chains(&protein_coords);
for (i, chain) in chains.iter().enumerate() {
println!("Chain {}: {} CA positions", i, chain.len());
}
Compute secondary structure
use molex::secondary_structure::dssp::from_entity;
let ss_types = from_entity(&entities[0]);
for (i, ss) in ss_types.iter().enumerate() {
println!("Residue {}: {:?}", i, ss);
}
Convert between formats
use molex::adapters::pdb::{pdb_to_coords, coords_to_pdb};
use molex::types::coords::{serialize, deserialize};
// PDB string → binary COORDS
let coords_bytes = pdb_to_coords(pdb_string)?;
// Binary COORDS → Coords struct
let coords = deserialize(&coords_bytes)?;
// Coords struct → PDB string
let pdb_out = coords_to_pdb(&coords_bytes)?;
Python usage
import molex
# PDB round-trip
coords_bytes = molex.pdb_to_coords(pdb_string)
pdb_back = molex.coords_to_pdb(coords_bytes)
# Entity-aware AtomArray conversion (for ML pipelines)
atom_array = molex.entities_to_atom_array(assembly_bytes)
assembly_bytes = molex.atom_array_to_entities(atom_array)
Installation
As a Rust dependency
Add to your Cargo.toml:
[dependencies]
molex = { git = "https://github.com/petridecus/molex" }
As a Python package
molex provides optional Python bindings via PyO3/maturin.
# Build and install the wheel
cd crates/molex
maturin develop --release --features python
# Verify
python -c "import molex; print('OK')"
Feature flags
| Feature | Description |
|---|---|
python | Enable PyO3 bindings (requires pyo3, numpy) |
The default build includes no optional features — it’s a pure Rust
library with no native dependencies beyond glam and pdbtbx.
Architecture Overview
molex is organized as a layered conversion pipeline. Raw file bytes enter through adapters, pass through a canonical intermediate representation, and exit as either transformed coordinates, render-ready geometry, or binary-serialized bytes.
Layer diagram
┌─────────────┐
PDB ──►┐ │ Coords │ ┌──► Binary COORDS
CIF ──►├─► adapters ──►│ (canonical) ├──► ops ───────►├──► PDB string
BCIF ──►├─► │ + Entity │ transform ├──► RenderCoords
DCD ──►┘ │ classify │ validate └──► AtomArray (Py)
└──────┬──────┘ align
│
▼
secondary_structure
(DSSP → SSType[])
Core types
Coords
The canonical atom-level representation. Flat parallel arrays:
pub struct Coords {
pub num_atoms: usize,
pub atoms: Vec<CoordsAtom>, // x, y, z, occupancy, b_factor
pub chain_ids: Vec<u8>,
pub res_names: Vec<[u8; 3]>,
pub res_nums: Vec<i32>,
pub atom_names: Vec<[u8; 4]>,
pub elements: Vec<Element>,
}
Flat arrays make iteration, slicing, and binary serialization cheap.
The tradeoff is no hierarchical chain→residue→atom tree — use
MoleculeEntity when you need entity-level grouping.
MoleculeEntity
A classified molecule with its coordinates:
pub struct MoleculeEntity {
pub entity_id: u32,
pub molecule_type: MoleculeType, // Protein, DNA, RNA, Ligand, ...
pub kind: EntityKind, // Polymer or AtomSet
}
split_into_entities() classifies residues by name and groups them
into entities. This is the primary input to viso’s rendering engine.
RenderCoords
Extracted backbone chains (N-CA-C triples) and sidechain atoms with
bond connectivity. Bridge between Coords and GPU renderers.
Module responsibilities
| Module | Responsibility |
|---|---|
types | Coords, MoleculeEntity, DensityMap, binary serialization |
adapters | Format I/O: PDB, mmCIF, BinaryCIF, DCD, MRC, AtomWorks |
cif | Low-level CIF/STAR parser with typed extractors |
ops | Coordinate transforms, Kabsch alignment, bond inference, validation |
render | Backbone/sidechain extraction, sequence extraction |
secondary_structure | DSSP algorithm, SS type classification |
ffi | extern "C" functions for C/C++ integration |
python | PyO3 bindings (feature-gated) |
Binary formats
molex defines two compact binary formats for IPC:
- COORDS01 — single molecule: magic + atom data (26 bytes/atom)
- ASSEM01 — multi-entity assembly: magic + entity headers + atom data
Both use big-endian encoding and are designed for zero-overhead round-tripping between Rust and C++ backends.
Data Flow
This page traces the typical lifecycle of molecular data through molex, from file input to render-ready output.
1. Parsing
Every supported format has a dedicated adapter that produces Coords:
PDB file → adapters::pdb::pdb_to_coords() → Vec<u8> (COORDS01)
CIF file → adapters::pdb::mmcif_to_coords() → Vec<u8> (COORDS01)
BCIF file → adapters::bcif::bcif_to_coords() → Vec<u8> (COORDS01)
DCD file → adapters::dcd::dcd_file_to_frames() → Vec<DcdFrame>
MRC file → adapters::mrc::mrc_to_density() → DensityMap
The binary Vec<u8> is the COORDS01 format. Deserialize it to get
the Coords struct:
let coords = molex::types::coords::deserialize(&bytes)?;
2. Entity classification
let entities = molex::types::entity::split_into_entities(&coords);
// entities: Vec<MoleculeEntity>
// Each entity has: entity_id, molecule_type, kind (Polymer or AtomSet)
Classification is based on residue name lookup — standard amino acids
become Protein, nucleotides become DNA/RNA, HOH becomes
Water, and everything else is classified as Ligand, Ion,
Cofactor, etc.
3. Transforms
The ops::transform module provides coordinate manipulation:
// Filter to protein-only atoms
let protein = ops::transform::protein_only(&coords);
// Kabsch superposition onto a reference
let (aligned, rmsd) = ops::transform::kabsch_alignment(&mobile, &target);
// Extract backbone chains as Vec<Vec3> (N-CA-C triples)
let chains = ops::transform::extract_backbone_chains(&coords);
4. Secondary structure
DSSP classification from backbone geometry:
let ss_types: Vec<SSType> = secondary_structure::dssp::from_entity(&entity);
// SSType::Helix, SSType::Sheet, SSType::Coil, SSType::Turn
5. Render extraction
RenderCoords splits atoms into backbone and sidechain data suitable
for GPU rendering:
let render = RenderCoords::from_entity(&entity, is_hydrophobic, get_bonds);
// render.backbone_chains: Vec<Vec<Vec3>> (N-CA-C per chain)
// render.sidechain_atoms: Vec<RenderSidechainAtom>
// render.sidechain_bonds: Vec<(u32, u32)>
6. Serialization
For IPC with C++ backends or storage:
// Single molecule
let bytes = molex::types::coords::serialize(&coords)?;
// Multi-entity assembly
let bytes = molex::types::coords::serialize_assembly(&entities)?;
Pipeline summary
File → Adapter → Coords → split_into_entities → MoleculeEntity[]
│ │
├──► ops::transform (align, filter) │
├──► ops::validation (completeness) │
└──► serialize (binary IPC) │
▼
secondary_structure::dssp
│
▼
RenderCoords::from_entity
(backbone + sidechain data)
Types — Core Data Structures
The types module contains molex’s canonical data representations.
types::coords — Atom-Level Data
Coords
Flat parallel arrays representing atoms in a molecular structure.
Every atom has a position (CoordsAtom), chain ID, residue name,
residue number, atom name, and element type.
This is the lowest-level representation — no hierarchy, no
classification. Use split_into_entities() to get grouped molecules.
CoordsAtom
pub struct CoordsAtom {
pub x: f32, pub y: f32, pub z: f32,
pub occupancy: f32,
pub b_factor: f32,
}
Element
Chemical element enum with methods for covalent radius, VDW radius, and CPK color. Supports lookup by symbol or by atom name heuristics.
Binary serialization
serialize(&coords) → Vec<u8>— COORDS01 format (26 bytes/atom)deserialize(&bytes) → Coords— inverseserialize_assembly(&entities) → Vec<u8>— ASSEM01 multi-entity formatdeserialize_assembly(&bytes) → Vec<MoleculeEntity>— inverse
types::entity — Molecule Classification
MoleculeEntity
A classified molecule with its atom data:
entity_id: u32— unique identifiermolecule_type: MoleculeType— Protein, DNA, RNA, Ligand, Ion, etc.kind: EntityKind—Polymer(PolymerData)orAtomSet(AtomSet)
MoleculeType
pub enum MoleculeType {
Protein, DNA, RNA, Ligand, Ion, Water, Lipid, Cofactor, Solvent,
}
Key functions
split_into_entities(&coords) → Vec<MoleculeEntity>— classify and groupmerge_entities(&[MoleculeEntity]) → Coords— flatten back to atomsclassify_residue(name) → MoleculeType— single residue lookup
types::density — Electron Density
DensityMap
3D grid of electron density values from MRC/CCP4 files:
- Grid dimensions, cell parameters, origin
f32voxel data- Methods for coordinate-to-grid mapping
Adapters — Format I/O
The adapters module provides parsers and serializers for common
molecular structure file formats.
PDB / mmCIF (adapters::pdb)
The primary entry point for structure files:
// Auto-detect format from extension
let coords = structure_file_to_coords("path/to/file.pdb")?;
// Explicit format
let bytes = pdb_to_coords(pdb_string)?; // PDB → COORDS01 bytes
let bytes = mmcif_to_coords(cif_string)?; // mmCIF → COORDS01 bytes
let pdb = coords_to_pdb(&coords_bytes)?; // COORDS01 → PDB string
Backed by the pdbtbx crate for robust PDB/mmCIF parsing.
BinaryCIF (adapters::bcif)
RCSB’s compressed binary CIF format. Handles gzip-compressed files automatically.
let bytes = bcif_to_coords(&bcif_bytes)?;
let bytes = bcif_file_to_coords("path/to/file.bcif")?;
DCD Trajectories (adapters::dcd)
CHARMM/NAMD trajectory format — a sequence of coordinate frames sharing the same topology.
let frames: Vec<DcdFrame> = dcd_file_to_frames("trajectory.dcd")?;
// Each frame: Vec<f32> positions (x,y,z interleaved)
Also provides streaming via DcdReader for large trajectories.
MRC / CCP4 Density (adapters::mrc)
Electron density maps:
let density = mrc_to_density(&bytes)?;
let density = mrc_file_to_density("map.mrc")?;
// density: DensityMap with 3D grid + cell parameters
AtomWorks (adapters::atomworks)
Bidirectional conversion between molex entities and Biotite
AtomArray objects (via PyO3). Used by ML model pipelines
(RF3, RFdiffusion3, LigandMPNN).
import molex
# molex → AtomArray (for model inference)
atom_array = molex.entities_to_atom_array(assembly_bytes)
# AtomArray → molex (after prediction)
assembly_bytes = molex.atom_array_to_entities(atom_array)
Feature-gated behind python.
CIF Parser
The cif module is a standalone CIF/STAR parser with typed data
extraction. It operates in two layers.
Layer 1 — DOM parsing (cif::parse)
let doc = molex::cif::parse(input)?;
Parses any CIF or STAR file into an untyped Document tree:
DocumentcontainsVec<Block>- Each
Blockhasname,categories(loop tables), andpairs(key-value entries) CategoryholdsColumndata (string values, one per row)
This layer makes no assumptions about the content — it works for mmCIF, CCD, reflection data, or any STAR-format file.
Layer 2 — Typed extractors (cif::extract)
Pull structured data from a parsed Block:
use molex::cif::extract::{CoordinateData, CifContent};
// Caller knows the content type:
let coords = CoordinateData::try_from(&block)?;
// Or auto-detect:
match CifContent::try_from(&block)? {
CifContent::Coordinates(data) => { /* atom_site data */ },
CifContent::Reflections(data) => { /* refln data */ },
CifContent::Dictionary(data) => { /* CCD entry */ },
}
CoordinateData
Extracted from _atom_site loops:
- Atom positions, names, elements, B-factors, occupancy
- Chain IDs, residue names, residue numbers
- Entity ID and molecule type annotations
- Cell parameters and space group (if present)
ReflectionData
Extracted from _refln loops (X-ray diffraction data).
DictionaryEntry
Extracted from CCD (Chemical Component Dictionary) entries — ideal coordinates, bond tables, and chemical metadata.
Ops — Transforms and Analysis
The ops module provides coordinate manipulation, structural
validation, and bond inference.
ops::transform — Coordinate Operations
Filtering
// Keep only standard amino acid atoms
let protein = protein_only(&coords);
// Keep only backbone atoms (N, CA, C)
let backbone = backbone_only(&coords);
// Keep only heavy atoms (non-hydrogen)
let heavy = heavy_atoms_only(&coords);
// Filter by residue name predicate
let filtered = filter_residues(&coords, |name| name == "ALA");
Backbone extraction
// Extract CA chains as Vec<Vec<Vec3>>
let chains = extract_backbone_chains(&coords);
// Get all CA positions as flat Vec<Vec3>
let cas = extract_ca_positions(&coords);
Alignment
Kabsch algorithm for optimal rigid-body superposition:
let (aligned, rmsd) = kabsch_alignment(&mobile, &reference);
// With uniform scaling
let (aligned, rmsd) = kabsch_alignment_with_scale(&mobile, &reference);
// Binary COORDS alignment (convenience wrapper)
let aligned_bytes = align_coords_bytes(&mobile_bytes, &reference_bytes)?;
Interpolation
Smooth coordinate transitions for animation:
let interpolated = interpolate_coords(&start, &end, t); // t in [0, 1]
// Collapse-expand: shrink to centroid then expand to target
let interp = interpolate_coords_collapse(&start, &end, t);
Atom lookup
let pos = get_atom_position(&coords, chain_id, res_num, "CA");
let (pos, name) = get_closest_atom_for_residue(&coords, chain, res, point);
ops::validation — Completeness Checks
let report = completeness_report(&coords);
let has_bb = has_complete_backbone(&coords);
let counts: AtomCounts = atom_counts(&coords);
ops::bond_inference — Distance-Based Bonds
Infer covalent bonds from interatomic distances using element-specific covalent radii:
let bonds: Vec<InferredBond> = infer_bonds(&coords, DEFAULT_TOLERANCE);
// InferredBond { atom_a, atom_b, order: BondOrder }
Used as a fallback for ligands and cofactors that lack explicit bond tables.
Render — Visualization Data
The render module extracts GPU-ready data from Coords and
MoleculeEntity values. It serves as the bridge between molex’s
canonical types and rendering engines like viso.
RenderCoords
The primary output type, containing separated backbone and sidechain data with bond connectivity:
let render = RenderCoords::from_entity(&entity, is_hydrophobic, get_bonds);
Fields
| Field | Type | Description |
|---|---|---|
backbone_chains | Vec<Vec<Vec3>> | N-CA-C position triples per chain |
backbone_chain_ids | Vec<u8> | Chain ID for each backbone chain |
backbone_residue_chains | Vec<Vec<RenderBackboneResidue>> | Full N/CA/C/O per residue |
sidechain_atoms | Vec<RenderSidechainAtom> | Non-backbone heavy atoms |
sidechain_bonds | Vec<(u32, u32)> | Sidechain bond pairs (indices into sidechain_atoms) |
backbone_sidechain_bonds | Vec<(Vec3, u32)> | CA→CB connections |
all_positions | Vec<Vec3> | Every atom position (for bounding box, picking) |
Construction methods
from_entity()— from aMoleculeEntitywith topology callbacksfrom_coords_with_topology()— from rawCoordswith callbacksfrom_coords()— minimal extraction (no bonds or hydrophobicity)
Queries
render.get_atom_position(residue_idx, "CB") // Option<Vec3>
render.find_closest_atom(residue_idx, point) // Option<(Vec3, String)>
render.ca_positions() // Vec<Vec3>
render.residue_count() // usize
render::backbone
Backbone-specific extraction from MoleculeEntity:
BackboneData— chains as flat CA arrays + chain IDsca_positions_from_chains()— extract CA positions from N-CA-C chains
render::sidechain
Sidechain extraction with bond topology:
SidechainAtoms— atom positions, names, bonds, hydrophobicitySidechainAtomData— per-atom data struct
extract_sequences()
Extract amino acid sequences from Coords:
let (full_sequence, chain_sequences) = extract_sequences(&coords);
// full_sequence: "MQIFVKTL..."
// chain_sequences: vec![(b'A', "MQIFVKTL..."), (b'B', "...")]
Secondary Structure
The secondary_structure module classifies protein residues into
helix, sheet, coil, and turn based on backbone geometry.
SSType
pub enum SSType {
Helix, // alpha-helix (H)
Sheet, // beta-strand (E)
Coil, // unstructured (C)
Turn, // hydrogen-bonded turn (T)
}
Implements From<char> for Q8-style single-letter codes and
Display for the reverse mapping.
DSSP algorithm (dssp submodule)
The primary classification method. Computes hydrogen bond energies from backbone N-H…O=C geometry and assigns secondary structure based on the standard DSSP criteria.
use molex::secondary_structure::dssp;
// From a MoleculeEntity
let ss: Vec<SSType> = dssp::from_entity(&entity);
// From a Q8 string (e.g., from PDB HELIX/SHEET records)
let ss: Vec<SSType> = dssp::from_string("HHHHCCCEEEECCC");
Auto-detection (auto submodule)
Fallback method using dihedral angle (phi/psi) ranges when full DSSP is not needed:
use molex::secondary_structure::auto::detect;
let ss: Vec<SSType> = detect(&backbone_residues);
Resolution and override
The resolve() function merges DSSP output with optional per-entity
Q8 overrides:
use molex::secondary_structure::{resolve, DetectionInput};
let input = DetectionInput { entity, override_q8: Some("HHHCCCEEE") };
let ss = resolve(&input);
This is the entry point used by viso’s scene metadata pipeline.
FFI — C Bindings
The ffi module provides extern "C" functions for integration with
C and C++ applications. These are re-exported by foldit-runner for
inclusion in its generated C header.
Functions
coords_from_backbone
CoordsResult coords_from_backbone(
const float* positions, // [x,y,z] × n_atoms
uint32_t n_atoms,
const uint8_t* chain_ids,
const int32_t* res_nums,
const char (*res_names)[4], // 3-letter codes, null-padded
const char (*atom_names)[5] // 4-letter codes, null-padded
);
Constructs COORDS01 bytes from raw arrays. Returns a CoordsResult
containing either serialized bytes or an error string.
coords_free_result
Free memory allocated by coords_from_backbone.
coords_free_string
Free an error string from a CoordsResult.
CoordsResult
typedef struct {
const uint8_t* data; // COORDS01 bytes (null on error)
uint32_t len; // byte length
const char* error; // error message (null on success)
} CoordsResult;
Usage from C++
#include "molex.h"
auto result = coords_from_backbone(
positions.data(), n_atoms,
chain_ids.data(), res_nums.data(),
res_names.data(), atom_names.data()
);
if (result.error) {
fprintf(stderr, "Error: %s\n", result.error);
coords_free_string(result.error);
} else {
// Use result.data[0..result.len]
coords_free_result(result);
}
Python Bindings
molex provides Python bindings via PyO3, enabled with the python
feature flag. The module is built with maturin.
Installation
cd crates/molex
maturin develop --release --features python
Core functions
pdb_to_coords(pdb_string) → bytes
Parse a PDB string and return COORDS01 binary bytes.
mmcif_to_coords(cif_string) → bytes
Parse an mmCIF string and return COORDS01 binary bytes.
coords_to_pdb(coords_bytes) → str
Convert COORDS01 bytes back to a PDB-format string.
deserialize_coords(coords_bytes) → dict
Deserialize COORDS01 bytes into a Python dictionary with NumPy arrays:
result = molex.deserialize_coords(coords_bytes)
# result["num_atoms"]: int
# result["x"], result["y"], result["z"]: np.ndarray[f32]
AtomWorks adapters
For ML model pipelines that use Biotite AtomArray objects:
entities_to_atom_array(assembly_bytes) → AtomArray
Convert ASSEM01 bytes to a Biotite AtomArray with standard and AtomWorks-specific annotations (entity_id, mol_type, chain_type).
entities_to_atom_array_plus(assembly_bytes) → AtomArray
Like entities_to_atom_array but also populates bonds via distance
inference.
atom_array_to_entities(atom_array) → bytes
Convert a Biotite AtomArray back to ASSEM01 bytes.
entities_to_atom_array_parsed(assembly_bytes, filename) → AtomArray
Convert via the full AtomWorks cleaning pipeline (leaving group removal, charge correction, missing atom imputation).
parse_file_to_entities(path) → bytes
Parse a structure file (PDB/mmCIF) directly to ASSEM01 bytes.
parse_file_full(path) → AtomArray
Parse a structure file through the full AtomWorks pipeline.
coords_to_atom_array(coords_bytes) → AtomArray
Convert single-molecule COORDS01 bytes to AtomArray.
coords_to_atom_array_plus(coords_bytes) → AtomArray
Like coords_to_atom_array with bond inference.
atom_array_to_coords(atom_array) → bytes
Convert AtomArray to single-molecule COORDS01 bytes.