xtb_model/
xtb.rs

1// [[file:../xtb.note::a89844af][a89844af]]
2//! high level wrapper for xTB
3// a89844af ends here
4
5// [[file:../xtb.note::a7b88800][a7b88800]]
6use super::*;
7
8use libxtb::*;
9// a7b88800 ends here
10
11// [[file:../xtb.note::392dc74e][392dc74e]]
12/// Possible parameters for XTB calculation.
13#[derive(Clone, Debug)]
14pub struct XtbParameters {
15    uhf: usize,
16    charge: f64,
17    verbosity: XtbOutputVerbosity,
18    max_iterations: usize,
19    electronic_temperature: f64,
20    method: XtbMethod,
21    lattice: Option<[f64; 9]>,
22    periodic: [bool; 3],
23    // TODO: solvent
24}
25
26#[derive(Clone, Debug)]
27pub enum XtbOutputVerbosity {
28    Muted,
29    Minimal,
30    Verbose,
31}
32
33impl From<&str> for XtbMethod {
34    fn from(s: &str) -> Self {
35        match s.to_uppercase().as_str() {
36            "GFN2XTB" | "GFN2-XTB" => XtbMethod::GFN2xTB,
37            "GFN1XTB" | "GFN1-XTB" => XtbMethod::GFN1xTB,
38            "GFN0XTB" | "GFN0-XTB" => XtbMethod::GFN0xTB,
39            "GFNFF" | "GFN-FF" => XtbMethod::GFNFF,
40            _ => panic!("invalid xTB method: {}", s),
41        }
42    }
43}
44
45impl Default for XtbParameters {
46    fn default() -> Self {
47        Self {
48            uhf: 0,
49            charge: 0.0,
50            verbosity: XtbOutputVerbosity::Muted,
51            max_iterations: 250,
52            electronic_temperature: 300.0,
53            method: XtbMethod::GFN2xTB,
54            lattice: None,
55            periodic: [false; 3],
56        }
57    }
58}
59
60impl XtbParameters {
61    /// Set system charge `charge`.
62    pub fn charge(&mut self, charge: f64) -> &mut Self {
63        self.charge = charge;
64        self
65    }
66
67    /// Set `n` unpaired electrons.
68    pub fn unpaired_electrons(&mut self, n: usize) -> &mut Self {
69        self.uhf = n;
70        self
71    }
72
73    /// Set electronic temperature for level filling in tight binding calculators in K.
74    pub fn electronic_temperature(&mut self, t: f64) -> &mut Self {
75        assert!(t.is_sign_positive(), "invalid temperature {:?}", t);
76        self.electronic_temperature = t;
77        self
78    }
79
80    /// Set maximum number of iterations for self-consistent TB calculators.
81    pub fn max_iterations(&mut self, n: usize) -> &mut Self {
82        self.max_iterations = n;
83        self
84    }
85
86    /// Set calculation output to be verbose.
87    pub fn output_verbose(&mut self) -> &mut Self {
88        self.verbosity = XtbOutputVerbosity::Verbose;
89        self
90    }
91
92    /// Set calculation output to be minmal.
93    pub fn output_minimal(&mut self) -> &mut Self {
94        self.verbosity = XtbOutputVerbosity::Minimal;
95        self
96    }
97
98    /// Set calculation output to be muted.
99    pub fn output_muted(&mut self) -> &mut Self {
100        self.verbosity = XtbOutputVerbosity::Muted;
101        self
102    }
103
104    /// Set xTB class of method
105    pub fn method(&mut self, method: impl Into<XtbMethod>) -> &mut Self {
106        self.method = method.into();
107        self
108    }
109
110    /// Periodic lattice
111    pub fn lattice(&mut self, lattice: impl Into<Option<[f64; 9]>>) -> &mut Self {
112        self.lattice = lattice.into();
113        self.periodic = [true; 3];
114        self
115    }
116}
117// 392dc74e ends here
118
119// [[file:../xtb.note::bcd483ad][bcd483ad]]
120/// High level abstraction for XTB evaluation of energy and gradient
121pub struct XtbModel {
122    params: XtbParameters,
123    atom_types: Vec<i32>,
124    coord: Vec<f64>,
125    lattice: Option<[f64; 9]>,
126    periodic: [bool; 3],
127
128    env: XtbEnvironment,
129    mol: XtbMolecule,
130    calc: XtbCalculator,
131
132    // calculated results
133    dipole: Option<[f64; 3]>,
134}
135
136impl XtbModel {
137    /// Construct new XtbModel for atoms specified with atomic numbers in
138    /// `atom_types`.
139    pub fn create(atom_types: &[i32], coord: &[f64], params: impl Into<Option<XtbParameters>>) -> Result<Self> {
140        assert_eq!(
141            atom_types.len() * 3,
142            coord.len(),
143            "Dimension missmatch between numbers and positions"
144        );
145        let env = XtbEnvironment::new();
146        let params = params.into().unwrap_or_default();
147        match params.verbosity {
148            XtbOutputVerbosity::Verbose => env.set_output_verbose()?,
149            XtbOutputVerbosity::Muted => env.set_output_muted()?,
150            XtbOutputVerbosity::Minimal => env.set_output_minimal()?,
151        }
152
153        let uhf = params.uhf as i32;
154        let charge = params.charge;
155        let lattice = params.lattice;
156        let periodic = params.periodic;
157        let mol = XtbMolecule::create(&env, &atom_types, coord, charge, uhf, lattice.as_ref(), &periodic)?;
158        let mut calc = XtbCalculator::new();
159        calc.load_parametrization(&mol, &env, params.method)?;
160        let xtb = Self {
161            coord: coord.to_vec(),
162            dipole: None,
163            lattice,
164            periodic,
165            mol,
166            calc,
167
168            params,
169            atom_types: atom_types.to_vec(),
170            env,
171        };
172
173        Ok(xtb)
174    }
175
176    /// Update coordinates and lattice parameters (quantities in Bohr).
177    pub fn update_structure(&mut self, positions: &[f64], lattice: impl Into<Option<[f64; 9]>>) -> Result<()> {
178        assert_eq!(positions.len(), self.coord.len());
179
180        self.coord.clone_from_slice(positions);
181        let lat = lattice.into();
182        if lat.is_some() {
183            self.lattice.clone_from(&lat);
184        }
185
186        Ok(())
187    }
188
189    /// Call XTB for evaluation of energy and gradient. coord in bohr.
190    pub fn calculate_energy_and_gradient(&mut self, gradient: &mut [f64]) -> Result<f64> {
191        let env = &self.env;
192        let mol = &self.mol;
193
194        mol.update(env, &self.coord, self.lattice.as_ref())?;
195        self.calc.load_parametrization(mol, env, self.params.method)?;
196        self.calc.set_accuracy(env, 1.0);
197        self.calc
198            .set_electronic_temperature(env, self.params.electronic_temperature);
199        self.calc.set_max_iterations(env, self.params.max_iterations);
200        let res = self.calc.single_point(mol, env)?;
201        let energy = res.get_energy(env)?;
202        res.get_gradient(env, gradient)?;
203        self.dipole = res.get_dipole(env)?.into();
204
205        Ok(energy)
206    }
207
208    /// Return last evaluated dipole moment. Return None if not calculated yet.
209    pub fn get_dipole(&self) -> Option<[f64; 3]> {
210        self.dipole
211    }
212}
213// bcd483ad ends here
214
215// [[file:../xtb.note::2398beeb][2398beeb]]
216#[test]
217fn test_xtb_method_into() {
218    let m: XtbMethod= "GFN0xTB".into();
219    assert_eq!(m, XtbMethod::GFN0xTB);
220    let m: XtbMethod= "GFN0-xTB".into();
221    assert_eq!(m, XtbMethod::GFN0xTB);
222    let m: XtbMethod= "gfn0-xtb".into();
223    assert_eq!(m, XtbMethod::GFN0xTB);
224
225    let m: XtbMethod= "GFN1xTB".into();
226    assert_eq!(m, XtbMethod::GFN1xTB);
227    let m: XtbMethod= "GFN2xTB".into();
228    assert_eq!(m, XtbMethod::GFN2xTB);
229    let m: XtbMethod= "GFNFF".into();
230    assert_eq!(m, XtbMethod::GFNFF);
231    let m: XtbMethod= "GFN-FF".into();
232    assert_eq!(m, XtbMethod::GFNFF);
233}
234
235#[should_panic]
236fn test_xtb_method_into_panic() {
237    let m: XtbMethod= "gfn-xtb".into();
238}
239// 2398beeb ends here