#tessellation #molecule #bioinformatics #voronoi #sasa

bin+lib voronota-ltr

Voronota-LT is an alternative version of Voronota for constructing tessellation-derived atomic contact areas and volumes

3 releases (breaking)

Uses new Rust 2024

0.4.0 Apr 2, 2026
0.3.0 Apr 1, 2026
0.1.0 Jan 22, 2026

#63 in Biology

MIT license

350KB
7.5K SLoC

CI

Voronota-LT in Rust

Experimental, native Rust port of voronota-lt originally written in C++. Replaces the previous bindings crate. Computes radical Voronoi tessellation of atomic balls constrained inside a solvent-accessible surface. Outputs inter-atom contact areas, solvent accessible surface (SAS) areas, and volumes.

Features

  • Pure, safe Rust
  • Basic radical tessellation (stateless)
  • Updateable tessellation for incremental updates (stateful)
  • Periodic boundaries
  • Per atom SASA and volume
  • Contacts-only mode (skips cells/SAS/volumes for ~30–50% speedup)
  • Groupings to avoid internal contacts
  • Parallel processing using Rayon - see benchmarks below
  • PDB, mmCIF, and XYZR input formats with auto-detection
  • Unit-tests and benchmarks carried over from the C++ side
  • Python bindings via PyO3
  • Rust API and CLI
  • Based on Voronota-LT v1.1.479 — reviewed against v1.1.492; all upstream bug fixes are already incorporated and new features (preliminary cutting planes, CAD-score-LT) are CLI-level additions not relevant to the core tessellation library

Installation

The port can be used either as a library for other projects, or as a basic CLI tool:

cargo install voronota-ltr

Rust API

use voronota_ltr::{Ball, Results, compute_tessellation};

let balls = vec![
    Ball::new(0.0, 0.0, 0.0, 1.5),
    Ball::new(3.0, 0.0, 0.0, 1.5),
    Ball::new(1.5, 2.5, 0.0, 1.5),
];

let result = compute_tessellation(&balls, 1.4, None, None, false);

// Per-ball SAS areas and volumes (indexed by ball)
// Returns None for atoms without contacts (lonely atoms)
let sas_areas: Vec<Option<f64>> = result.sas_areas();
let volumes: Vec<Option<f64>> = result.volumes();

// Total SAS area
let total_sas: f64 = result.total_sas_area();

// Detailed contact and cell data
for contact in &result.contacts {
    println!("Contact {}-{}: area={:.2}", contact.id_a, contact.id_b, contact.area);
}

Contacts only (no cells/SAS/volumes)

When only contact areas are needed (e.g. for contact energy calculations), compute_contacts_only skips solid angle, volume, and cell computation:

use voronota_ltr::{Ball, compute_contacts_only};

let balls = vec![
    Ball::new(0.0, 0.0, 0.0, 1.5),
    Ball::new(3.0, 0.0, 0.0, 1.5),
    Ball::new(1.5, 2.5, 0.0, 1.5),
];

// Optional group IDs to restrict to inter-group contacts
let groups = vec![0, 0, 1];
let contacts = compute_contacts_only(&balls, 1.4, None, Some(&groups));

for c in &contacts {
    println!("Contact {}-{}: area={:.2}", c.id_a, c.id_b, c.area);
}

With periodic boundary conditions

use voronota_ltr::{Ball, PeriodicBox, compute_tessellation};

let balls = vec![Ball::new(0.0, 0.0, 0.0, 1.5)];
let pbox = PeriodicBox::from_corners((0.0, 0.0, 0.0), (10.0, 10.0, 10.0));

let result = compute_tessellation(&balls, 1.4, Some(&pbox), None, false);

Updateable tessellation

For simulations where only a few spheres change position each step, UpdateableTessellation provides efficient incremental updates:

use voronota_ltr::{Ball, UpdateableTessellation};

let mut balls = vec![
    Ball::new(0.0, 0.0, 0.0, 1.0),
    Ball::new(2.0, 0.0, 0.0, 1.0),
    Ball::new(4.0, 0.0, 0.0, 1.0),
];

