Skip to main content

Rustina

Rustina is a molecular-docking engine written in Rust. It provides Vina and Vinardo empirical scoring, an exhaustive CPU 3D FFT correlation engine, experimental batched GPU Monte Carlo search, native GNINA-compatible CNN rescoring, and a learned GNN pose ranker. The supported interfaces are Python and the command-line program.

Rustina v0.6 targets Linux x86-64 and Python 3.9 or newer.

Docking Engines

Rustina supports two distinct docking engines:

  • Correlation Engine (--engine correlation / engine="correlation"): (Recommended) Exhaustive translational enumeration using 3D FFT correlation across deterministic SO(3) orientations. Solves the global translational landscape exactly, refines the leading multi-torsion basins, and ranks poses using a fused CNN and learned GNN ranker (z-mean). Completely deterministic and fast (<1 s/target on multi-core CPU).

  • Monte Carlo Engine (--engine mc / engine="mc"): Traditional iterated local search with empirical scoring, adaptive step sizing, and local optimization (RILC, BFGS, LBFGS). Supports qvina2 (default), rustina, vina, atlas, hybrid, and experimental gpu search modes.

Search policy under the MC engine:

  • qvina2 is the default MC search policy. It uses the Vina budget with the official QuickVina 2 BFGS-history significance test.
  • rustina is the opt-in optimized CPU policy with capped steps, LBFGS, early termination, restart/polish-reuse heuristics, and torsion-adaptive run scaling (8 runs for rigid ligands, 12 for mid, 16 for flexible).
  • atlas is the opt-in interaction-field and torsion-beam search.
  • hybrid merges the native and ATLAS basins.
  • vina follows the AutoDock Vina 1.2.3 Monte Carlo/BFGS policy.
  • gpu is the experimental wgpu search engine. It requires engine="mc"; rustina.screen(..., scheduler="ligand") submits valid ligands together and reuses the receptor maps. gpu_lanes overrides its default 256 lanes in both the Python API and CLI (--gpu-lanes).

Compatibility modes reject custom steps, BFGS iteration counts, and RILC. Use search_mode="rustina" for experimental optimizer or budget combinations.

Rustina also includes opt-in template docking for congeneric ligands through dock_reference() and screen_reference().

Installation

Install the Python package:

python -m pip install rustina

Ligand preparation is an optional extra because it installs RDKit, molscrub, Meeko, SciPy, and Gemmi:

python -m pip install "rustina[prep]"

For a source checkout, build a production extension with:

maturin develop --release

Python API

Inputs to docking and scoring are prepared PDBQT paths or raw PDBQT strings. CNN rescoring defaults to GNINA's distilled fast model. Set cnn="default" for Rustina's bundled GNINA 1.3 ensemble, skip_cnn=True to disable rescoring, or supply a native weight path through cnn.

import rustina

poses = rustina.dock(
    receptor="data/receptor.pdbqt",
    ligand="data/ligand.pdbqt",
    cx=-14.0,
    cy=18.0,
    cz=-15.0,
    sx=14.0,
    sy=18.0,
    sz=15.0,
    runs=8,
    threads=8,
    seed=42,
    direct_refine_candidates=8,
)
print(poses[0]["affinity"], poses[0]["cnn_score"])

cnn_rotations (CLI --cnn-rotations, 1-24, default 1) averages the CNN pose score over that many orientations of the voxel grid, the equivalent of GNINA's --cnn_rotations. Only the rescore is affected: the search, the poses, and every empirical number are identical at any setting. The default of 1 is deliberate. Measured over 361 redocking targets by rescoring stored poses at all 24 orientations, averaging changed the chosen pose on 4 of 361 targets and never improved one, while costing about 7 s per target on six cores. See CLAUDE.md for the measurement.

The low-level CLI and Python API require an explicit pocket center and size. The product-style benchmark driver defaults to ligand-sized dynamic boxes, using the crystallographic heavy-atom extent plus adaptive padding max(4 Å, 8 Å - 0.2 Å × torsions). The fixed-25 release qualification remains explicitly pinned to its published protocol.

Screen multiple ligands while reusing the receptor grid:

results = rustina.screen(
    receptor="data/receptor.pdbqt",
    ligands=["ligand-1.pdbqt", "ligand-2.pdbqt"],
    cx=-14.0,
    cy=18.0,
    cz=-15.0,
    sx=14.0,
    sy=18.0,
    sz=15.0,
    threads=8,
)

Experimental negative-image screening compiles typed pocket hotspots once, generates rigid pharmacophore alignments, and selects/refines them with the atom-specific Vina grids. It is a retrieval prototype, not a replacement for final docking:

pocket = rustina.prepare_negative_image(
    "data/receptor.pdbqt",
    cx=-14.0, cy=18.0, cz=-15.0,
    sx=14.0, sy=18.0, sz=15.0,
)
matches = pocket.screen(["ligand-1.pdbqt", "ligand-2.pdbqt"], threads=6)

# Pose-generation diagnostics: refine and serialize up to 32 distinct basins.
candidates = pocket.screen_candidates(
    "ligand-1.pdbqt", limit=16, refine_candidates=32
)

