Skip to main content

sci_form/scf/
basis.rs

1//! STO-3G minimal basis set with contracted Gaussian primitives.
2//!
3//! Implements the Hehre-Stewart-Pople STO-3G basis (1969) where each
4//! Slater-type orbital is approximated by 3 Gaussian primitives:
5//!
6//!   χ_STO(r) ≈ Σ_{i=1}^{3} c_i · g(α_i, r)
7//!
8//! where g(α, r) = N · r^l · exp(-α·r²) is a Cartesian Gaussian primitive.
9//!
10//! # Supported Elements
11//!
12//! H(1), He(2), Li(3), Be(4), B(5), C(6), N(7), O(8), F(9), Ne(10),
13//! Si(14), P(15), S(16), Cl(17), Br(35), I(53)
14
15use std::f64::consts::PI;
16
17/// A single Gaussian primitive: g(r) = coeff · exp(-alpha · r²)
18#[derive(Debug, Clone, Copy)]
19pub struct GaussianPrimitive {
20    /// Gaussian exponent α (Bohr⁻²).
21    pub alpha: f64,
22    /// Contraction coefficient (pre-normalized).
23    pub coefficient: f64,
24}
25
26/// A contracted Gaussian shell (a group of primitives sharing a center).
27#[derive(Debug, Clone)]
28pub struct ContractedShell {
29    /// Atom index this shell belongs to.
30    pub atom_index: usize,
31    /// Center coordinates in Bohr [x, y, z].
32    pub center: [f64; 3],
33    /// Angular momentum quantum number (0=s, 1=p, 2=d).
34    pub l: u32,
35    /// Gaussian primitives forming the contraction.
36    pub primitives: Vec<GaussianPrimitive>,
37}
38
39impl ContractedShell {
40    /// Number of Cartesian components for this shell.
41    /// s=1, p=3, d=6 (Cartesian)
42    pub fn n_cartesian(&self) -> usize {
43        match self.l {
44            0 => 1,
45            1 => 3,
46            2 => 6,
47            _ => ((self.l + 1) * (self.l + 2) / 2) as usize,
48        }
49    }
50
51    /// Evaluate the radial part at distance r² from center (s-type only).
52    pub fn evaluate_s(&self, r_sq: f64) -> f64 {
53        self.primitives
54            .iter()
55            .map(|p| p.coefficient * (-p.alpha * r_sq).exp())
56            .sum()
57    }
58}
59
60/// A basis function: one component of a contracted shell.
61#[derive(Debug, Clone)]
62pub struct BasisFunction {
63    /// Index of the atom this function is centered on.
64    pub atom_index: usize,
65    /// Center in Bohr.
66    pub center: [f64; 3],
67    /// Angular momentum: (l_x, l_y, l_z) such that l_x + l_y + l_z = l.
68    pub angular: [u32; 3],
69    /// Total angular momentum l = l_x + l_y + l_z.
70    pub l_total: u32,
71    /// Contracted Gaussian primitives.
72    pub primitives: Vec<GaussianPrimitive>,
73}
74
75impl BasisFunction {
76    /// Normalization constant for a Cartesian Gaussian.
77    ///
78    /// N = (2α/π)^{3/4} · (4α)^{l/2} · sqrt(1 / ((2lx-1)!! (2ly-1)!! (2lz-1)!!))
79    pub fn normalization(alpha: f64, lx: u32, ly: u32, lz: u32) -> f64 {
80        let l = lx + ly + lz;
81        let prefactor = (2.0 * alpha / PI).powf(0.75) * (4.0 * alpha).powf(l as f64 / 2.0);
82        let denom = odd_double_factorial(2 * lx as i32 - 1)
83            * odd_double_factorial(2 * ly as i32 - 1)
84            * odd_double_factorial(2 * lz as i32 - 1);
85        prefactor / denom.sqrt()
86    }
87
88    /// Evaluate this basis function at a point (x, y, z) in Bohr.
89    pub fn evaluate(&self, x: f64, y: f64, z: f64) -> f64 {
90        let dx = x - self.center[0];
91        let dy = y - self.center[1];
92        let dz = z - self.center[2];
93        let r_sq = dx * dx + dy * dy + dz * dz;
94
95        let angular_part = dx.powi(self.angular[0] as i32)
96            * dy.powi(self.angular[1] as i32)
97            * dz.powi(self.angular[2] as i32);
98
99        let radial: f64 = self
100            .primitives
101            .iter()
102            .map(|p| {
103                let norm =
104                    Self::normalization(p.alpha, self.angular[0], self.angular[1], self.angular[2]);
105                norm * p.coefficient * (-p.alpha * r_sq).exp()
106            })
107            .sum();
108
109        angular_part * radial
110    }
111}
112
113/// Double factorial: n!! = n · (n-2) · ... · 1
114/// Convention: 0!! = 1, (-1)!! = 1
115#[cfg(test)]
116fn double_factorial(n: u32) -> u64 {
117    if n <= 1 {
118        return 1;
119    }
120    let mut result = 1u64;
121    let mut k = n;
122    while k > 1 {
123        result *= k as u64;
124        k -= 2;
125    }
126    result
127}
128
129/// Odd double factorial: (2n-1)!! for normalization.
130/// Accepts negative inputs: (-1)!! = 1, (-3)!! = 1.
131fn odd_double_factorial(n: i32) -> f64 {
132    if n <= 0 {
133        return 1.0;
134    }
135    let mut acc = 1.0;
136    let mut k = n;
137    while k > 0 {
138        acc *= k as f64;
139        k -= 2;
140    }
141    acc
142}
143
144/// Complete basis set for a molecular system.
145#[derive(Debug, Clone)]
146pub struct BasisSet {
147    /// All basis functions in canonical order.
148    pub functions: Vec<BasisFunction>,
149    /// Shell structure (groups of functions sharing exponents).
150    pub shells: Vec<ContractedShell>,
151    /// Number of basis functions.
152    pub n_basis: usize,
153    /// Mapping: basis function index → atom index.
154    pub function_to_atom: Vec<usize>,
155}
156
157impl BasisSet {
158    /// Build STO-3G basis set for a molecular system.
159    pub fn sto3g(elements: &[u8], positions_bohr: &[[f64; 3]]) -> Self {
160        let mut functions = Vec::new();
161        let mut shells = Vec::new();
162        let mut function_to_atom = Vec::new();
163
164        for (atom_idx, (&z, &center)) in elements.iter().zip(positions_bohr.iter()).enumerate() {
165            let atom_shells = get_sto3g_shells(z, atom_idx, center);
166            for shell in &atom_shells {
167                match shell.l {
168                    0 => {
169                        functions.push(BasisFunction {
170                            atom_index: atom_idx,
171                            center,
172                            angular: [0, 0, 0],
173                            l_total: 0,
174                            primitives: shell.primitives.clone(),
175                        });
176                        function_to_atom.push(atom_idx);
177                    }
178                    1 => {
179                        for (lx, ly, lz) in [(1, 0, 0), (0, 1, 0), (0, 0, 1)] {
180                            functions.push(BasisFunction {
181                                atom_index: atom_idx,
182                                center,
183                                angular: [lx, ly, lz],
184                                l_total: 1,
185                                primitives: shell.primitives.clone(),
186                            });
187                            function_to_atom.push(atom_idx);
188                        }
189                    }
190                    2 => {
191                        for (lx, ly, lz) in [
192                            (2, 0, 0),
193                            (1, 1, 0),
194                            (1, 0, 1),
195                            (0, 2, 0),
196                            (0, 1, 1),
197                            (0, 0, 2),
198                        ] {
199                            functions.push(BasisFunction {
200                                atom_index: atom_idx,
201                                center,
202                                angular: [lx, ly, lz],
203                                l_total: 2,
204                                primitives: shell.primitives.clone(),
205                            });
206                            function_to_atom.push(atom_idx);
207                        }
208                    }
209                    _ => {}
210                }
211            }
212            shells.extend(atom_shells);
213        }
214
215        let n_basis = functions.len();
216        BasisSet {
217            functions,
218            shells,
219            n_basis,
220            function_to_atom,
221        }
222    }
223}
224
225/// Return the STO-3G shells for a given element.
226///
227/// Parameters from Hehre, Stewart, Pople, J. Chem. Phys. 51, 2657 (1969).
228fn get_sto3g_shells(z: u8, atom_index: usize, center: [f64; 3]) -> Vec<ContractedShell> {
229    match z {
230        // Hydrogen: 1s
231        1 => vec![ContractedShell {
232            atom_index,
233            center,
234            l: 0,
235            primitives: vec![
236                GaussianPrimitive {
237                    alpha: 3.42525091,
238                    coefficient: 0.15432897,
239                },
240                GaussianPrimitive {
241                    alpha: 0.62353014,
242                    coefficient: 0.53532814,
243                },
244                GaussianPrimitive {
245                    alpha: 0.16885540,
246                    coefficient: 0.44463454,
247                },
248            ],
249        }],
250
251        // Helium: 1s
252        2 => vec![ContractedShell {
253            atom_index,
254            center,
255            l: 0,
256            primitives: vec![
257                GaussianPrimitive {
258                    alpha: 6.36242139,
259                    coefficient: 0.15432897,
260                },
261                GaussianPrimitive {
262                    alpha: 1.15892300,
263                    coefficient: 0.53532814,
264                },
265                GaussianPrimitive {
266                    alpha: 0.31364979,
267                    coefficient: 0.44463454,
268                },
269            ],
270        }],
271
272        // Carbon: 1s, 2s, 2p
273        6 => vec![
274            ContractedShell {
275                atom_index,
276                center,
277                l: 0,
278                primitives: vec![
279                    GaussianPrimitive {
280                        alpha: 71.6168370,
281                        coefficient: 0.15432897,
282                    },
283                    GaussianPrimitive {
284                        alpha: 13.0450960,
285                        coefficient: 0.53532814,
286                    },
287                    GaussianPrimitive {
288                        alpha: 3.53051220,
289                        coefficient: 0.44463454,
290                    },
291                ],
292            },
293            ContractedShell {
294                atom_index,
295                center,
296                l: 0,
297                primitives: vec![
298                    GaussianPrimitive {
299                        alpha: 2.94124940,
300                        coefficient: -0.09996723,
301                    },
302                    GaussianPrimitive {
303                        alpha: 0.68348310,
304                        coefficient: 0.39951283,
305                    },
306                    GaussianPrimitive {
307                        alpha: 0.22228990,
308                        coefficient: 0.70011547,
309                    },
310                ],
311            },
312            ContractedShell {
313                atom_index,
314                center,
315                l: 1,
316                primitives: vec![
317                    GaussianPrimitive {
318                        alpha: 2.94124940,
319                        coefficient: 0.15591627,
320                    },
321                    GaussianPrimitive {
322                        alpha: 0.68348310,
323                        coefficient: 0.60768372,
324                    },
325                    GaussianPrimitive {
326                        alpha: 0.22228990,
327                        coefficient: 0.39195739,
328                    },
329                ],
330            },
331        ],
332
333        // Nitrogen: 1s, 2s, 2p
334        7 => vec![
335            ContractedShell {
336                atom_index,
337                center,
338                l: 0,
339                primitives: vec![
340                    GaussianPrimitive {
341                        alpha: 99.1061690,
342                        coefficient: 0.15432897,
343                    },
344                    GaussianPrimitive {
345                        alpha: 18.0523120,
346                        coefficient: 0.53532814,
347                    },
348                    GaussianPrimitive {
349                        alpha: 4.88566020,
350                        coefficient: 0.44463454,
351                    },
352                ],
353            },
354            ContractedShell {
355                atom_index,
356                center,
357                l: 0,
358                primitives: vec![
359                    GaussianPrimitive {
360                        alpha: 3.78045590,
361                        coefficient: -0.09996723,
362                    },
363                    GaussianPrimitive {
364                        alpha: 0.87849660,
365                        coefficient: 0.39951283,
366                    },
367                    GaussianPrimitive {
368                        alpha: 0.28571440,
369                        coefficient: 0.70011547,
370                    },
371                ],
372            },
373            ContractedShell {
374                atom_index,
375                center,
376                l: 1,
377                primitives: vec![
378                    GaussianPrimitive {
379                        alpha: 3.78045590,
380                        coefficient: 0.15591627,
381                    },
382                    GaussianPrimitive {
383                        alpha: 0.87849660,
384                        coefficient: 0.60768372,
385                    },
386                    GaussianPrimitive {
387                        alpha: 0.28571440,
388                        coefficient: 0.39195739,
389                    },
390                ],
391            },
392        ],
393
394        // Oxygen: 1s, 2s, 2p
395        8 => vec![
396            ContractedShell {
397                atom_index,
398                center,
399                l: 0,
400                primitives: vec![
401                    GaussianPrimitive {
402                        alpha: 130.709320,
403                        coefficient: 0.15432897,
404                    },
405                    GaussianPrimitive {
406                        alpha: 23.8088610,
407                        coefficient: 0.53532814,
408                    },
409                    GaussianPrimitive {
410                        alpha: 6.44360830,
411                        coefficient: 0.44463454,
412                    },
413                ],
414            },
415            ContractedShell {
416                atom_index,
417                center,
418                l: 0,
419                primitives: vec![
420                    GaussianPrimitive {
421                        alpha: 5.03315130,
422                        coefficient: -0.09996723,
423                    },
424                    GaussianPrimitive {
425                        alpha: 1.16959610,
426                        coefficient: 0.39951283,
427                    },
428                    GaussianPrimitive {
429                        alpha: 0.38038900,
430                        coefficient: 0.70011547,
431                    },
432                ],
433            },
434            ContractedShell {
435                atom_index,
436                center,
437                l: 1,
438                primitives: vec![
439                    GaussianPrimitive {
440                        alpha: 5.03315130,
441                        coefficient: 0.15591627,
442                    },
443                    GaussianPrimitive {
444                        alpha: 1.16959610,
445                        coefficient: 0.60768372,
446                    },
447                    GaussianPrimitive {
448                        alpha: 0.38038900,
449                        coefficient: 0.39195739,
450                    },
451                ],
452            },
453        ],
454
455        // Fluorine: 1s, 2s, 2p
456        9 => vec![
457            ContractedShell {
458                atom_index,
459                center,
460                l: 0,
461                primitives: vec![
462                    GaussianPrimitive {
463                        alpha: 166.679130,
464                        coefficient: 0.15432897,
465                    },
466                    GaussianPrimitive {
467                        alpha: 30.3608120,
468                        coefficient: 0.53532814,
469                    },
470                    GaussianPrimitive {
471                        alpha: 8.21682070,
472                        coefficient: 0.44463454,
473                    },
474                ],
475            },
476            ContractedShell {
477                atom_index,
478                center,
479                l: 0,
480                primitives: vec![
481                    GaussianPrimitive {
482                        alpha: 6.46480320,
483                        coefficient: -0.09996723,
484                    },
485                    GaussianPrimitive {
486                        alpha: 1.50228120,
487                        coefficient: 0.39951283,
488                    },
489                    GaussianPrimitive {
490                        alpha: 0.48858850,
491                        coefficient: 0.70011547,
492                    },
493                ],
494            },
495            ContractedShell {
496                atom_index,
497                center,
498                l: 1,
499                primitives: vec![
500                    GaussianPrimitive {
501                        alpha: 6.46480320,
502                        coefficient: 0.15591627,
503                    },
504                    GaussianPrimitive {
505                        alpha: 1.50228120,
506                        coefficient: 0.60768372,
507                    },
508                    GaussianPrimitive {
509                        alpha: 0.48858850,
510                        coefficient: 0.39195739,
511                    },
512                ],
513            },
514        ],
515
516        // Phosphorus: 1s, 2s, 2p, 3s, 3p
517        15 => vec![
518            ContractedShell {
519                atom_index,
520                center,
521                l: 0,
522                primitives: vec![
523                    GaussianPrimitive {
524                        alpha: 508.291310,
525                        coefficient: 0.15432897,
526                    },
527                    GaussianPrimitive {
528                        alpha: 92.5891370,
529                        coefficient: 0.53532814,
530                    },
531                    GaussianPrimitive {
532                        alpha: 25.0571730,
533                        coefficient: 0.44463454,
534                    },
535                ],
536            },
537            ContractedShell {
538                atom_index,
539                center,
540                l: 0,
541                primitives: vec![
542                    GaussianPrimitive {
543                        alpha: 18.5172490,
544                        coefficient: -0.09996723,
545                    },
546                    GaussianPrimitive {
547                        alpha: 4.30422160,
548                        coefficient: 0.39951283,
549                    },
550                    GaussianPrimitive {
551                        alpha: 1.39999670,
552                        coefficient: 0.70011547,
553                    },
554                ],
555            },
556            ContractedShell {
557                atom_index,
558                center,
559                l: 1,
560                primitives: vec![
561                    GaussianPrimitive {
562                        alpha: 18.5172490,
563                        coefficient: 0.15591627,
564                    },
565                    GaussianPrimitive {
566                        alpha: 4.30422160,
567                        coefficient: 0.60768372,
568                    },
569                    GaussianPrimitive {
570                        alpha: 1.39999670,
571                        coefficient: 0.39195739,
572                    },
573                ],
574            },
575            ContractedShell {
576                atom_index,
577                center,
578                l: 0,
579                primitives: vec![
580                    GaussianPrimitive {
581                        alpha: 1.56367880,
582                        coefficient: -0.09996723,
583                    },
584                    GaussianPrimitive {
585                        alpha: 0.36368650,
586                        coefficient: 0.39951283,
587                    },
588                    GaussianPrimitive {
589                        alpha: 0.11828520,
590                        coefficient: 0.70011547,
591                    },
592                ],
593            },
594            ContractedShell {
595                atom_index,
596                center,
597                l: 1,
598                primitives: vec![
599                    GaussianPrimitive {
600                        alpha: 1.56367880,
601                        coefficient: 0.15591627,
602                    },
603                    GaussianPrimitive {
604                        alpha: 0.36368650,
605                        coefficient: 0.60768372,
606                    },
607                    GaussianPrimitive {
608                        alpha: 0.11828520,
609                        coefficient: 0.39195739,
610                    },
611                ],
612            },
613        ],
614
615        // Sulfur: 1s, 2s, 2p, 3s, 3p
616        16 => vec![
617            ContractedShell {
618                atom_index,
619                center,
620                l: 0,
621                primitives: vec![
622                    GaussianPrimitive {
623                        alpha: 598.642680,
624                        coefficient: 0.15432897,
625                    },
626                    GaussianPrimitive {
627                        alpha: 109.046680,
628                        coefficient: 0.53532814,
629                    },
630                    GaussianPrimitive {
631                        alpha: 29.5121090,
632                        coefficient: 0.44463454,
633                    },
634                ],
635            },
636            ContractedShell {
637                atom_index,
638                center,
639                l: 0,
640                primitives: vec![
641                    GaussianPrimitive {
642                        alpha: 22.1564680,
643                        coefficient: -0.09996723,
644                    },
645                    GaussianPrimitive {
646                        alpha: 5.14855900,
647                        coefficient: 0.39951283,
648                    },
649                    GaussianPrimitive {
650                        alpha: 1.67441430,
651                        coefficient: 0.70011547,
652                    },
653                ],
654            },
655            ContractedShell {
656                atom_index,
657                center,
658                l: 1,
659                primitives: vec![
660                    GaussianPrimitive {
661                        alpha: 22.1564680,
662                        coefficient: 0.15591627,
663                    },
664                    GaussianPrimitive {
665                        alpha: 5.14855900,
666                        coefficient: 0.60768372,
667                    },
668                    GaussianPrimitive {
669                        alpha: 1.67441430,
670                        coefficient: 0.39195739,
671                    },
672                ],
673            },
674            ContractedShell {
675                atom_index,
676                center,
677                l: 0,
678                primitives: vec![
679                    GaussianPrimitive {
680                        alpha: 1.80579080,
681                        coefficient: -0.09996723,
682                    },
683                    GaussianPrimitive {
684                        alpha: 0.41988400,
685                        coefficient: 0.39951283,
686                    },
687                    GaussianPrimitive {
688                        alpha: 0.13655330,
689                        coefficient: 0.70011547,
690                    },
691                ],
692            },
693            ContractedShell {
694                atom_index,
695                center,
696                l: 1,
697                primitives: vec![
698                    GaussianPrimitive {
699                        alpha: 1.80579080,
700                        coefficient: 0.15591627,
701                    },
702                    GaussianPrimitive {
703                        alpha: 0.41988400,
704                        coefficient: 0.60768372,
705                    },
706                    GaussianPrimitive {
707                        alpha: 0.13655330,
708                        coefficient: 0.39195739,
709                    },
710                ],
711            },
712        ],
713
714        // Chlorine: 1s, 2s, 2p, 3s, 3p
715        17 => vec![
716            ContractedShell {
717                atom_index,
718                center,
719                l: 0,
720                primitives: vec![
721                    GaussianPrimitive {
722                        alpha: 696.408520,
723                        coefficient: 0.15432897,
724                    },
725                    GaussianPrimitive {
726                        alpha: 126.888800,
727                        coefficient: 0.53532814,
728                    },
729                    GaussianPrimitive {
730                        alpha: 34.3399080,
731                        coefficient: 0.44463454,
732                    },
733                ],
734            },
735            ContractedShell {
736                atom_index,
737                center,
738                l: 0,
739                primitives: vec![
740                    GaussianPrimitive {
741                        alpha: 25.9670530,
742                        coefficient: -0.09996723,
743                    },
744                    GaussianPrimitive {
745                        alpha: 6.03406090,
746                        coefficient: 0.39951283,
747                    },
748                    GaussianPrimitive {
749                        alpha: 1.96235810,
750                        coefficient: 0.70011547,
751                    },
752                ],
753            },
754            ContractedShell {
755                atom_index,
756                center,
757                l: 1,
758                primitives: vec![
759                    GaussianPrimitive {
760                        alpha: 25.9670530,
761                        coefficient: 0.15591627,
762                    },
763                    GaussianPrimitive {
764                        alpha: 6.03406090,
765                        coefficient: 0.60768372,
766                    },
767                    GaussianPrimitive {
768                        alpha: 1.96235810,
769                        coefficient: 0.39195739,
770                    },
771                ],
772            },
773            ContractedShell {
774                atom_index,
775                center,
776                l: 0,
777                primitives: vec![
778                    GaussianPrimitive {
779                        alpha: 2.14407210,
780                        coefficient: -0.09996723,
781                    },
782                    GaussianPrimitive {
783                        alpha: 0.49841410,
784                        coefficient: 0.39951283,
785                    },
786                    GaussianPrimitive {
787                        alpha: 0.16208590,
788                        coefficient: 0.70011547,
789                    },
790                ],
791            },
792            ContractedShell {
793                atom_index,
794                center,
795                l: 1,
796                primitives: vec![
797                    GaussianPrimitive {
798                        alpha: 2.14407210,
799                        coefficient: 0.15591627,
800                    },
801                    GaussianPrimitive {
802                        alpha: 0.49841410,
803                        coefficient: 0.60768372,
804                    },
805                    GaussianPrimitive {
806                        alpha: 0.16208590,
807                        coefficient: 0.39195739,
808                    },
809                ],
810            },
811        ],
812
813        // Default fallback: use scaled hydrogen-like 1s
814        _ => {
815            let zeta = 0.5 * (z as f64).sqrt();
816            vec![ContractedShell {
817                atom_index,
818                center,
819                l: 0,
820                primitives: vec![
821                    GaussianPrimitive {
822                        alpha: 3.42525091 * zeta * zeta,
823                        coefficient: 0.15432897,
824                    },
825                    GaussianPrimitive {
826                        alpha: 0.62353014 * zeta * zeta,
827                        coefficient: 0.53532814,
828                    },
829                    GaussianPrimitive {
830                        alpha: 0.16885540 * zeta * zeta,
831                        coefficient: 0.44463454,
832                    },
833                ],
834            }]
835        }
836    }
837}
838
839/// Number of valence electrons for common elements.
840pub fn valence_electrons(z: u8) -> usize {
841    match z {
842        1 => 1,
843        2 => 2,
844        3 => 1,
845        4 => 2,
846        5 => 3,
847        6 => 4,
848        7 => 5,
849        8 => 6,
850        9 => 7,
851        10 => 8,
852        14 => 4,
853        15 => 5,
854        16 => 6,
855        17 => 7,
856        35 => 7,
857        53 => 7,
858        _ => z as usize,
859    }
860}
861
862#[cfg(test)]
863mod tests {
864    use super::*;
865
866    #[test]
867    fn test_hydrogen_basis_size() {
868        let basis = BasisSet::sto3g(&[1], &[[0.0, 0.0, 0.0]]);
869        assert_eq!(basis.n_basis, 1);
870        assert_eq!(basis.functions[0].l_total, 0);
871    }
872
873    #[test]
874    fn test_carbon_basis_size() {
875        let basis = BasisSet::sto3g(&[6], &[[0.0, 0.0, 0.0]]);
876        assert_eq!(basis.n_basis, 5); // 1s + 2s + 2px + 2py + 2pz
877    }
878
879    #[test]
880    fn test_water_basis_size() {
881        let basis = BasisSet::sto3g(
882            &[8, 1, 1],
883            &[[0.0, 0.0, 0.0], [1.43, 1.11, 0.0], [-1.43, 1.11, 0.0]],
884        );
885        assert_eq!(basis.n_basis, 7); // O(5) + H(1) + H(1)
886    }
887
888    #[test]
889    fn test_double_factorial() {
890        assert_eq!(double_factorial(0), 1);
891        assert_eq!(double_factorial(1), 1);
892        assert_eq!(double_factorial(5), 15);
893        assert_eq!(double_factorial(6), 48);
894    }
895
896    #[test]
897    fn test_s_function_evaluation() {
898        let basis = BasisSet::sto3g(&[1], &[[0.0, 0.0, 0.0]]);
899        let val = basis.functions[0].evaluate(0.0, 0.0, 0.0);
900        assert!(val > 0.0);
901    }
902}