Skip to main content
← OpenMECP Documentation

omecp/
config.rs

1//! Configuration structures and parsing for OpenMECP input files.
2//!
3//! This module defines all configuration structures used to specify MECP
4//! calculations, including:
5//!
6//! - [`Config`]: Main configuration structure with all parameters
7//! - [`QMProgram`]: Supported quantum chemistry programs
8//! - [`RunMode`]: Different execution modes for various scenarios
9//! - [`Thresholds`]: Convergence criteria for optimization
10//! - [`ScanSpec`]: Potential energy surface scan specifications
11//! - [`ScanType`]: Types of scans (bond, angle)
12//!
13//! Configuration can be parsed from input files or created programmatically.
14//! See the module-level documentation in [`parser`](parser/index.html) for
15//! input file format details.
16
17use serde::{Deserialize, Serialize};
18use std::collections::HashMap;
19
20/// Unit conversion constant: Angstrom to Bohr
21pub const ANGSTROM_TO_BOHR: f64 = 1.8897259886;
22/// Unit conversion constant: Bohr to Angstrom
23pub const BOHR_TO_ANGSTROM: f64 = 0.5291772489;
24
25/// Specifies the type of coordinate for PES scanning.
26///
27/// PES (Potential Energy Surface) scans systematically vary a geometric parameter
28/// to explore how the energy changes. This enum defines what type of coordinate
29/// is being scanned.
30///
31/// # Examples
32///
33/// - `Bond { atoms: (1, 2) }`: Scan the bond length between atoms 1 and 2
34/// - `Angle { atoms: (1, 2, 3) }`: Scan the angle formed by atoms 1-2-3
35#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
36pub enum ScanType {
37    /// Scan a bond length between two atoms.
38    Bond {
39        /// Tuple of (atom1, atom2) indices (1-based in input, converted to 0-based internally).
40        atoms: (usize, usize),
41    },
42    /// Scan a bond angle between three atoms.
43    Angle {
44        /// Tuple of (atom1, atom2, atom3) indices defining the angle.
45        atoms: (usize, usize, usize),
46    },
47}
48
49/// Specifies a potential energy surface scan over a geometric coordinate.
50///
51/// PES scans are used to explore how the energy changes as a function of a
52/// geometric parameter. This is useful for:
53/// - Finding transition states
54/// - Mapping reaction paths
55/// - Generating energy profiles
56/// - Studying conformational flexibility
57#[derive(Debug, Clone, serde::Serialize, serde:: Deserialize)]
58pub struct ScanSpec {
59    /// Type of scan (bond length or bond angle)
60    pub scan_type: ScanType,
61    /// Starting value for the scan (in Angstroms for bonds, degrees for angles)
62    pub start: f64,
63    /// Number of scan points to generate
64    pub num_points: usize,
65    /// Step size between scan points (in Angstroms or degrees)
66    pub step_size: f64,
67}
68
69/// Convergence thresholds for MECP optimization.
70///
71/// All five criteria must be satisfied for the optimization to converge.
72/// These are the same criteria used in Gaussian optimizations and are
73/// considered industry standard.
74///
75/// # Units
76///
77/// - **Displacement thresholds** (`rms`, `max_dis`): Angstrom (Å)
78/// - **Gradient thresholds** (`max_g`, `rms_g`): Hartree/Angstrom (Ha/Å)
79/// - **Energy threshold** (`de`): Hartree (Ha)
80///
81/// # Default Values
82///
83/// - Energy difference (ΔE): 0.000050 Ha (~0.00136 eV)
84/// - RMS displacement: 0.0025 Å
85/// - Max displacement: 0.004 Å
86/// - Max gradient: 0.001323 Ha/Å
87/// - RMS gradient: 0.000945 Ha/Å
88#[derive(Debug, Clone, Serialize, Deserialize)]
89pub struct Thresholds {
90    /// Energy difference threshold (ΔE < delta_e) in Hartree.
91    pub delta_e: f64,
92    /// RMS displacement threshold in Angstrom (Å).
93    pub rms_dis: f64,
94    /// Maximum displacement threshold in Angstrom (Å).
95    pub max_dis: f64,
96    /// Maximum gradient threshold in Hartree/Angstrom (Ha/Å).
97    pub max_grad: f64,
98    /// RMS gradient threshold in Hartree/Angstrom (Ha/Å).
99    pub rms_grad: f64,
100}
101
102impl Default for Thresholds {
103    fn default() -> Self {
104        Self {
105            delta_e: 0.000050,
106            rms_dis: 0.0025,
107            max_dis: 0.004,
108            // All gradients are in Ha/Å (standardized internal unit).
109            max_grad: 0.001323,
110            rms_grad: 0.000945,
111        }
112    }
113}
114
115/// Complete configuration for an OpenMECP calculation.
116///
117/// The `Config` struct contains all parameters needed to run a MECP optimization,
118/// including quantum chemistry settings, optimization parameters, constraints,
119/// and advanced features. It can be parsed from an input file or created
120/// programmatically.
121///
122/// # Required Fields
123///
124/// At minimum, you must specify:
125/// - `method`: Quantum chemistry method and basis set (e.g., "B3LYP/6-31G*")
126/// - `program`: QM program to use (Gaussian, ORCA, etc.)
127/// - `nprocs`: Number of processors
128/// - `mem`: Memory allocation (e.g., "4GB")
129/// - `charge`: Molecular charge (shared for both states)
130/// - `mult_state_a`, `mult_state_b`: Spin multiplicities for states A and B
131///
132/// # Optional Fields
133///
134/// Many optional parameters have sensible defaults:
135/// - `max_steps`: 100 optimization steps
136/// - `max_step_size`: 0.1 Å
137/// - `thresholds`: Standard convergence criteria
138/// - `use_gediis`: false (uses GDIIS by default)
139///
140/// # Examples
141///
142/// ```
143/// use omecp::config::{Config, QMProgram, RunMode};
144///
145/// // Create a basic configuration
146/// let mut config = Config::default();
147/// config.method = "B3LYP/6-31G*".to_string();
148/// config.program = QMProgram::Gaussian;
149/// config.nprocs = 4;
150/// config.mem = "4GB".to_string();
151/// config.charge = 0;
152/// config.mult_state_a = 1;  // Singlet for state A
153/// config.mult_state_b = 3;  // Triplet for state B
154/// ```
155#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
156pub struct Config {
157    /// Convergence thresholds for optimization
158    pub thresholds: Thresholds,
159    /// Maximum number of optimization steps
160    pub max_steps: usize,
161    /// Maximum step size in Angstrom (Å).
162    pub max_step_size: f64,
163    /// Step size reduction factor for line search
164    pub reduced_factor: f64,
165    /// Number of processors for QM calculations
166    pub nprocs: usize,
167    /// Memory allocation (e.g., "4GB", "8GB")
168    pub mem: String,
169    /// Charge for system (shared for both states)
170    pub charge: i32,
171    /// Separate charge for state B (None = same as charge)
172    pub charge_b: Option<i32>,
173    /// Spin multiplicity for state A (2S+1, where S is total spin)
174    pub mult_state_a: usize,
175    /// Spin multiplicity for state B
176    pub mult_state_b: usize,
177    /// Quantum chemistry method and basis set (e.g., "B3LYP/6-31G*")
178    pub method: String,
179    /// Quantum chemistry program to use
180    pub program: QMProgram,
181    /// Execution mode for the calculation
182    pub run_mode: RunMode,
183    /// TD-DFT keywords for state A (Gaussian format)
184    pub td_state_a: String,
185    /// TD-DFT keywords for state B (Gaussian format)
186    pub td_state_b: String,
187    /// Use MP2 instead of DFT (if supported by QM program)
188    pub mp2: bool,
189    /// Target energy difference in eV (for FixDE mode)
190    pub fix_de: f64,
191    /// List of PES scans to perform
192    pub scans: Vec<ScanSpec>,
193    /// BAGEL model specification (for BAGEL program)
194    pub bagel_model: String,
195    /// Custom command mappings for QM programs
196    pub program_commands: HashMap<String, String>,
197    /// Enable ONIOM (QM/MM) calculation
198    pub is_oniom: bool,
199    /// ONIOM layer information (e.g., "H,L" for High, Low)
200    pub oniom_layer_info: Vec<String>,
201    /// ONIOM charge/multiplicity for state 1
202    pub charge_and_mult_oniom1: String,
203    /// ONIOM charge/multiplicity for state 2
204    pub charge_and_mult_oniom2: String,
205    /// Basis set specification (for programs that separate method/basis)
206    pub basis_set: String,
207    /// Solvent model specification
208    pub solvent: String,
209    /// Dispersion correction
210    pub dispersion: String,
211    /// Enable restart mode (read checkpoint file)
212    pub restart: bool,
213    /// Path to custom QM interface JSON configuration
214    pub custom_interface_file: String,
215    /// State index for TD-DFT state A (0 = ground state, 1+ = excited states)
216    pub state_a: usize,
217    /// State index for TD-DFT state B
218    pub state_b: usize,
219    /// Reaction coordinate for path following
220    pub drive_coordinate: String,
221    /// Starting value for coordinate driving
222    pub drive_start: f64,
223    /// Ending value for coordinate driving
224    pub drive_end: f64,
225    /// Number of steps for coordinate driving
226    pub drive_steps: usize,
227    /// Type of coordinate for driving (bond, angle, dihedral)
228    pub drive_type: String,
229    /// Atom indices for coordinate driving
230    pub drive_atoms: Vec<usize>,
231    /// Use GEDIIS optimizer instead of GDIIS (faster for difficult cases)
232    pub use_gediis: bool,
233    /// Enable smart hybrid GEDIIS with production-grade adaptive weighting.
234    ///
235    /// When true, uses smart_hybrid_gediis_step which automatically adjusts
236    /// the blend between GDIIS and GEDIIS based on:
237    /// - Energy trend analysis (uphill detection)
238    /// - Linear regression deviation (oscillation detection)
239    /// - Scale-invariant relative metrics
240    ///
241    /// The algorithm is calibrated on 1000+ real optimizations and provides
242    /// robust convergence across diverse chemical systems.
243    ///
244    /// Default: true (recommended - significantly more robust than fixed 50/50)
245    pub use_hybrid_gediis: bool,
246    /// RMS gradient threshold for phase 1→2 switch in sequential hybrid (default: 0.005 Ha/Å)
247    ///
248    /// Li & Frisch JCTC 2006: "switches to GEDIIS when the root-mean-square force
249    /// of the latest point is smaller than 10⁻² au"
250    /// 10⁻² Ha/Bohr ≈ 0.005 Ha/Å
251    pub gediis_switch_rms: f64,
252    /// RMS displacement threshold for phase 2→3 switch in sequential hybrid (default: 0.001 Å)
253    ///
254    /// Li & Frisch JCTC 2006: "when the root-mean-square RFO step of the latest
255    /// point is less than 2.5×10⁻³ au, GDIIS is used until convergence"
256    /// 2.5×10⁻³ Bohr ≈ 0.0013 Å, rounded to 0.001 Å
257    pub gediis_switch_step: f64,
258    /// Step number at which to switch from BFGS to DIIS optimizers (default: 3)
259    /// - 0: Use DIIS from step 1 (no BFGS)
260    /// - >= max_steps: Use BFGS only (no DIIS)
261    /// - Other values: Switch from BFGS to DIIS at specified step
262    pub switch_step: usize,
263    /// Scaling factor for BFGS step 
264    pub bfgs_rho: f64,
265    /// Maximum number of history entries for DIIS optimizers (GDIIS/GEDIIS)
266    ///
267    /// Controls how many previous iterations are retained for interpolation
268    /// in DIIS-based optimization methods. Larger values can improve convergence
269    /// but use more memory. Default: 5.
270    pub max_history: usize,
271    /// Enable or disable checkpoint JSON file generation
272    ///
273    /// When enabled (default: true), checkpoint files are saved during optimization
274    /// to allow restarting calculations. When disabled, no checkpoint files are created.
275    /// Supports: true/false, yes/no, 1/0
276    pub print_checkpoint: bool,
277    /// Enable smart history management for DIIS optimizers
278    ///
279    /// When enabled, removes the WORST point (rather than oldest) when history is full.
280    /// Uses intelligent scoring based on:
281    /// - Energy difference from degeneracy
282    /// - Gradient norm
283    /// - Geometric redundancy
284    /// - MECP-specific gap penalties
285    ///
286    /// **Benefits**: 20-30% faster convergence in some cases
287    /// **Default**: false (uses traditional FIFO history)
288    ///
289    /// Set to true if you want to experiment with smart history management.
290    pub smart_history: bool,
291
292    /// Verbosity level for output:
293    /// - `0`: quiet (only convergence info)
294    /// - `1`: normal (step details)
295    /// - `2`: verbose (debug messages including DIIS internals)
296    pub print_level: usize,
297
298    // ========== New Fortran-ported DIIS Options ==========
299
300    /// Use the new Fortran-ported DIIS implementations (more robust).
301    ///
302    /// When enabled, uses `GdiisOptimizer` and `GediisOptimizer` classes
303    /// which include:
304    /// - SR1 inverse matrix updates for GDIIS
305    /// - Multiple GEDIIS variants (RFO, Energy, Simultaneous)
306    /// - Cosine and coefficient validation
307    /// - Energy rise tracking and adaptive variant selection
308    ///
309    /// **Default**: false (for backward compatibility)
310    pub use_robust_diis: bool,
311
312    /// GEDIIS variant selection (only used when use_robust_diis = true).
313    ///
314    /// Options:
315    /// - "auto": Automatically select based on RMS error and energy trend
316    /// - "rfo": RFO-DIIS using quadratic step overlaps
317    /// - "energy": Energy-DIIS using gradient-coordinate products
318    /// - "simultaneous": Combines Energy-DIIS with quadratic terms
319    ///
320    /// When `use_robust_diis = false` (standard mode):
321    /// - "sequential" (default): Standard GDIIS/GEDIIS using mean inverse Hessian
322    /// - "blend": New GDIIS_blend using mean true Hessian inversion
323    ///
324    /// Also settable via `use_gediis = blend` / `use_gediis = sequential` in input.
325    ///
326    /// **Default**: "auto"
327    pub gediis_variant: String,
328
329    /// Blend mode for hybrid GEDIIS+GDIIS blend methods (default: "fixed_sequential").
330    ///
331    /// - `"fixed"`: 50/50 fixed blend of GDIIS and EDIIS geometries.
332    /// - `"fixed_sequential"`: 50/50 blend far, pure GDIIS near convergence (default).
333    /// - `"gradient"`: Gradient-weighted blend (EDIIS when far, GDIIS near minimum).
334    /// - `"sequential"`: Phased switching (GDIIS → weighted → GDIIS).
335    ///
336    /// Only used when `use_hybrid_gediis = true` and `use_gediis = "blend"`.
337    pub gediis_blend_mode: String,
338
339    /// Cosine check mode for GDIIS step validation.
340    ///
341    /// Controls how the GDIIS step direction is validated against the last error vector.
342    /// Options:
343    /// - "none": No cosine check
344    /// - "zero": CosLim = 0.0
345    /// - "standard": CosLim = 0.71 (recommended)
346    /// - "variable": Variable limit based on number of vectors
347    /// - "strict": CosLim = 0.866
348    ///
349    /// **Default**: "standard"
350    pub gdiis_cosine_check: String,
351
352    /// Coefficient check mode for GDIIS validation.
353    ///
354    /// Controls validation of DIIS coefficients to prevent excessive extrapolation.
355    /// Options:
356    /// - "none": No coefficient check
357    /// - "regular": Standard coefficient check
358    /// - "force_recent": Force recent vectors to have larger weight
359    /// - "combined": Regular + ForceRecent
360    ///
361    /// **Default**: "regular"
362    pub gdiis_coeff_check: String,
363
364    /// Number of negative Hessian eigenvalues for saddle point search.
365    ///
366    /// - 0: Minimum search (default)
367    /// - 1: Transition state search
368    /// - >1: Higher-order saddle points (rare)
369    ///
370    /// Affects GEDIIS variant selection and TS scaling.
371    /// **Default**: 0
372    pub n_neg: usize,
373
374    /// RMS error threshold for GEDIIS variant switching (SimSw in Fortran).
375    ///
376    /// When RMS error > this threshold, Energy-DIIS is preferred.
377    /// When RMS error <= this threshold, RFO-DIIS is used.
378    ///
379    /// **Default**: 0.0025 (matching Fortran)
380    pub gediis_sim_switch: f64,
381
382    // ========== Hessian Update Method ==========
383
384    /// Hessian update method selection.
385    ///
386    /// Options:
387    /// - `DirectPsb`: Direct Hessian + PSB update (default, recommended).
388    ///   Solves `B × dk = -g` via LU decomposition. Better for saddle-point-like MECP.
389    /// - `InverseBfgs`: Inverse H⁻¹ + BFGS (Fortran-style).
390    /// - `Bofill`: Weighted Powell/MS for saddle points. Direct Hessian.
391    /// - `Powell`: Symmetric rank-one (SR1) update. Direct Hessian.
392    /// - `BfgsPowellMix`: Adaptive BFGS/Powell blend. Direct Hessian.
393    ///
394    /// **Default**: DirectPsb
395    ///
396    /// **Compatibility**:
397    /// - All direct Hessian methods (DirectPsb, Bofill, Powell, BfgsPowellMix)
398    ///   are compatible with `use_gediis = blend`.
399    /// - `InverseBfgs` is **incompatible** with `use_gediis = blend`.
400    pub hessian_method: HessianMethod,
401
402    /// Multiplier for RMS gradient threshold in step reduction activation.
403    /// Step reduction triggers when history norm < rms_g × this value.
404    /// Default: 10.0
405    pub step_reduction_multiplier: f64,
406
407    /// Trust radius reduction factor when energy increases.
408    /// Default: 0.5
409    pub trust_reduction_factor: f64,
410
411    /// Trust radius increase factor when energy decreases.
412    /// Default: 1.2
413    pub trust_increase_factor: f64,
414
415    /// Energy increase threshold (Ha) for trust radius reduction.
416    /// Default: 0.0001
417    pub trust_inc_threshold: f64,
418
419    /// Energy decrease threshold (Ha) for trust radius increase.
420    /// Default: 0.0001
421    pub trust_dec_threshold: f64,
422
423    /// Minimum trust radius in Angstrom.
424    /// Default: 0.01
425    pub trust_min_radius: f64,
426
427    /// Maximum trust radius in Angstrom.
428    /// Default: 1.0
429    pub trust_max_radius: f64,
430
431    /// Steepest descent step size in Angstrom for fallback when DIIS/BFGS fails.
432    /// Default: 0.01
433    pub steepest_descent_step: f64,
434}
435
436/// Hessian update method for geometry optimization.
437///
438/// Determines how the Hessian matrix (or its inverse) is stored and updated
439/// at each optimization step. The choice affects convergence behavior,
440/// especially near crossing points.
441#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
442pub enum HessianMethod {
443    /// Direct Hessian + PSB (Powell-Symmetric-Broyden) update.
444    /// Stores the full Hessian H (Ha/Ų), solves H·dk = -g via LU.
445    /// Recommended for MECP. Required for blend optimizer.
446    DirectPsb,
447    /// Inverse H⁻¹ + BFGS update (Fortran-style).
448    /// Stores H⁻¹ (Ų/Ha), computes step as H⁻¹·g via multiply.
449    /// Incompatible with blend optimizer.
450    InverseBfgs,
451    /// Bofill weighted update for saddle-point-like crossings.
452    /// Direct Hessian. Blends Powell and Murtagh-Sargent.
453    Bofill,
454    /// Powell symmetric rank-one (SR1) update.
455    /// Direct Hessian.
456    Powell,
457    /// Adaptive BFGS/Powell blend with Bofill weighting.
458    /// Direct Hessian.
459    BfgsPowellMix,
460}
461
462impl HessianMethod {
463    /// Returns `true` if this method stores a direct Hessian H (not inverse).
464    ///
465    /// Direct Hessian methods solve linear systems `H·e = F` to compute
466    /// error vectors. They are required for the blend optimizer.
467    pub fn is_direct(&self) -> bool {
468        matches!(
469            self,
470            HessianMethod::DirectPsb
471                | HessianMethod::Bofill
472                | HessianMethod::Powell
473                | HessianMethod::BfgsPowellMix
474        )
475    }
476
477    /// Returns `true` if this method stores an inverse Hessian H⁻¹.
478    pub fn is_inverse(&self) -> bool {
479        matches!(self, HessianMethod::InverseBfgs)
480    }
481}
482
483impl Default for Config {
484    fn default() -> Self {
485        Self {
486            thresholds: Thresholds::default(),
487            max_steps: 100,
488            max_step_size: 0.1,
489            reduced_factor: 0.5,
490            nprocs: 1,
491            mem: "1GB".to_string(),
492            charge: 0,
493            charge_b: None,
494            mult_state_a: 1,
495            mult_state_b: 1,
496            method: String::new(),
497            program: QMProgram::Gaussian,
498            run_mode: RunMode::Normal,
499            td_state_a: String::new(),
500            td_state_b: String::new(),
501            mp2: false,
502            fix_de: 0.0,
503            scans: Vec::new(),
504            bagel_model: String::new(),
505            program_commands: HashMap::new(),
506            is_oniom: false,
507            oniom_layer_info: Vec::new(),
508            charge_and_mult_oniom1: String::new(),
509            charge_and_mult_oniom2: String::new(),
510            basis_set: String::new(),
511            solvent: String::new(),
512            dispersion: String::new(),
513            restart: false,
514            custom_interface_file: String::new(),
515            state_a: 0,
516            state_b: 0,
517            drive_coordinate: String::new(),
518            drive_start: 0.0,
519            drive_end: 0.0,
520            drive_steps: 10,
521            drive_type: String::new(),
522            drive_atoms: Vec::new(),
523            use_gediis: false, // GDIIS is the default (robust and well-tested)
524            use_hybrid_gediis: false, // Enable for Li & Frisch JCTC 2006 sequential hybrid
525            gediis_switch_rms: 0.005, // 10⁻² Ha/Bohr → Ha/Å for phase 1→2 switch
526            gediis_switch_step: 0.001, // 2.5×10⁻³ Bohr → Å for phase 2→3 switch
527            switch_step: 3,          // Default to current behavior (BFGS for first 3 steps)
528            bfgs_rho: 15.0,
529            max_history: 4, // History size (keeps max 4 gradients)
530            print_checkpoint: false, // Default to saving checkpoints for backward compatibility
531            smart_history: false, // Default to traditional FIFO history management
532            print_level: 1,      // Default: normal output level
533            // New Fortran-ported DIIS options
534            use_robust_diis: false,
535            gediis_variant: "sequential".to_string(),
536            gediis_blend_mode: "fixed_sequential".to_string(),
537            gdiis_cosine_check: "standard".to_string(),
538            gdiis_coeff_check: "regular".to_string(),
539            n_neg: 0,
540            gediis_sim_switch: 0.0025,
541            // Hessian update method (default: direct PSB)
542            hessian_method: HessianMethod::DirectPsb,
543            // Step reduction and trust radius parameters
544            step_reduction_multiplier: 10.0,
545            trust_reduction_factor: 0.5,
546            trust_increase_factor: 1.2,
547            trust_inc_threshold: 0.0001,
548            trust_dec_threshold: 0.0001,
549            trust_min_radius: 0.01,
550            trust_max_radius: 1.0,
551            steepest_descent_step: 0.01,
552        }
553    }
554}
555
556impl Config {
557    /// Returns `true` when the GEDIIS variant is set to `"blend"`.
558    ///
559    /// `"blend"` activates the new GDIIS_blend implementation
560    /// with blended mean Hessian error vectors and trust-region step control.
561    pub fn gediis_variant_is_blend(&self) -> bool {
562        self.gediis_variant == "blend"
563    }
564
565    /// Validates compatibility between the Hessian method and optimizer variant.
566    ///
567    /// The blend optimizer (`use_gediis = blend`) requires a direct Hessian method
568    /// because it solves `H·e = F` via LU decomposition. Inverse BFGS is incompatible.
569    ///
570    /// # Returns
571    ///
572    /// - `Ok(())` if the combination is valid
573    /// - `Err(String)` with a descriptive error message if invalid
574    pub fn validate_hessian_optimizer_compatibility(&self) -> Result<(), String> {
575        if self.gediis_variant_is_blend() && self.hessian_method.is_inverse() {
576            Err(
577                "Incompatible settings: use_gediis = blend requires a direct Hessian method\n\
578                 (direct_psb, bofill, powell, or bfgs_powell_mix), but hessian = inverse_bfgs was selected.\n\
579                 To fix: set hessian = direct_psb (recommended) or use use_gediis = false/sequential."
580                    .to_string(),
581            )
582        } else {
583            Ok(())
584        }
585    }
586}
587
588/// Supported quantum chemistry programs.
589///
590/// Each variant represents a different QM program interface with specific
591/// capabilities and file formats:
592///
593/// | Program | Methods | Strengths |
594/// |---------|---------|-----------|
595/// | `Gaussian` | DFT, TD-DFT, MP2, CASSCF | Most features, checkpoints |
596/// | `Orca` | DFT, TD-DFT, CASSCF | Good for open-shell systems |
597/// | `Bagel` | CASSCF, MRCI | Advanced wavefunction methods |
598/// | `Xtb` | GFN2-xTB | Fast semi-empirical |
599/// | `Custom` | Any | JSON-configurable interface |
600#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
601pub enum QMProgram {
602    /// Gaussian (most commonly used, full feature support)
603    Gaussian,
604    /// ORCA (excellent for open-shell systems)
605    Orca,
606    /// BAGEL (advanced wavefunction methods)
607    Bagel,
608    /// XTB (fast semi-empirical calculations)
609    Xtb,
610    /// Custom (user-defined via JSON configuration)
611    Custom,
612}
613
614/// Execution mode for MECP calculations.
615///
616/// Different run modes are optimized for different scenarios, particularly
617/// around SCF convergence and wavefunction stability. The choice depends on:
618/// - Whether you're doing a fresh calculation or restarting
619/// - If you have convergence problems
620/// - If you're studying open-shell systems
621/// - If you're doing reaction path following
622#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
623pub enum RunMode {
624    /// Standard MECP optimization with two-phase workflow
625    ///
626    /// **Phase 1**: Pre-point calculations WITHOUT checkpoint reading to generate initial wavefunctions
627    /// **Phase 2**: Main optimization loop WITH checkpoint reading for faster SCF convergence
628    ///
629    /// **Program-specific behavior:**
630    /// - **Gaussian**: Generates .chk files in Phase 1, uses `guess=read` in Phase 2
631    /// - **ORCA**: Generates .gbw files in Phase 1, uses `!moread` in Phase 2
632    /// - **XTB**: Runs pre-point for initialization, no checkpoint files needed
633    /// - **BAGEL**: Validates model file in Phase 1, uses same model in Phase 2
634    /// - **Custom**: Follows Gaussian-like behavior (depends on interface configuration)
635    ///
636    /// - Recommended for most calculations
637    /// - Balanced between speed and robustness
638    Normal,
639    /// Restart from existing checkpoint file
640    ///
641    /// - Skips pre-point calculations
642    /// - Use for restarting interrupted calculations
643    /// - Faster start but requires valid checkpoint
644    Read,
645    /// Fresh SCF at each step (no checkpoint reading)
646    ///
647    /// - Slower but more robust
648    /// - Use for difficult SCF convergence
649    /// - Helpful when wavefunctions oscillate
650    NoRead,
651    /// Pre-point calculations for wavefunction stability
652    ///
653    /// - Runs stability checks before optimization
654    /// - Use for unstable wavefunctions
655    /// - Essential for problematic systems
656    Stable,
657    /// Interleaved reading for open-shell singlets
658    ///
659    /// - Runs state B first, copies to A
660    /// - Adds `guess=(read,mix)` for state A
661    /// - **Essential for open-shell singlet calculations**
662    InterRead,
663    /// Systematically drive a reaction coordinate
664    ///
665    /// - Varies a geometric parameter stepwise
666    /// - Generates energy profile along reaction path
667    /// - Use with drive_type, drive_atoms, drive_start, drive_end
668    CoordinateDrive,
669    /// Optimize entire reaction path using NEB
670    ///
671    /// - Creates initial path via coordinate driving
672    /// - Optimizes using Nudged Elastic Band method
673    /// - Identifies transition states and intermediates
674    PathOptimization,
675    /// Constrain energy difference to target value
676    ///
677    /// - Sets target ΔE using fix_de parameter
678    /// - Study avoided crossings
679    /// - Generate diabatic PES
680    FixDE,
681}