Each successful record reports physical grid energy, pharmacophore coverage, clash diagnostics, pre/post-refinement transforms, selection provenance, and a ready-to-score PDBQT pose. Malformed ligands return an isolated {"error": ...} record. Use scripts/benchmark_negative_image.py for versioned teacher-recall experiments; do not interpret the prototype score as experimental binding affinity.

Before promoting RIFT as a docking prescreen, run scripts/run_rift_pose_qualification.py. It evaluates randomized crystal, single-ETKDG, and eight-ETKDG inputs on the fixed-box Astex diagnostic panel, reporting raw proposal RMSD separately from grid-refined RMSD and physical PoseBusters validity. Qualification and retrieval benchmark drivers print configuration/target progress as they run and keep resumable raw results under scratch/.

Optional ligand preparation accepts SMILES, structure files/blocks, or an RDKit molecule:

states = rustina.prepare_ligand("CC(=O)Nc1ccc(O)cc1", random_seed=42)
print(states[0]["pdbqt"])

Template docking

Template docking is a supported, opt-in v1 feature for congeneric series when a ligand with known coordinates is already in the receptor coordinate frame. It requires the prep extra because maximum-common-substructure matching is performed with RDKit:

poses = rustina.dock_reference(
    receptor="receptor.pdbqt",
    ligand="query.sdf",
    reference="co-crystal-ligand.sdf",
    cx=-14.0,
    cy=18.0,
    cz=-15.0,
    sx=14.0,
    sy=18.0,
    sz=15.0,
    runs=8,
    seed=42,
)
print(
    poses[0]["relaxed_core_rmsd"],
    poses[0]["physical_score"],
    poses[0]["reference_satisfied"],
)

The mapped heavy-atom core is restrained by a soft flat-bottom potential during search, followed by a short unrestrained relaxation. Results report constrained and relaxed core RMSD, physical and guided scores, the restraint penalty, mapping identity, both MCS coverage fractions, and whether the relaxed core remains within the default 1.0 Angstrom satisfaction threshold.

Automatic guidance requires at least six mapped heavy atoms and 50% query coverage. An explicit atom_map can define a smaller anchor. Rustina fails rather than silently switching to free docking when automatic guidance does not meet these gates. screen_reference() applies the same workflow to a series while reusing one receptor grid.

Up to eight symmetry-distinct MCS mappings are evaluated deterministically. The exact requested run budget is distributed across them globally; mappings do not multiply the run count. Candidates are merged and deduplicated before one CNN rescore using the requested cnn_pool_size.

Timing smoke test

A release-mode 5SAK_ZRY methyl-analog smoke test used four total runs, four mapping hypotheses, empirical scoring, matched pose-pool budgets, and three seeds. Median wall times were:

CPU threads Normal docking Template docking Difference
2 0.711 s 0.732 s +3%
8 0.620 s 0.682 s +10%

The two-thread end-to-end template call, including MCS generation and final relaxation, took 0.758 s median. These numbers characterize one small smoke case, not expected performance across ligand series. The solved-congeneric qualification requirements are documented in docs/benchmarks/REFERENCE_DOCKING_PROTOCOL.md.

Template docking assumes the reference and target receptor use the same coordinate frame. It does not align receptor structures or provide shape-only or pharmacophore guidance. 5SAK_ZRY demonstrates restraint behavior and physical validity only; its methyl analog has no experimental pose and is not an accuracy benchmark.

rustina.build_profile() returns debug or release. Performance results are valid only when the actually imported extension reports release.

Command line

# Correlation engine (recommended, exact FFT global enumeration):
rustina dock \
  --engine correlation \
  --receptor data/receptor.pdbqt \
  --ligand data/ligand.pdbqt \
  --output docked.pdbqt \
  --cx -14 --cy 18 --cz -15 \
  --sx 14 --sy 18 --sz 15 \
  --scoring vinardo \
  --ranker ranker.bin \
  --ranker-graph ligand.ranker-graph.json \
  --num-modes 8

# Monte Carlo engine (iterated local search):
rustina dock \
  --engine mc \
  --receptor data/receptor.pdbqt \
  --ligand data/ligand.pdbqt \
  --output docked.pdbqt \
  --cx -14 --cy 18 --cz -15 \
  --sx 14 --sy 18 --sz 15 \
  --runs 8 --threads 8 --seed 42

rustina score \
  --receptor data/receptor.pdbqt \
  --ligand data/ligand.pdbqt \
  --cnn

Use rustina dock --help and rustina score --help for the complete supported options.

Learned Pose Ranker

The v2 pose ranker is a native GNN that rescores candidate poses. It requires native Vinardo docking, a model checkpoint (ranker.bin or JSON), and a ligand graph sidecar generated by rustina.prepare_ligand or scripts/export_ranker_ligand_graph.py.

# 1. Generate ligand PDBQT and graph sidecar in one step:
python -c "import rustina; res = rustina.prepare_ligand('CCO')[0]; open('lig.pdbqt','w').write(res['pdbqt']); open('lig.graph.json','w').write(res['ranker_graph_json'])"

