Skip to content

Rust API

Core Types

ChemEnv

The chemical environment holding the building block database.

use renkin::chem_env::{ChemEnv, mol_from_smiles};

// Load from SMILES file (402 unique compounds when this file is present --
// `?` propagates an error if it isn't; ChemEnv::load itself has no fallback).
let env = ChemEnv::load("data/building_blocks.smi")?;

// Load from in-memory list
let env = ChemEnv::in_memory(&["CC(=O)O", "c1ccccc1", "Brc1ccccc1"]);

// Check if a molecule is in the stock
let mol = mol_from_smiles("CC(=O)O")?;
assert!(env.is_building_block(&mol));

SearchConfig

Configuration for the retrosynthesis search. Has a manual Default impl (not #[derive(Default)]) — always use ..Default::default() for fields you don't set explicitly, since new fields are added over time.

use renkin::search::SearchConfig;

let config = SearchConfig {
    max_depth: 5,      // maximum retrosynthetic depth
    max_routes: 3,     // maximum number of routes to return
    beam_width: 50,    // A* beam width (0 = unlimited)
    ..Default::default()
};

find_routes

Main search function. Returns (Vec<Route>, SearchStats), not a bare Vec<Route>.

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

let env = ChemEnv::load("data/building_blocks.smi")?;
let rules = default_rules();
let config = SearchConfig { max_depth: 5, ..Default::default() };

let (routes, stats) = find_routes("CC(=O)Oc1ccccc1C(=O)O", &env, &rules, &config)?;
println!("Found {} routes ({} nodes expanded)", routes.len(), stats.nodes_expanded);
for route in &routes {
    println!("Route depth: {}", route.depth); // `depth` is a plain field, not a method
}

A full, CI-compiled-and-run version of this example lives at examples/quickstart.rs:

//! 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(())
}

Reaction Rules

use renkin::chem_env::{default_rules, RetroRule};

// Get the default rule set (22 hand-crafted rules)
let rules = default_rules();

// Each rule has a stable template_id, name, and SMIRKS pattern
// (SMIRKS is empty for graph-based rules dispatched by name in apply_retro,
// e.g. "ester_cleavage", "amide_cleavage", "suzuki_retro")
for rule in &rules {
    println!("{} ({}): {}", rule.name, rule.template_id, rule.smirks);
}

// Apply a single rule to a molecule
use renkin::chem_env::{apply_retro, mol_from_smiles};
let mol = mol_from_smiles("CC(=O)Oc1ccccc1C(=O)O")?;
let ester_rule = rules.iter().find(|r| r.name == "ester_cleavage").unwrap();
let precursor_sets = apply_retro(&mol, ester_rule); // Vec<Vec<PrecursorMol>>

RetroRule has 5 fields: name, template_id (rule:<name> for hand-crafted rules, smirks-sha256:<hex> for extracted templates — see Template Evidence Metadata), smirks, weight, required_elements. A hand-written RetroRule { .. } literal needs ..Default::default() for any field you don't set explicitly.

Molecule Utilities

use renkin::chem_env::{mol_from_smiles, to_canonical};

// Parse SMILES
let mol = mol_from_smiles("CC(=O)O")?;

// Get canonical SMILES
let canon = to_canonical(&mol);
println!("{}", canon);  // "CC(=O)O"

Feature Flags

Feature Description
python Enable PyO3 Python bindings (for maturin build)
nn-scoring ONNX-based template relevance scorer (--scorer CLI flag); not available on wasm32
(default: wasm32 target) WASM bindings via wasm-bindgen, gated on target_arch = "wasm32" rather than a Cargo feature

Error Types

RENKIN uses anyhow::Error for all fallible operations in the CLI and native library surface (PyO3 wraps these as Python ValueError; WASM formats them into the {"error": "..."} JSON shape). Common errors: - SMILES parse failures from chematic - Building block file I/O errors - Template metadata sidecar validation failures (schema version, duplicate/dangling reference IDs, out-of-range yields, etc.)