Rustina
Linux x86-64 molecular docking in Rust, with a command-line interface and Python bindings. Rustina combines a QuickVina2-style Monte Carlo search on Vinardo grids with native inference of GNINA's fast CNN, fused with the empirical energy to pick the final pose. It needs no GPU, no PyTorch and no network access at run time.
| Component | Default | Alternatives |
|---|---|---|
| Search | qvina2-active: staged QuickVina2 runs (16), no moves on torsions the scoring function cannot see |
qvina2-race, qvina2, vina (CPU), gpu (wgpu batched Monte Carlo) |
| Scoring | vinardo |
vina, ad4 (charge-aware AutoDock 4.2) |
| Pose ranking | GNINA fast CNN on the 8 best candidates, fused with the energy: logit(p) − 0.25 (E − median E) / max(IQR E, 1 kcal/mol) |
--skip-cnn (energy only) |
| Receptor waters | kept as supplied | --waters strip, --waters toggle |
| Affinity | CNN pK of the returned pose | optional ensemble of GNINA heads (--cnn-affinity-models) |
Benchmarks
Pose prediction with the default setup: crystal-ligand box + 8 Å per axis (minimum
18 Å), receptors from rustina.prepare_receptor, ligands from the supplied start
conformer via rustina.prepare_ligand(skip_gen3d=True), seed 1. Success is a top-1
pose within 2 Å RMSD of the crystal ligand, evaluated with PoseBusters 0.6.5
(redock, all crystal instances); "valid" also requires its physical checks.
Failures, including preparation failures, count as misses. Published rows use the
same inputs and the same RMSD criterion.
| Set | Rustina | Rustina, valid | GNINA 1.3 | Glide SP | GOLD | AutoDock Vina | DiffDock |
|---|---|---|---|---|---|---|---|
| Astex Diverse (85) | 76.5% | 74.1% | 81.2%¹ | 63.5%¹ | 67.1%² | 57.6%² | 71.8%² |
| PoseBusters (428) | 61.7% | 60.3% | — | — | 51.2%² | 52.3%² | 37.9%² |
| PoseX self-docking (718) | 58.1% | 56.5% | 64.6%¹ | 48.3%¹ | — | 40.0%¹ | — |
| PoseX cross-docking (1,312 pairs) | 47.6% | 46.9% | 53.1%¹ | 37.5%¹ | — | 28.1%¹ | — |
¹ PoseX (ICLR 2026); cross-docking is the per-target average. ² PoseBusters paper data, no post-processing.
Median docking time is 2.1 s per complex on 4 CPU threads (Astex, Ryzen 5 5600X). GNINA's lead
on the PoseX sets comes from its slower three-model CNN ensemble and from that
benchmark's tighter box convention (crystal ligand + 4 Å per side, no minimum);
Rustina's search already returns a near-native pose among its nine outputs for
79% of the PoseX self-docking cases (567/718). With a GPU, --search-mode gpu matches the
CPU default's accuracy about 11× faster: median 0.17 s per complex on Astex (77.6%) and
0.18 s on PoseBusters (61.7%, as on CPU).
Virtual screening on LIT-PCBA, all 15 targets (1,096 actives, 14,993 sampled inactives): mean AUROC 0.597 with the CNN affinity and EF1% 3.4 with CNN affinity × pose probability (Vinardo: 0.548 / 1.3). Published results on the full sets are GNINA 0.609 / 4.6 and Glide SP 0.532 / 3.4 (DrugCLIP, Table 3). LIT-PCBA remains hard for every structure-based score; heavy-atom count alone gives an AUROC of 0.567.
Details, protocols and per-target results are in docs/benchmarks.md.
Build
cargo build --release --locked
python -m pip install maturin
maturin build --release --locked --out target/wheels
python -m pip install target/wheels/*.whl
The Python package supports Python 3.9+. Docking and scoring need no Python
dependencies. Receptor and ligand preparation use pip install 'rustina[prep]'
(RDKit, Meeko, molscrub, gemmi).
Command line
Inputs are receptor and ligand PDBQT files with polar hydrogens and partial charges.
target/release/rustina dock -r data/receptor.pdbqt -l data/ligand.pdbqt \
--cx 0 --cy -5 --cz 0 --sx 12 --sy 16 --sz 12 --seed 42 \
--num-modes 9 -o target/docked.pdbqt
target/release/rustina score -r data/receptor.pdbqt -l data/ligand.pdbqt \
--scoring vinardo --cnn
--batch DIRECTORY docks a directory of ligands against one receptor prepared once
and writes a summary.csv. --diagnostics-json FILE records resolved settings and
every written pose's scores. dock --help and score --help list all options.
- Reproducibility. A seed makes CPU output identical across thread counts. GPU reproducibility also depends on the adapter and driver.
- Search budget. CPU budgets are fixed by the mode;
qvina2-activeandqvina2-raceneed at least 16 runs.--stepsand--bfgs-iterapply to GPU search. - Waters.
--waters fixed(default) keeps receptor waters,stripremoves them, andtoggledocks with and without them and keeps the result whose top pose has the higher CNN probability (single-ligand docking only, twice the cost). On PoseX cross-docking with ordered crystal waters the toggle gained 2.4 percentage points. - Inputs. Coordinates and charges must be finite, atom serials unique and branch
bonds valid; multi-model PDBQT uses its first model. Ligands with pseudo-water
Watoms are rejected.
Python
import rustina
receptor = rustina.prepare_receptor("protein.pdb", center=(0, -5, 0), size=(20, 20, 20))
ligand = rustina.prepare_ligand("CC(=O)Nc1ccc(O)cc1")[0]
poses = rustina.dock(
receptor["pdbqt"], ligand["pdbqt"],
cx=0, cy=-5, cz=0, sx=20, sy=20, sz=20, seed=42,
)
print(poses[0]["affinity"], poses[0]["cnn_score"], poses[0]["cnn_affinity"])
results = rustina.screen(receptor["pdbqt"], [ligand["pdbqt"]], cx=0, cy=-5, cz=0, sx=20, sy=20, sz=20)
dockreturns ranked poses with the empiricalaffinity(kcal/mol), the CNN pose probabilitycnn_scoreand CNN affinitycnn_affinity(pK).screenshares the receptor grids across a batch and returns the top pose per ligand in input order; a failed ligand carries anerrorstring. Paths and PDBQT text are both accepted.prepare_receptor(protein, center, size)crops whole residues around the box, removes waters (keep_waters=Truekeeps them) and crystallisation additives, and runs Meeko. Cofactors and ions Meeko cannot template, and observed atoms Meeko deletes from incomplete residues, are kept as element-typed atoms instead of failing or silently changing the pocket; tied histidine states, unbuildable residues and untemplated modified residues (e.g. selenomethionine) are retried. The returnedreportrecords every such change.prepare_ligand(smiles_or_file)enumerates protonation (pH 7.4) and tautomer states with molscrub and embeds them deterministically;skip_gen3d=Truekeeps supplied 3D coordinates and protonation.scoreandscore_cnnrescore an existing pose.
Optional affinity ensemble
Averaging the affinity heads of GNINA's default2018-architecture models gives small, consistent ranking gains over the fast model (congeneric series and LIT-PCBA; see the benchmarks). The models are not bundled; convert them from a GNINA checkout:
python scripts/convert_gnina_models.py /path/to/gnina/gninasrc/lib/models --affinity-pack models/
target/release/rustina dock ... --cnn-affinity-models models/
Python dock() and screen() accept cnn_affinity_models="models/". Results add
cnn_affinity_ensemble and cnn_affinity_ensemble_std; pose ranking is unchanged.
The cost is one fast-CNN evaluation per model and pose. See docs/models.md.
Seed poses from deep-learning docking
Poses from a deep-learning model can join Rustina's ranking. Each seed is refined by
Rustina's direct Vinardo minimisation, CNN-scored and ranked with the search's candidates
by the CNN-energy fusion plus a fixed bonus for seeds (--seed-bonus, default 2 logit
units); the search still runs, so a wrong seed can lose to a better Rustina pose.
Uni-Mol Docking V2, built in. --unimol-model FILE adds the poses that
Uni-Mol Docking V2 predicts for the ligand from 10
conformers. The network runs natively in Rust, on the CPU or on the GPU
(--unimol-device gpu, the default for --search-mode gpu); no PyTorch or RDKit is needed.
The weights (MIT) are not bundled: download upstream's unimol_docking_v2_240517.pt and
convert it once with NumPy.
python scripts/convert_unimol_model.py unimol_docking_v2_240517.pt unimol_docking_v2.bin
target/release/rustina dock -r receptor.pdbqt -l ligand.pdbqt ... \
--unimol-model unimol_docking_v2.bin [--unimol-reference crystal_ligand.sdf]
Uni-Mol's pocket is the docking box or, with --unimol-reference, upstream's box around a
reference ligand (its extent + 10 Å), which suits the model better. Python:
dock(..., unimol_model=..., unimol_reference=...); screen() takes the same arguments and
encodes the pocket once. Top pose within 2 Å:
| Set | Rustina | + Uni-Mol, docking-box pocket | + Uni-Mol, reference pocket |
|---|---|---|---|
| Astex (85) | 76.5% | 95.3% | 91.8% |
| Family-disjoint cohort, 2021–2025 (132) | 48.5% | 58.3% | 64.4% |
| PoseX cross-docking (1,312 pairs) | 45.9% | 63.9% | 68.7% |
| PoseX self-docking (717) | 58.4% | 72.0% | 78.7% |
| PoseBusters (428; held out) | 61.7% | 75.5% | 81.3% |
Seeds are cleared of the receptor clashes PoseBusters flags; 72.9% and 78.3% of PoseBusters
top poses are within 2 Å and pass all its checks (Rustina alone: 60.3%). Seeding adds 1.1 s
per complex with the network on the GPU (Astex median 3.2 s against 2.2 s; 1.4 s with
--search-mode gpu) and 4.7 s on the CPU; upstream's PyTorch pipeline took 9–10 s.
The seeding settings were chosen before any PoseBusters run, the clash relief after it.
With the reference pocket the results match those of upstream's own PyTorch pipeline used
as a seed. Uni-Mol was trained on PDBbind, which contains the Astex complexes. Details:
docs/benchmarks.md.
Other programs. --seed-poses seeds.pdbqt; Python dock(..., seed_poses=[...])
accepts PDBQT texts or paths, and poses refined from a seed carry seed_index.
rustina.seed_pose_from_sdf(pose_sdf, ligand_pdbqt) converts any program's SDF pose of the
ligand into a seed; scripts/unimol_seed_poses.py writes one from upstream's PyTorch
Uni-Mol.
Models and licenses
The bundled fast CNN (GNINA 1.3.2 all_default_to_default_1_3_1, Apache-2.0), the
optional Uni-Mol Docking V2 weights (MIT, not bundled) and their provenance are described
in docs/models.md. Attribution is in
THIRD_PARTY_NOTICES.md.
Development
screening.rs shares receptor preparation, docking, refinement and pose ranking
between the CLI and Python bindings. Search lives in optimize.rs, qvina.rs and
gpu/; empirical potentials in scoring.rs and ad4.rs; CNN inference in cnn.rs.
AGENTS.md describes working in the repository, and
docs/validation.md the protocol for evaluating changes.
docs/benchmark.md documents the search and ranking diagnostics harness.
cargo fmt --all --check
cargo clippy --all-targets --locked -- -D warnings
cargo test --all-targets --locked
Install the release wheel, then run python -m unittest discover -s /absolute/path/to/rustina/python/tests -v
from outside the checkout. GPU tests exercise the available adapter; an absent adapter
prints a skip message but counts as passing, so hardware validation must confirm that
they ran on a real GPU.
Tagged releases publish one Linux x86-64 Python wheel and a CLI archive, built in manylinux 2.28 and tested as installed packages before publishing.
Graphify is optional navigation tooling: scripts/setup_graphify.sh installs it and
scripts/graphify update . --force rebuilds the code graph under scratch/graphify.
Metadata
Release files for rustina 0.9.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| rustina-0.9.0-cp39-abi3-manylinux_2_28_x86_64.whl | CPython 3.9 | abi3 | Linux glibc 2.28+ x86-64 | Details |
Release files / rustina-0.9.0-cp39-abi3-manylinux_2_28_x86_64.whl
| Download URL | rustina-0.9.0-cp39-abi3-manylinux_2_28_x86_64.whl |
|---|---|
| Size | 3.6 MB |
| Tags | CPython 3.9 Linux glibc 2.28+ x86-64 abi3 |
|
SHA-256 checksum How to use checksums |
b4b4e4856f8d8173941c9870d4694a0e54fb9ec540f72ecc7b54ed5e56c55688
|
|
BLAKE2b-256 checksum How to use checksums |
abf9b63071204fe82933241ef746e4c531d66db9e05702067ecff8b197bfdae4
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.16
|
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 Oct 25, 2026.
Transparency logSecurity for rustina 0.9.0
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.
Full provenance
All 1 file in this release has been uploaded with provenance metadata.
Attestation for rustina-0.9.0-cp39-abi3-manylinux_2_28_x86_64.whl
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 Oct 25, 2026.
Transparency logA note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.