// Create with backup support for undo
let mut tess = UpdateableTessellation::with_backup();
tess.init(&balls, 1.0, None);

// Move first sphere
balls[0].x += 0.1;
tess.update_with_changed(&balls, &[0]);  // Only recompute affected contacts

// Get results
let summary = tess.summary();
println!("Total contacts: {}", summary.contacts.len());

// Undo last update
tess.restore();

Command Line Tool

Supports PDB, mmCIF, and XYZR input formats (auto-detected from extension or content):

# PDB / mmCIF / XYZR input
voronota-ltr structure.pdb --probe 1.4

# Save JSON output to file instead of stdout
voronota-ltr structure.pdb -o results.json

# Exclude heteroatoms (HETATM records)
voronota-ltr structure.pdb --exclude-heteroatoms

# Include hydrogen atoms (excluded by default)
voronota-ltr structure.pdb --include-hydrogens

# Custom radii file (format: residue atom radius per line)
voronota-ltr structure.pdb --radii-file custom_radii.txt

# With periodic boundary conditions
voronota-ltr structure.xyzr --periodic-box-corners 0 0 0 100 100 100

Custom selections

Group atoms using VMD-like selection syntax to filter contacts. Only inter-group contacts are computed.

# Protein-ligand interface (5IN3: galactose-1-phosphate uridylyltransferase)
voronota-ltr 5IN3.cif -s "protein" "resname G1P H2U"

# Homodimer interface
voronota-ltr 5IN3.cif -s "chain A" "chain B"

See the Selection Language section for full syntax.

Loading JSON output in Python

If using the CLI, results can be loaded from JSON:

import json

with open("results.json") as f:
    data = json.load(f)

# Per-ball data (indexed by ball, None for atoms without contacts)
sas_areas = data["sas_areas"]  # list of float or None
volumes = data["volumes"]  # list of float or None

# Totals
total_sasa = data["total_sas_area"]
total_volume = data["total_volume"]
total_contact_area = data["total_contact_area"]

# Contact details
for contact in data["contacts"]:
    print(f"Contact {contact['id_a']}-{contact['id_b']}: area={contact['area']:.2f}")

PyMOL visualization

Generate a Python script to visualize contact surfaces in PyMOL:

voronota-ltr structure.pdb --inter-chain-only --pymol contacts.py
pymol structure.pdb contacts.py

This creates three CGO objects: contacts_balls (cyan spheres), contacts_faces (yellow contact surfaces), and contacts_wireframe (red boundary lines).

Python Interface

Install directly from GitHub (requires a Rust toolchain):

pip install git+https://github.com/mlund/voronota-ltr.git

For local development, build and install using maturin:

pip install maturin
maturin develop --features python          # Development build
maturin develop --features python --release  # Optimized build

Both methods install the Python module and CLI binary.

Run tests:

python -m unittest discover -s tests -p "test_*.py"

Basic usage; input balls can be tuples, dicts, or NumPy arrays.

from voronota_ltr import compute_tessellation

result = compute_tessellation(
    balls=[(0, 0, 0, 1.5), (3, 0, 0, 1.5), (1.5, 2.5, 0, 1.5)],
    probe=1.4,
)

print(f"Total SAS area: {result['total_sas_area']:.2f}")
print(f"Total volume: {result['total_volume']:.2f}")

for contact in result["contacts"]:
    print(f"Contact {contact['id_a']}-{contact['id_b']}: area={contact['area']:.2f}")

With periodic boundaries:

# Orthorhombic box from corner coordinates
result = compute_tessellation(
    balls=balls,
    probe=1.4,
    periodic_box={"corners": [(0, 0, 0), (50, 50, 50)]},
)

# Triclinic cell from lattice vectors
result = compute_tessellation(
    balls=balls,
    probe=1.4,
    periodic_box={"vectors": [(50, 0, 0), (0, 50, 0), (0, 0, 50)]},
)

With tessellation network output:

result = compute_tessellation(
    balls=balls,
    probe=1.4,
    with_cell_vertices=True,
)

for vertex in result["cell_vertices"]:
    print(f"Vertex at ({vertex['x']:.2f}, {vertex['y']:.2f}, {vertex['z']:.2f})")
    print(f"  On SAS: {vertex['is_on_sas']}")

From structure files

Compute tessellation directly from PDB, mmCIF, or XYZR files:

from voronota_ltr import compute_tessellation_from_file

# Basic usage
result = compute_tessellation_from_file("structure.pdb", probe=1.4)

# VMD-like selections for protein-ligand interface
result = compute_tessellation_from_file(
    "complex.pdb",
    probe=1.4,
    group_selections=["protein", "resname LIG"],
)

# Inter-chain contacts only
result = compute_tessellation_from_file(
    "dimer.cif",
    probe=1.4,
    group_selections=["chain A", "chain B"],
)

Solvent spheres

Generate weighted pseudo-solvent spheres on the exposed molecular surface:

from voronota_ltr import compute_solvent_spheres

spheres = compute_solvent_spheres(
    balls=[(0, 0, 0, 1.5), (3, 0, 0, 1.5)],
    probe=1.4,
    subdivision_depth=2,  # 0-4, controls sampling density (default: 2)
)

for s in spheres:
    print(f"Sphere at ({s['x']:.2f}, {s['y']:.2f}, {s['z']:.2f}), "
          f"weight={s['weight']:.3f}, parent={s['parent_index']}")

Selection Language

The -s/--selection flag accepts VMD-like selection syntax for defining atom groups. Only contacts between different groups are computed, which is useful for interface analysis.

Syntax

voronota-ltr file.pdb -s "selection1" "selection2" ...

At least two selections are required. Only contacts between atoms in different groups are computed.

Keywords

Keyword Description Example
chain Chain identifier(s) chain A B
resname Residue name(s) resname ALA GLY
resid Residue number(s) or range resid 1 to 100 or resid 1:100
name Atom name(s) with glob patterns name CA or name C*
protein Standard amino acids protein
backbone Protein backbone (C, CA, N, O) backbone
sidechain Protein sidechains (non-backbone) sidechain
nucleic Nucleic acid residues nucleic
hetatm HETATM records hetatm
hydrophobic Hydrophobic residues (ALA, VAL, LEU, ILE, MET, PHE, TRP, PRO, GLY) hydrophobic
aromatic Aromatic residues (PHE, TYR, TRP, HIS) aromatic
acidic Acidic residues (ASP, GLU) acidic
basic Basic residues (ARG, LYS, HIS) basic
polar Polar residues (SER, THR, ASN, GLN) polar
charged Charged residues (ASP, GLU, ARG, LYS) charged
all All atoms all
none No atoms none

Boolean operators

Combine selections with and, or, not, and parentheses:

# Sidechain-only dimer interface (exclude backbone)
voronota-ltr 5IN3.cif -s "chain A and sidechain" "chain B and sidechain"

# Protein-ligand contacts, excluding cryoprotectant (EDO)
voronota-ltr 5IN3.cif -s "protein and not resname EDO" "resname G1P H2U ZN"

Glob patterns

Atom and residue names support glob wildcards:

  • * matches any characters: name C* matches CA, CB, CG, etc.
  • ? matches single character: name ?A matches CA, NA, etc.
  • [abc] matches character class: name C[AG] matches CA or CG

Benchmarks

Run benchmarks with cargo bench. Performance on Apple M4 processor (10 cores) - the speedup is relative to single threaded runs:

Dataset Balls C++ (OpenMP) Rust (Rayon) C++ Speedup Rust Speedup
balls_cs_1x1 100 179 µs 79 µs 0.4x 0.8x
balls_2zsk 3545 14 ms 12 ms 5.1x 5.9x
balls_3dlb 9745 38 ms 30 ms 5.0x 5.9x

License

MIT License

Copyright (c) 2026 Kliment Olechnovic and Mikael Lund

Dependencies

~5–7.5MB
~150K SLoC