Skip to content

Rust Retrosynthesis with RENKIN

Most open-source retrosynthesis tools are Python packages built around RDKit and, often, a trained neural network. RENKIN takes a different approach: the whole engine — SMILES/SMARTS parsing, molecule canonicalization, retrosynthetic rule application, and A* search — is a single Rust crate with zero C/C++ dependencies.

Install

[dependencies]
renkin = "0.21"

or

cargo add renkin

Why Rust

  • No RDKit, no Boost, no C/C++ toolchain. Chemistry parsing/canonicalization comes from chematic, a pure-Rust cheminformatics crate. cargo build is the entire build story.
  • #![forbid(unsafe_code)] on every crate in the workspace — compiler-enforced, not just a style guideline.
  • One codebase, four targets. The same core search code compiles to a native CLI binary, a Rust library (cargo add renkin), a Python extension module (PyO3), and a WebAssembly module (wasm-bindgen) that runs entirely client-side in a browser — see WASM API and the live playground.

A Working Example

//! RENKIN Rust quickstart. Compiled and run as part of CI (see
//! .github/workflows/ci.yml) so this example can never silently drift from
//! the real `find_routes` API.

use renkin::chem_env::{ChemEnv, default_rules};
use renkin::search::{SearchConfig, find_routes};

fn main() -> anyhow::Result<()> {
    let env = ChemEnv::load("data/building_blocks.smi")?;
    let rules = default_rules();
    let config = SearchConfig {
        max_depth: 5,
        max_routes: 3,
        ..Default::default()
    };

    let (routes, _stats) = find_routes("CC(=O)Oc1ccccc1C(=O)O", &env, &rules, &config)?;
    println!("Routes found: {}", routes.len());
    for route in &routes {
        println!("Route (depth {}):", route.depth);
        for step in &route.steps {
            println!("  {} -> {}", step.target, step.precursors.join(" + "));
            println!("  via {}", step.rule);
        }
    }
    Ok(())
}

find_routes returns Result<(Vec<Route>, SearchStats)> — a tuple, not a bare Vec<Route> — and Route/ReactionStep fields like depth are plain struct fields, not methods. Full signature and types: Rust API reference.

Search Algorithm

RENKIN searches with A* / beam search over a set of retrosynthetic rules:

  • 22 hand-crafted, graph-based or SMIRKS-based rules covering common pharmaceutical disconnections (esters, amides, Suzuki, Heck, Wittig, sulfonamides, and more).
  • Up to 50k additional SMIRKS templates auto-extracted from USPTO-50k/MIT via rdchiral, loaded from a .smi file and weighted by training-set frequency (step_cost is discounted for high-frequency templates).
  • Every template — hand-crafted or extracted — has a stable template_id, independent of file order or extraction run, so external evidence (a DOI, a reported yield) can be durably attached to it — see Reaction Evidence Metadata.

h(molecule) for the A* heuristic defaults to an SA-Score-based estimate and is pluggable via the MoleculeValueEstimator trait; template ranking is pluggable via the ReactionPrior trait.

WASM: Running in the Browser

Because the whole engine is Rust with no OS/filesystem dependency in its search core, it compiles directly to WebAssembly:

wasm-pack build --target web --no-default-features

The live playground is this exact build running client-side — no server, no network call, no Python runtime. This is a real architectural difference from Python-based CASP tools built on RDKit/PyTorch, which can't currently compile to WASM at all.

When to Reach for the Rust API Directly

If you're calling RENKIN from a Rust service, embedding it in another tool, or need to avoid a Python/Docker runtime entirely (e.g. a CLI tool, a WASM module, or a resource-constrained deployment), the Rust API is the native surface — the Python and WASM bindings are thin wrappers over the same find_routes function documented here. If you're prototyping in a Python/Jupyter workflow instead, see the Python retrosynthesis guide.

Next Steps