1use super::*;
7
8use libxtb::*;
9#[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 }
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 pub fn charge(&mut self, charge: f64) -> &mut Self {
63 self.charge = charge;
64 self
65 }
66
67 pub fn unpaired_electrons(&mut self, n: usize) -> &mut Self {
69 self.uhf = n;
70 self
71 }
72
73 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 pub fn max_iterations(&mut self, n: usize) -> &mut Self {
82 self.max_iterations = n;
83 self
84 }
85
86 pub fn output_verbose(&mut self) -> &mut Self {
88 self.verbosity = XtbOutputVerbosity::Verbose;
89 self
90 }
91
92 pub fn output_minimal(&mut self) -> &mut Self {
94 self.verbosity = XtbOutputVerbosity::Minimal;
95 self
96 }
97
98 pub fn output_muted(&mut self) -> &mut Self {
100 self.verbosity = XtbOutputVerbosity::Muted;
101 self
102 }
103
104 pub fn method(&mut self, method: impl Into<XtbMethod>) -> &mut Self {
106 self.method = method.into();
107 self
108 }
109
110 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}
117pub 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 dipole: Option<[f64; 3]>,
134}
135
136impl XtbModel {
137 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 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 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 pub fn get_dipole(&self) -> Option<[f64; 3]> {
210 self.dipole
211 }
212}
213#[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