Skip to main content

molrs/gen3d/
options.rs

1//! Public options for 3D generation.
2
3/// Stage-1 embedding algorithm selector.
4///
5/// Names are algorithm-based (not toolkit-based), so backends can evolve
6/// without coupling public API names to a specific implementation source.
7#[derive(Debug, Clone, Copy, PartialEq, Eq)]
8pub enum EmbedAlgorithm {
9    /// Rule- and fragment-based coordinate construction.
10    ///
11    /// This is the algorithm family currently implemented in this crate.
12    FragmentRules,
13    /// Distance-geometry based embedding.
14    DistanceGeometry,
15}
16
17/// Force-field backend selector.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum ForceFieldKind {
20    /// Merck Molecular Force Field 94.
21    MMFF94,
22    /// Universal Force Field.
23    Uff,
24    /// Prefer MMFF94, fall back to UFF.
25    Auto,
26}
27
28/// Preset quality/speed profile.
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum Gen3DSpeed {
31    /// Short minimization and fewer rotor trials.
32    Fast,
33    /// Balanced defaults.
34    Medium,
35    /// More rotor trials and longer minimization.
36    Better,
37}
38
39/// Options for [`super::generate_3d`].
40#[derive(Debug, Clone)]
41pub struct Gen3DOptions {
42    /// Stage-1 embedding algorithm.
43    pub embed_algorithm: EmbedAlgorithm,
44    /// Target force-field family (or auto selection).
45    pub forcefield: ForceFieldKind,
46    /// Throughput/quality preset.
47    pub speed: Gen3DSpeed,
48    /// Add explicit hydrogens before generation.
49    pub add_hydrogens: bool,
50    /// Total optimization budget. `0` means "use speed preset default".
51    pub max_steps: usize,
52    /// Optional deterministic RNG seed.
53    pub rng_seed: Option<u64>,
54}
55
56impl Default for Gen3DOptions {
57    fn default() -> Self {
58        Self {
59            embed_algorithm: EmbedAlgorithm::FragmentRules,
60            forcefield: ForceFieldKind::Auto,
61            speed: Gen3DSpeed::Medium,
62            add_hydrogens: true,
63            max_steps: 0,
64            rng_seed: None,
65        }
66    }
67}
68
69impl Gen3DOptions {
70    /// Effective optimization step budget.
71    pub(crate) fn effective_max_steps(&self) -> usize {
72        if self.max_steps > 0 {
73            return self.max_steps;
74        }
75        match self.speed {
76            Gen3DSpeed::Fast => 120,
77            Gen3DSpeed::Medium => 260,
78            Gen3DSpeed::Better => 520,
79        }
80    }
81
82    /// Coarse minimization steps.
83    pub(crate) fn coarse_steps(&self) -> usize {
84        (self.effective_max_steps() * 35 / 100).max(20)
85    }
86
87    /// Final minimization steps.
88    pub(crate) fn final_steps(&self) -> usize {
89        (self.effective_max_steps() * 50 / 100).max(20)
90    }
91
92    /// Rotor search attempts.
93    pub(crate) fn rotor_attempts(&self, n_rot_bonds: usize) -> usize {
94        if n_rot_bonds == 0 {
95            return 0;
96        }
97        match self.speed {
98            Gen3DSpeed::Fast => (n_rot_bonds * 4).max(8),
99            Gen3DSpeed::Medium => (n_rot_bonds * 8).max(20),
100            Gen3DSpeed::Better => (n_rot_bonds * 16).max(40),
101        }
102    }
103
104    /// Maximum per-step rotor perturbation (radians).
105    pub(crate) fn rotor_max_delta(&self) -> f64 {
106        match self.speed {
107            Gen3DSpeed::Fast => std::f64::consts::PI / 5.0,
108            Gen3DSpeed::Medium => std::f64::consts::PI / 3.0,
109            Gen3DSpeed::Better => std::f64::consts::PI / 2.0,
110        }
111    }
112}