1use serde::{Deserialize, Serialize};
7use std::f64::consts::PI;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct GaussianPrimitive {
12 pub coeff: f64,
14 pub alpha: f64,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct SlaterOrbital {
21 pub n: u8,
23 pub l: u8,
25 pub m: i8,
27 pub zeta: f64,
29}
30
31#[derive(Debug, Clone)]
33pub struct AtomicOrbital {
34 pub atom_index: usize,
36 pub center: [f64; 3],
38 pub n: u8,
40 pub l: u8,
42 pub m: i8,
44 pub vsip: f64,
46 pub zeta: f64,
48 pub label: String,
50 pub gaussians: Vec<GaussianPrimitive>,
52}
53
54static STO3G_1S: [(f64, f64); 3] = [
61 (0.154329, 2.227660),
62 (0.535328, 0.405771),
63 (0.444635, 0.109818),
64];
65
66static STO3G_2SP_INNER: [(f64, f64); 3] = [
69 (-0.099967, 2.581058),
70 (0.399513, 0.532013),
71 (0.700115, 0.168856),
72];
73
74static STO3G_2SP_OUTER: [(f64, f64); 3] = [
76 (0.155916, 2.581058),
77 (0.607684, 0.532013),
78 (0.391957, 0.168856),
79];
80
81static STO3G_3SP_INNER: [(f64, f64); 3] = [
83 (-0.219620, 0.994203),
84 (0.225595, 0.231031),
85 (0.900398, 0.075142),
86];
87
88static STO3G_3SP_OUTER: [(f64, f64); 3] = [
89 (0.010588, 0.994203),
90 (0.595167, 0.231031),
91 (0.462001, 0.075142),
92];
93
94static STO3G_4SP_INNER: [(f64, f64); 3] = [
96 (-0.310438, 0.494718),
97 (0.015515, 0.120193),
98 (1.023468, 0.040301),
99];
100
101static STO3G_4SP_OUTER: [(f64, f64); 3] = [
102 (-0.063311, 0.494718),
103 (0.574459, 0.120193),
104 (0.499768, 0.040301),
105];
106
107static STO3G_5SP_INNER: [(f64, f64); 3] = [
109 (-0.383204, 0.289515),
110 (-0.159438, 0.073780),
111 (1.143456, 0.025335),
112];
113
114static STO3G_5SP_OUTER: [(f64, f64); 3] = [
115 (-0.094810, 0.289515),
116 (0.570520, 0.073780),
117 (0.497340, 0.025335),
118];
119
120pub fn sto3g_expansion(n: u8, l: u8, zeta: f64) -> Vec<GaussianPrimitive> {
122 let zeta_sq = zeta * zeta;
123
124 let table: &[(f64, f64); 3] = match (n, l) {
125 (1, 0) => &STO3G_1S,
126 (2, 0) => &STO3G_2SP_INNER,
127 (2, 1) => &STO3G_2SP_OUTER,
128 (3, 0) => &STO3G_3SP_INNER,
129 (3, 1) => &STO3G_3SP_OUTER,
130 (4, 0) => &STO3G_4SP_INNER,
131 (4, 1) => &STO3G_4SP_OUTER,
132 (5, 0) => &STO3G_5SP_INNER,
133 (5, 1) => &STO3G_5SP_OUTER,
134 _ => return vec![],
135 };
136
137 table
138 .iter()
139 .map(|&(coeff, alpha)| GaussianPrimitive {
140 coeff,
141 alpha: alpha * zeta_sq,
142 })
143 .collect()
144}
145
146pub fn gaussian_s_value(alpha: f64, r2: f64) -> f64 {
148 let norm = (2.0 * alpha / PI).powf(0.75);
149 norm * (-alpha * r2).exp()
150}
151
152pub fn gaussian_p_value(alpha: f64, r2: f64, component: f64) -> f64 {
155 let norm = (128.0 * alpha.powi(5) / (PI * PI * PI)).powf(0.25);
156 norm * component * (-alpha * r2).exp()
157}
158
159pub fn build_basis(elements: &[u8], positions: &[[f64; 3]]) -> Vec<AtomicOrbital> {
162 use super::params::get_params;
163
164 let ang_to_bohr = 1.0 / 0.529177249;
165 let mut basis = Vec::new();
166
167 for (atom_idx, (&z, pos)) in elements.iter().zip(positions.iter()).enumerate() {
168 let params = match get_params(z) {
169 Some(p) => p,
170 None => continue,
171 };
172
173 let center = [
174 pos[0] * ang_to_bohr,
175 pos[1] * ang_to_bohr,
176 pos[2] * ang_to_bohr,
177 ];
178
179 let symbol = params.symbol;
180
181 for orb_def in params.orbitals {
182 if orb_def.l == 0 {
183 basis.push(AtomicOrbital {
185 atom_index: atom_idx,
186 center,
187 n: orb_def.n,
188 l: 0,
189 m: 0,
190 vsip: orb_def.vsip,
191 zeta: orb_def.zeta,
192 label: format!("{}_{}", symbol, orb_def.label),
193 gaussians: sto3g_expansion(orb_def.n, 0, orb_def.zeta),
194 });
195 } else if orb_def.l == 1 {
196 let p_labels = ["x", "y", "z"];
198 let m_values: [i8; 3] = [-1, 0, 1];
199 for (idx, &m) in m_values.iter().enumerate() {
200 basis.push(AtomicOrbital {
201 atom_index: atom_idx,
202 center,
203 n: orb_def.n,
204 l: 1,
205 m,
206 vsip: orb_def.vsip,
207 zeta: orb_def.zeta,
208 label: format!("{}_{}{}", symbol, orb_def.label, p_labels[idx]),
209 gaussians: sto3g_expansion(orb_def.n, 1, orb_def.zeta),
210 });
211 }
212 }
213 }
214 }
215
216 basis
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
222
223 #[test]
224 fn test_sto3g_1s_expansion() {
225 let gs = sto3g_expansion(1, 0, 1.0);
226 assert_eq!(gs.len(), 3);
227 let sum: f64 = gs.iter().map(|g| g.coeff).sum();
229 assert!((sum - 1.134292).abs() < 0.01);
230 }
231
232 #[test]
233 fn test_sto3g_scaling() {
234 let gs1 = sto3g_expansion(1, 0, 1.0);
236 let gs2 = sto3g_expansion(1, 0, 2.0);
237 for (a, b) in gs1.iter().zip(gs2.iter()) {
238 assert!((b.alpha - a.alpha * 4.0).abs() < 1e-10);
239 assert!((b.coeff - a.coeff).abs() < 1e-10);
240 }
241 }
242
243 #[test]
244 fn test_build_basis_h2() {
245 let elements = [1u8, 1];
247 let positions = [[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
248 let basis = build_basis(&elements, &positions);
249 assert_eq!(basis.len(), 2);
251 assert_eq!(basis[0].label, "H_1s");
252 assert_eq!(basis[1].label, "H_1s");
253 assert_eq!(basis[0].atom_index, 0);
254 assert_eq!(basis[1].atom_index, 1);
255 }
256
257 #[test]
258 fn test_build_basis_h2o() {
259 let elements = [8u8, 1, 1];
261 let positions = [[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
262 let basis = build_basis(&elements, &positions);
263 assert_eq!(basis.len(), 6);
265 }
266
267 #[test]
268 fn test_gaussian_s_normalization() {
269 let alpha = 1.0;
273 let val = gaussian_s_value(alpha, 0.0);
274 let expected = (2.0 * alpha / PI).powf(0.75);
275 assert!((val - expected).abs() < 1e-12);
276 }
277
278 #[test]
279 fn test_gaussian_s_decay() {
280 let alpha = 1.0;
281 let v0 = gaussian_s_value(alpha, 0.0);
282 let v1 = gaussian_s_value(alpha, 1.0);
283 let v5 = gaussian_s_value(alpha, 5.0);
284 assert!(v0 > v1);
285 assert!(v1 > v5);
286 assert!(v5 > 0.0);
287 }
288}