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 let d_scale = if l == 2 && n > 3 {
215 let ratio = 3.0 / n as f64;
216 ratio * ratio
217 } else {
218 1.0
219 };
220
221 table
222 .iter()
223 .map(|&(coeff, alpha)| GaussianPrimitive {
224 coeff,
225 alpha: alpha * zeta_sq * d_scale,
226 })
227 .collect()
228}
229
230pub fn gaussian_s_value(alpha: f64, r2: f64) -> f64 {
232 let norm = (2.0 * alpha / PI).powf(0.75);
233 norm * (-alpha * r2).exp()
234}
235
236pub fn gaussian_p_value(alpha: f64, r2: f64, component: f64) -> f64 {
239 let norm = (128.0 * alpha.powi(5) / (PI * PI * PI)).powf(0.25);
240 norm * component * (-alpha * r2).exp()
241}
242
243pub fn build_basis(elements: &[u8], positions: &[[f64; 3]]) -> Vec<AtomicOrbital> {
246 use super::params::get_params;
247
248 let ang_to_bohr = 1.0 / 0.529177249;
249 let mut basis = Vec::new();
250
251 for (atom_idx, (&z, pos)) in elements.iter().zip(positions.iter()).enumerate() {
252 let params = match get_params(z) {
253 Some(p) => p,
254 None => continue,
255 };
256
257 let center = [
258 pos[0] * ang_to_bohr,
259 pos[1] * ang_to_bohr,
260 pos[2] * ang_to_bohr,
261 ];
262
263 let symbol = params.symbol;
264
265 for orb_def in params.orbitals {
266 if orb_def.l == 0 {
267 basis.push(AtomicOrbital {
269 atom_index: atom_idx,
270 center,
271 n: orb_def.n,
272 l: 0,
273 m: 0,
274 vsip: orb_def.vsip,
275 zeta: orb_def.zeta,
276 label: format!("{}_{}", symbol, orb_def.label),
277 gaussians: sto3g_expansion(orb_def.n, 0, orb_def.zeta),
278 });
279 } else if orb_def.l == 1 {
280 let p_labels = ["x", "y", "z"];
282 let m_values: [i8; 3] = [-1, 0, 1];
283 for (idx, &m) in m_values.iter().enumerate() {
284 basis.push(AtomicOrbital {
285 atom_index: atom_idx,
286 center,
287 n: orb_def.n,
288 l: 1,
289 m,
290 vsip: orb_def.vsip,
291 zeta: orb_def.zeta,
292 label: format!("{}_{}{}", symbol, orb_def.label, p_labels[idx]),
293 gaussians: sto3g_expansion(orb_def.n, 1, orb_def.zeta),
294 });
295 }
296 } else if orb_def.l == 2 {
297 let d_labels = ["xy", "xz", "yz", "x2-y2", "z2"];
299 let m_values: [i8; 5] = [-2, -1, 0, 1, 2];
300 for (idx, &m) in m_values.iter().enumerate() {
301 basis.push(AtomicOrbital {
302 atom_index: atom_idx,
303 center,
304 n: orb_def.n,
305 l: 2,
306 m,
307 vsip: orb_def.vsip,
308 zeta: orb_def.zeta,
309 label: format!("{}_{}{}", symbol, orb_def.label, d_labels[idx]),
310 gaussians: sto3g_expansion(orb_def.n, 2, orb_def.zeta),
311 });
312 }
313 }
314 }
315 }
316
317 basis
318}
319
320#[cfg(test)]
321mod tests {
322 use super::*;
323
324 #[test]
325 fn test_sto3g_1s_expansion() {
326 let gs = sto3g_expansion(1, 0, 1.0);
327 assert_eq!(gs.len(), 3);
328 let sum: f64 = gs.iter().map(|g| g.coeff).sum();
330 assert!((sum - 1.134292).abs() < 0.01);
331 }
332
333 #[test]
334 fn test_sto3g_scaling() {
335 let gs1 = sto3g_expansion(1, 0, 1.0);
337 let gs2 = sto3g_expansion(1, 0, 2.0);
338 for (a, b) in gs1.iter().zip(gs2.iter()) {
339 assert!((b.alpha - a.alpha * 4.0).abs() < 1e-10);
340 assert!((b.coeff - a.coeff).abs() < 1e-10);
341 }
342 }
343
344 #[test]
345 fn test_build_basis_h2() {
346 let elements = [1u8, 1];
348 let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
349 let basis = build_basis(&elements, &positions);
350 assert_eq!(basis.len(), 2);
352 assert_eq!(basis[0].label, "H_1s");
353 assert_eq!(basis[1].label, "H_1s");
354 assert_eq!(basis[0].atom_index, 0);
355 assert_eq!(basis[1].atom_index, 1);
356 }
357
358 #[test]
359 fn test_build_basis_h2o() {
360 let elements = [8u8, 1, 1];
362 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
363 let basis = build_basis(&elements, &positions);
364 assert_eq!(basis.len(), 6);
366 }
367
368 #[test]
369 fn test_gaussian_s_normalization() {
370 let alpha = 1.0;
374 let val = gaussian_s_value(alpha, 0.0);
375 let expected = (2.0 * alpha / PI).powf(0.75);
376 assert!((val - expected).abs() < 1e-12);
377 }
378
379 #[test]
380 fn test_gaussian_s_decay() {
381 let alpha = 1.0;
382 let v0 = gaussian_s_value(alpha, 0.0);
383 let v1 = gaussian_s_value(alpha, 1.0);
384 let v5 = gaussian_s_value(alpha, 5.0);
385 assert!(v0 > v1);
386 assert!(v1 > v5);
387 assert!(v5 > 0.0);
388 }
389
390 #[test]
391 fn test_sto3g_d_expansion() {
392 let gs = sto3g_expansion(3, 2, 1.0);
393 assert_eq!(gs.len(), 3);
394 }
395
396 #[test]
397 fn test_fe_basis_contains_nine_functions() {
398 let elements = [26u8];
399 let positions = [[0.0, 0.0, 0.0]];
400 let basis = build_basis(&elements, &positions);
401 assert_eq!(basis.len(), 9);
403 }
404}