# 2. Dock with correlation engine + ranker:
rustina dock \
  --engine correlation \
  --receptor receptor.pdbqt --ligand lig.pdbqt \
  --cx -14 --cy 18 --cz -15 --sx 14 --sy 18 --sz 15 \
  --scoring vinardo \
  --ranker ranker.bin --ranker-graph lig.graph.json

Reproducibility and benchmarks

Docking is deterministic when seed is provided. Release benchmarks must record the Rustina version, build profile, input dataset revision, complete arguments, CPU model, and raw per-target results. Historical pre-v1 research is preserved by the pre-v1-research tag; v1 benchmark qualification lives under docs/benchmarks/.

PoseBusters + Astex Benchmark

All numbers below come from a single binary (97d0dd73), seed 42, budget 8, 393 targets (308 PoseBusters + 85 Astex), pinned to six physical cores on a Ryzen 5 5600X. Wilson 95% intervals are on the all-target denominator; at ~390 targets the minimum detectable difference is roughly ±4 points, so do not read a smaller gap as real without a paired test.

Superseded numbers: this section previously reported 79.1% over "485 evaluated targets" and 68.6% over "433" — both larger than the 393 targets that exist. Those rows pooled records from several different binaries into one figure. See docs/benchmarks/MERGE_QUALIFICATION_RESULTS.md.

Controlled protocol — the headline

Generated ETKDGv3+UFF start conformers and the pinned fixed 25 Å box; crystallographic coordinates are evaluation-only. This is the protocol comparable to published PoseBusters results.

Engine Mode RMSD<=2A (95% CI) PB-valid Mean s
rustina fast 53.7% (48.7-58.6) 95.7% 2.18
rustina_qvina2 fast 63.9% (59.0-68.5) 96.9% 4.83

On this protocol the qvina2 compatibility mode outperforms native rustina by 10.2 points — but it also takes 2.2x as long at the same --runs 8, so an unknown part of that gap is simply more search. A budget-matched comparison has not been run. Earlier releases claimed native mode surpassed qvina2; that comparison was made on a ligand-sized box, where the two modes differ by 2.6 points, which is inside the noise floor.

Experimental protocol — a labelled upper bound, not a headline

Crystal ligand conformers fed as docking input and a box sized from the crystal ligand extent. Both leak the answer, so this runs about 28 points high. It is published because it is the corpus used for offline re-ranking research, not because it is a fair number.

Engine Mode RMSD<=2A Top-5 <=2A PB-valid Any pose <=2A Mean s
rustina fast 81.4% 92.6% 98.0% 97.5% 1.84

The gap between 81.4% top-1 and the 97.5% sampling ceiling is the engine's real limitation: near-native poses are usually generated and then ranked below #1.

CNN fast is the recommended mode: it costs about 1.1x empirical. CNN default (the GNINA 1.3 ensemble) is marginally better and 1.3-1.9x slower.

Rustina is research software. Docking scores and predicted poses are not a substitute for experimental evidence or clinical decision-making.

Attribution and license

Rustina is MIT licensed. Its algorithms and bundled CNN parameters build on AutoDock Vina, Smina, QuickVina2, GNINA, Vina-GPU, and related published work. See THIRD_PARTY_NOTICES.md and CITATION.cff for provenance and citations. Model conversion is documented in docs/models.md.

Release files for rustina 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for rustina 0.6.0
File Size Uploaded
rustina-0.6.0.tar.gz 14.5 MB Details

Built distribution (wheel)

Table of built distributions (wheels) for rustina 0.6.0
File Interpreter ABI Platform
rustina-0.6.0-cp39-abi3-manylinux_2_28_x86_64.whl CPython 3.9 abi3 Linux glibc 2.28+ x86-64 Details

Total release size: 25.0 MB

Release files / rustina-0.6.0.tar.gz

Download URL rustina-0.6.0.tar.gz
Size 14.5 MB
Tags Source
SHA-256 checksum
How to use checksums
4baf24395f509ec800326b42db51e1033e591c86353b01b59e6d0de8062ed42b
BLAKE2b-256 checksum
How to use checksums
894052722f943922a3227dfe69f35c965d4280f95869e0b0f0d4474cdb22e295
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release files / rustina-0.6.0-cp39-abi3-manylinux_2_28_x86_64.whl

Download URL rustina-0.6.0-cp39-abi3-manylinux_2_28_x86_64.whl
Size 10.6 MB
Tags CPython 3.9 Linux glibc 2.28+ x86-64 abi3
SHA-256 checksum
How to use checksums
3d7e381be835c83d4a51c26db854606b31d5ff3aadc17ae2216caf1fc04a5839
BLAKE2b-256 checksum
How to use checksums
d5ec0ac30852605e140ebbd414a57c2d4fb8e309c44a468a6f15e003448ca848
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 1, 2026.

Transparency log

Release history Release notifications | RSS feed

0.7.0

2 release files

This release

0.6.0 This release

2 release files

0.5.0

2 release files

0.4.0

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page