molcrafts-molrs 0.7.0

Molecular simulation toolkit: core data structures, IO, trajectory analysis, force fields, SMILES, and 3D conformer generation (feature-gated modules)
Documentation
//! Distance-geometry constraint generation (ETKDGv3), a faithful port of
//! RDKit's bounds-matrix builder + smoothing + experimental-torsion knowledge.
//!
//! This module replaces the approximate, first-principles bounds in
//! `super::distance_geometry` with a port of RDKit's actual ETKDGv3 constraint
//! generation (BSD-3, Copyright (C) Greg Landrum / Sereina Riniker and other
//! RDKit contributors). It produces, for a molecular graph:
//!
//!   * a smoothed **bounds matrix** identical (< 1e-3 Å) to
//!     `rdkit.Chem.rdDistGeom.GetMoleculeBoundsMatrix`,
//!   * **experimental torsion** preferences (CrystalFF M6),
//!   * **chiral** volume constraints,
//!   * **improper** (out-of-plane) constraints.
//!
//! ## References
//! - Blaney & Dixon, *Rev. Comput. Chem.* **5**, 299 (1994) — bounds smoothing.
//! - Crippen & Havel, *Distance Geometry and Molecular Conformation* (1988).
//! - Riniker & Landrum, *J. Chem. Inf. Model.* **55**, 2562 (2015) — ETKDG.
//! - Wang, Witek, Landrum, Riniker, *J. Chem. Inf. Model.* **60**, 2044 (2020)
//!   — ETKDGv3 (small rings + macrocycles).
//!
//! ## Faithfulness boundary (honest scope)
//! - **Bounds + triangle smoothing**: full port; numerically matches RDKit.
//! - **Chiral / improper / flat-ring knowledge**: full port of the
//!   `findChiralSets` + basic-knowledge logic. Chirality sign is taken from
//!   the input 3D coordinates (molrs has no RDKit `ChiralTag`).
//! - **Experimental torsions**: full port. The complete ETKDGv3 CrystalFF
//!   three-table set (v2 ++ small-rings ++ macrocycles) is matched by the core
//!   SMARTS engine (`molrs::chem::smarts`), reproducing RDKit
//!   `getExperimentalTorsions` (first-match-wins, one torsion per rotatable
//!   bond). See `torsion_prefs` for the precise boundary. Tetrangle smoothing
//!   is omitted because RDKit's reference matrix does not apply it.

mod bounds;
mod chirality;
mod knowledge;
mod matrix;
mod perceive;
mod smooth;
mod torsion_prefs;
mod torsion_tables;
mod uff;

use molrs::error::MolRsError;
use molrs::system::atomistic::Atomistic;

pub use chirality::{ChiralConstraint, ChiralSign, ImproperConstraint};
pub use knowledge::KnowledgeTorsion;
pub use matrix::BoundsMatrix;
pub use smooth::{smooth_bounds, smooth_bounds_tol};
pub use torsion_prefs::{AssignedTorsion, TorsionConstraint, TorsionTable, assign_with_provenance};

/// ETKDG generation version. This spec targets `Etkdgv3` by default.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EtkdgVersion {
    /// Plain ETDG — topological bounds + smoothing only.
    Etdg,
    /// ETKDGv2 — experimental torsions + basic knowledge.
    Etkdgv2,
    /// ETKDGv3 — v2 plus small-ring / macrocycle handling.
    Etkdgv3,
}

/// The full ETKDGv3 constraint set consumed by the embedding stage (spec 04).
pub struct DgConstraints {
    /// Smoothed distance-bounds matrix.
    pub bounds: BoundsMatrix,
    /// Experimental (CrystalFF) torsion preferences (partial — see module docs).
    pub experimental_torsions: Vec<TorsionConstraint>,
    /// Flat sp2-ring planarising torsions (basic knowledge).
    pub flat_ring_torsions: Vec<KnowledgeTorsion>,
    /// Chiral volume constraints.
    pub chiral: Vec<ChiralConstraint>,
    /// Improper / out-of-plane constraints.
    pub improper: Vec<ImproperConstraint>,
}

/// Assign ETKDGv3 experimental torsions to `mol` with table provenance, for
/// validation against RDKit `getExperimentalTorsions`. Perceives aromaticity /
/// rings internally, then drives the full SMARTS-table matcher.
pub fn experimental_torsions_with_provenance(mol: &Atomistic) -> Vec<AssignedTorsion> {
    let p = perceive::perceive(mol);
    assign_with_provenance(mol, &p)
}

/// Build the unsmoothed topological bounds matrix for `mol`
/// (RDKit `setTopolBounds`, `set15bounds=true, scaleVDW=false`).
pub fn build_bounds(mol: &Atomistic) -> Result<BoundsMatrix, MolRsError> {
    let n = mol.n_atoms();
    if n == 0 {
        return Err(MolRsError::validation("molecule has no atoms"));
    }
    let p = perceive::perceive(mol);
    Ok(bounds::set_topol_bounds(&p))
}

/// Build the complete ETKDGv3 constraint set: topological bounds (then
/// triangle-smoothed in place), experimental torsions, knowledge terms,
/// chiral and improper constraints.
///
/// `version` gates the knowledge layers: `Etdg` emits bounds only;
/// `Etkdgv2` / `Etkdgv3` additionally emit torsion / chiral / improper
/// constraints. (v2 vs v3 differ only in small-ring/macrocycle torsion data,
/// which is part of the documented experimental-torsion partial.)
pub fn build_constraints(
    mol: &Atomistic,
    version: EtkdgVersion,
) -> Result<DgConstraints, MolRsError> {
    let n = mol.n_atoms();
    if n == 0 {
        return Err(MolRsError::validation("molecule has no atoms"));
    }
    let p = perceive::perceive(mol);

    let mut bounds = bounds::set_topol_bounds(&p);
    smooth::smooth_bounds(&mut bounds)?;

    let (experimental_torsions, flat_ring_torsions, chiral, improper) = match version {
        EtkdgVersion::Etdg => (Vec::new(), Vec::new(), Vec::new(), Vec::new()),
        EtkdgVersion::Etkdgv2 | EtkdgVersion::Etkdgv3 => (
            torsion_prefs::assign_experimental_torsions(mol, &p),
            knowledge::build_flat_ring_torsions(&p),
            chirality::build_chiral(mol, &p),
            chirality::build_improper(&p),
        ),
    };

    Ok(DgConstraints {
        bounds,
        experimental_torsions,
        flat_ring_torsions,
        chiral,
        improper,
    })
}