1use serde::{Deserialize, Serialize};
7use std::f64::consts::PI;
8
9fn odd_double_factorial(n: i32) -> f64 {
10 if n <= 0 {
11 return 1.0;
12 }
13 let mut acc = 1.0;
14 let mut k = n;
15 while k > 0 {
16 acc *= k as f64;
17 k -= 2;
18 }
19 acc
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct GaussianPrimitive {
25 pub coeff: f64,
27 pub alpha: f64,
29}
30
31#[derive(Debug, Clone, Serialize, Deserialize)]
33pub struct SlaterOrbital {
34 pub n: u8,
36 pub l: u8,
38 pub m: i8,
40 pub zeta: f64,
42}
43
44#[derive(Debug, Clone)]
46pub struct AtomicOrbital {
47 pub atom_index: usize,
49 pub center: [f64; 3],
51 pub n: u8,
53 pub l: u8,
55 pub m: i8,
57 pub vsip: f64,
59 pub zeta: f64,
61 pub label: String,
63 pub gaussians: Vec<GaussianPrimitive>,
65}
66
67static STO3G_1S: [(f64, f64); 3] = [
74 (0.154329, 2.227660),
75 (0.535328, 0.405771),
76 (0.444635, 0.109818),
77];
78
79static STO3G_2SP_INNER: [(f64, f64); 3] = [
82 (-0.099967, 2.581058),
83 (0.399513, 0.532013),
84 (0.700115, 0.168856),
85];
86
87static STO3G_2SP_OUTER: [(f64, f64); 3] = [
89 (0.155916, 2.581058),
90 (0.607684, 0.532013),
91 (0.391957, 0.168856),
92];
93
94static STO3G_3SP_INNER: [(f64, f64); 3] = [
96 (-0.219620, 0.994203),
97 (0.225595, 0.231031),
98 (0.900398, 0.075142),
99];
100
101static STO3G_3SP_OUTER: [(f64, f64); 3] = [
102 (0.010588, 0.994203),
103 (0.595167, 0.231031),
104 (0.462001, 0.075142),
105];
106
107static STO3G_4SP_INNER: [(f64, f64); 3] = [
109 (-0.310438, 0.494718),
110 (0.015515, 0.120193),
111 (1.023468, 0.040301),
112];
113
114static STO3G_4SP_OUTER: [(f64, f64); 3] = [
115 (-0.063311, 0.494718),
116 (0.574459, 0.120193),
117 (0.499768, 0.040301),
118];
119
120static STO3G_5SP_INNER: [(f64, f64); 3] = [
122 (-0.383204, 0.289515),
123 (-0.159438, 0.073780),
124 (1.143456, 0.025335),
125];
126
127static STO3G_5SP_OUTER: [(f64, f64); 3] = [
128 (-0.094810, 0.289515),
129 (0.570520, 0.073780),
130 (0.497340, 0.025335),
131];
132
133static STO3G_3D: [(f64, f64); 3] = [
138 (0.219767, 2.488296),
139 (0.655547, 0.798105),
140 (0.286573, 0.331966),
141];
142
143pub fn orbital_cartesian_terms(l: u8, m: i8) -> Vec<(f64, [u8; 3])> {
148 match (l, m) {
149 (0, _) => vec![(1.0, [0, 0, 0])],
150 (1, -1) => vec![(1.0, [1, 0, 0])], (1, 0) => vec![(1.0, [0, 1, 0])], (1, 1) => vec![(1.0, [0, 0, 1])], (2, -2) => vec![(1.0, [1, 1, 0])], (2, -1) => vec![(1.0, [1, 0, 1])], (2, 0) => vec![(1.0, [0, 1, 1])], (2, 1) => vec![
158 (1.0 / 2.0f64.sqrt(), [2, 0, 0]),
159 (-1.0 / 2.0f64.sqrt(), [0, 2, 0]),
160 ],
161 (2, 2) => vec![
163 (-1.0 / 6.0f64.sqrt(), [2, 0, 0]),
164 (-1.0 / 6.0f64.sqrt(), [0, 2, 0]),
165 (2.0 / 6.0f64.sqrt(), [0, 0, 2]),
166 ],
167 _ => vec![],
168 }
169}
170
171pub fn gaussian_cartesian_norm(alpha: f64, lx: u8, ly: u8, lz: u8) -> f64 {
174 let lsum = (lx + ly + lz) as i32;
175 let pref = (2.0 * alpha / PI).powf(0.75);
176 let ang = (4.0 * alpha).powf(lsum as f64 / 2.0);
177 let denom = (odd_double_factorial(2 * lx as i32 - 1)
178 * odd_double_factorial(2 * ly as i32 - 1)
179 * odd_double_factorial(2 * lz as i32 - 1))
180 .sqrt();
181 pref * ang / denom
182}
183
184pub fn gaussian_cartesian_value(alpha: f64, x: f64, y: f64, z: f64, lx: u8, ly: u8, lz: u8) -> f64 {
187 let norm = gaussian_cartesian_norm(alpha, lx, ly, lz);
188 let poly = x.powi(lx as i32) * y.powi(ly as i32) * z.powi(lz as i32);
189 norm * poly * (-alpha * (x * x + y * y + z * z)).exp()
190}
191
192pub fn sto3g_expansion(n: u8, l: u8, zeta: f64) -> Vec<GaussianPrimitive> {
194 let zeta_sq = zeta * zeta;
195
196 let table: &[(f64, f64); 3] = match (n, l) {
197 (1, 0) => &STO3G_1S,
198 (2, 0) => &STO3G_2SP_INNER,
199 (2, 1) => &STO3G_2SP_OUTER,
200 (3, 0) => &STO3G_3SP_INNER,
201 (3, 1) => &STO3G_3SP_OUTER,
202 (3, 2) => &STO3G_3D,
203 (4, 0) => &STO3G_4SP_INNER,
204 (4, 1) => &STO3G_4SP_OUTER,
205 (4, 2) => &STO3G_3D,
206 (5, 0) => &STO3G_5SP_INNER,
207 (5, 1) => &STO3G_5SP_OUTER,
208 (5, 2) => &STO3G_3D,
209 _ => return vec![],
210 };
211
212 table
213 .iter()
214 .map(|&(coeff, alpha)| GaussianPrimitive {
215 coeff,
216 alpha: alpha * zeta_sq,
217 })
218 .collect()
219}
220
221pub fn gaussian_s_value(alpha: f64, r2: f64) -> f64 {
223 let norm = (2.0 * alpha / PI).powf(0.75);
224 norm * (-alpha * r2).exp()
225}
226
227pub fn gaussian_p_value(alpha: f64, r2: f64, component: f64) -> f64 {
230 let norm = (128.0 * alpha.powi(5) / (PI * PI * PI)).powf(0.25);
231 norm * component * (-alpha * r2).exp()
232}
233
234pub fn build_basis(elements: &[u8], positions: &[[f64; 3]]) -> Vec<AtomicOrbital> {
237 use super::params::get_params;
238
239 let ang_to_bohr = 1.0 / 0.529177249;
240 let mut basis = Vec::new();
241
242 for (atom_idx, (&z, pos)) in elements.iter().zip(positions.iter()).enumerate() {
243 let params = match get_params(z) {
244 Some(p) => p,
245 None => continue,
246 };
247
248 let center = [
249 pos[0] * ang_to_bohr,
250 pos[1] * ang_to_bohr,
251 pos[2] * ang_to_bohr,
252 ];
253
254 let symbol = params.symbol;
255
256 for orb_def in params.orbitals {
257 if orb_def.l == 0 {
258 basis.push(AtomicOrbital {
260 atom_index: atom_idx,
261 center,
262 n: orb_def.n,
263 l: 0,
264 m: 0,
265 vsip: orb_def.vsip,
266 zeta: orb_def.zeta,
267 label: format!("{}_{}", symbol, orb_def.label),
268 gaussians: sto3g_expansion(orb_def.n, 0, orb_def.zeta),
269 });
270 } else if orb_def.l == 1 {
271 let p_labels = ["x", "y", "z"];
273 let m_values: [i8; 3] = [-1, 0, 1];
274 for (idx, &m) in m_values.iter().enumerate() {
275 basis.push(AtomicOrbital {
276 atom_index: atom_idx,
277 center,
278 n: orb_def.n,
279 l: 1,
280 m,
281 vsip: orb_def.vsip,
282 zeta: orb_def.zeta,
283 label: format!("{}_{}{}", symbol, orb_def.label, p_labels[idx]),
284 gaussians: sto3g_expansion(orb_def.n, 1, orb_def.zeta),
285 });
286 }
287 } else if orb_def.l == 2 {
288 let d_labels = ["xy", "xz", "yz", "x2-y2", "z2"];
290 let m_values: [i8; 5] = [-2, -1, 0, 1, 2];
291 for (idx, &m) in m_values.iter().enumerate() {
292 basis.push(AtomicOrbital {
293 atom_index: atom_idx,
294 center,
295 n: orb_def.n,
296 l: 2,
297 m,
298 vsip: orb_def.vsip,
299 zeta: orb_def.zeta,
300 label: format!("{}_{}{}", symbol, orb_def.label, d_labels[idx]),
301 gaussians: sto3g_expansion(orb_def.n, 2, orb_def.zeta),
302 });
303 }
304 }
305 }
306 }
307
308 basis
309}
310
311#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_sto3g_1s_expansion() {
317 let gs = sto3g_expansion(1, 0, 1.0);
318 assert_eq!(gs.len(), 3);
319 let sum: f64 = gs.iter().map(|g| g.coeff).sum();
321 assert!((sum - 1.134292).abs() < 0.01);
322 }
323
324 #[test]
325 fn test_sto3g_scaling() {
326 let gs1 = sto3g_expansion(1, 0, 1.0);
328 let gs2 = sto3g_expansion(1, 0, 2.0);
329 for (a, b) in gs1.iter().zip(gs2.iter()) {
330 assert!((b.alpha - a.alpha * 4.0).abs() < 1e-10);
331 assert!((b.coeff - a.coeff).abs() < 1e-10);
332 }
333 }
334
335 #[test]
336 fn test_build_basis_h2() {
337 let elements = [1u8, 1];
339 let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
340 let basis = build_basis(&elements, &positions);
341 assert_eq!(basis.len(), 2);
343 assert_eq!(basis[0].label, "H_1s");
344 assert_eq!(basis[1].label, "H_1s");
345 assert_eq!(basis[0].atom_index, 0);
346 assert_eq!(basis[1].atom_index, 1);
347 }
348
349 #[test]
350 fn test_build_basis_h2o() {
351 let elements = [8u8, 1, 1];
353 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
354 let basis = build_basis(&elements, &positions);
355 assert_eq!(basis.len(), 6);
357 }
358
359 #[test]
360 fn test_gaussian_s_normalization() {
361 let alpha = 1.0;
365 let val = gaussian_s_value(alpha, 0.0);
366 let expected = (2.0 * alpha / PI).powf(0.75);
367 assert!((val - expected).abs() < 1e-12);
368 }
369
370 #[test]
371 fn test_gaussian_s_decay() {
372 let alpha = 1.0;
373 let v0 = gaussian_s_value(alpha, 0.0);
374 let v1 = gaussian_s_value(alpha, 1.0);
375 let v5 = gaussian_s_value(alpha, 5.0);
376 assert!(v0 > v1);
377 assert!(v1 > v5);
378 assert!(v5 > 0.0);
379 }
380
381 #[test]
382 fn test_sto3g_d_expansion() {
383 let gs = sto3g_expansion(3, 2, 1.0);
384 assert_eq!(gs.len(), 3);
385 }
386
387 #[test]
388 fn test_fe_basis_contains_nine_functions() {
389 let elements = [26u8];
390 let positions = [[0.0, 0.0, 0.0]];
391 let basis = build_basis(&elements, &positions);
392 assert_eq!(basis.len(), 9);
394 }
395}