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

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

TypeDescription
CoordsFlat atom arrays: positions, names, chains, residues, elements
MoleculeEntityA classified molecule (protein, ligand, etc.) with its Coords
RenderCoordsBackbone chains + sidechain atoms, ready for GPU consumption
SSTypeSecondary structure classification (helix, sheet, coil, turn)
DensityMap3D electron density grid from MRC/CCP4 files

Design principles

  1. Zero-copy where possible. Parsing produces owned data, but transforms operate on slices and iterators.
  2. Format-agnostic core. Coords and MoleculeEntity carry no format-specific metadata — adapters handle the translation.
  3. Embeddable. No filesystem, network, or GPU dependencies in the core. Optional python feature 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

FeatureDescription
pythonEnable 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

ModuleResponsibility
typesCoords, MoleculeEntity, DensityMap, binary serialization
adaptersFormat I/O: PDB, mmCIF, BinaryCIF, DCD, MRC, AtomWorks
cifLow-level CIF/STAR parser with typed extractors
opsCoordinate transforms, Kabsch alignment, bond inference, validation
renderBackbone/sidechain extraction, sequence extraction
secondary_structureDSSP algorithm, SS type classification
ffiextern "C" functions for C/C++ integration
pythonPyO3 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 — inverse
  • serialize_assembly(&entities) → Vec<u8> — ASSEM01 multi-entity format
  • deserialize_assembly(&bytes) → Vec<MoleculeEntity> — inverse

types::entity — Molecule Classification

MoleculeEntity

A classified molecule with its atom data:

  • entity_id: u32 — unique identifier
  • molecule_type: MoleculeType — Protein, DNA, RNA, Ligand, Ion, etc.
  • kind: EntityKindPolymer(PolymerData) or AtomSet(AtomSet)

MoleculeType

pub enum MoleculeType {
    Protein, DNA, RNA, Ligand, Ion, Water, Lipid, Cofactor, Solvent,
}

Key functions

  • split_into_entities(&coords) → Vec<MoleculeEntity> — classify and group
  • merge_entities(&[MoleculeEntity]) → Coords — flatten back to atoms
  • classify_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
  • f32 voxel 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:

  • Document contains Vec<Block>
  • Each Block has name, categories (loop tables), and pairs (key-value entries)
  • Category holds Column data (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

FieldTypeDescription
backbone_chainsVec<Vec<Vec3>>N-CA-C position triples per chain
backbone_chain_idsVec<u8>Chain ID for each backbone chain
backbone_residue_chainsVec<Vec<RenderBackboneResidue>>Full N/CA/C/O per residue
sidechain_atomsVec<RenderSidechainAtom>Non-backbone heavy atoms
sidechain_bondsVec<(u32, u32)>Sidechain bond pairs (indices into sidechain_atoms)
backbone_sidechain_bondsVec<(Vec3, u32)>CA→CB connections
all_positionsVec<Vec3>Every atom position (for bounding box, picking)

Construction methods

  • from_entity() — from a MoleculeEntity with topology callbacks
  • from_coords_with_topology() — from raw Coords with callbacks
  • from_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 IDs
  • ca_positions_from_chains() — extract CA positions from N-CA-C chains

render::sidechain

Sidechain extraction with bond topology:

  • SidechainAtoms — atom positions, names, bonds, hydrophobicity
  • SidechainAtomData — 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.