1use nalgebra::DMatrix;
7
8use super::basis::BasisSet;
9use super::kinetic_matrix::build_kinetic_matrix;
10use super::nuclear_matrix::build_nuclear_matrix;
11use super::overlap_matrix::build_overlap_matrix;
12
13#[derive(Debug, Clone)]
15pub struct CoreMatrices {
16 pub overlap: DMatrix<f64>,
18 pub kinetic: DMatrix<f64>,
20 pub nuclear: DMatrix<f64>,
22 pub core_hamiltonian: DMatrix<f64>,
24 pub n_basis: usize,
26}
27
28impl CoreMatrices {
29 pub fn build(basis: &BasisSet, elements: &[u8], positions_bohr: &[[f64; 3]]) -> Self {
33 let s = build_overlap_matrix(basis);
34 let t = build_kinetic_matrix(basis);
35 let v = build_nuclear_matrix(basis, elements, positions_bohr);
36 let h_core = &t + &v;
37
38 CoreMatrices {
39 overlap: s,
40 kinetic: t,
41 nuclear: v,
42 core_hamiltonian: h_core,
43 n_basis: basis.n_basis,
44 }
45 }
46}
47
48pub fn nuclear_repulsion_energy(elements: &[u8], positions_bohr: &[[f64; 3]]) -> f64 {
52 let n_atoms = elements.len();
53 let mut e_nuc = 0.0;
54
55 for a in 0..n_atoms {
56 for b in (a + 1)..n_atoms {
57 let za = elements[a] as f64;
58 let zb = elements[b] as f64;
59 let dx = positions_bohr[a][0] - positions_bohr[b][0];
60 let dy = positions_bohr[a][1] - positions_bohr[b][1];
61 let dz = positions_bohr[a][2] - positions_bohr[b][2];
62 let r = (dx * dx + dy * dy + dz * dz).sqrt();
63 e_nuc += za * zb / r;
64 }
65 }
66
67 e_nuc
68}
69
70#[cfg(test)]
71mod tests {
72 use super::*;
73
74 #[test]
75 fn test_core_hamiltonian_symmetric() {
76 let elements = [8u8, 1, 1];
77 let positions = [
78 [0.0, 0.0, 0.2216],
79 [0.0, 1.4313, -0.8864],
80 [0.0, -1.4313, -0.8864],
81 ];
82 let basis = BasisSet::sto3g(&elements, &positions);
83 let matrices = CoreMatrices::build(&basis, &elements, &positions);
84
85 let h = &matrices.core_hamiltonian;
86 for i in 0..matrices.n_basis {
87 for j in 0..matrices.n_basis {
88 assert!(
89 (h[(i, j)] - h[(j, i)]).abs() < 1e-12,
90 "H⁰ not symmetric at ({}, {})",
91 i,
92 j
93 );
94 }
95 }
96 }
97
98 #[test]
99 fn test_h_core_equals_t_plus_v() {
100 let basis = BasisSet::sto3g(&[1, 1], &[[0.0, 0.0, 0.0], [1.4, 0.0, 0.0]]);
101 let matrices = CoreMatrices::build(&basis, &[1, 1], &[[0.0, 0.0, 0.0], [1.4, 0.0, 0.0]]);
102
103 for i in 0..matrices.n_basis {
104 for j in 0..matrices.n_basis {
105 let expected = matrices.kinetic[(i, j)] + matrices.nuclear[(i, j)];
106 assert!(
107 (matrices.core_hamiltonian[(i, j)] - expected).abs() < 1e-14,
108 "H⁰ ≠ T + V at ({}, {})",
109 i,
110 j
111 );
112 }
113 }
114 }
115
116 #[test]
117 fn test_nuclear_repulsion_h2() {
118 let e_nuc = nuclear_repulsion_energy(&[1, 1], &[[0.0, 0.0, 0.0], [1.4, 0.0, 0.0]]);
119 assert!((e_nuc - 1.0 / 1.4).abs() < 1e-10);
120 }
121
122 #[test]
123 fn test_overlap_diagonal_positive() {
124 let elements = [8u8, 1, 1];
125 let positions = [
126 [0.0, 0.0, 0.2216],
127 [0.0, 1.4313, -0.8864],
128 [0.0, -1.4313, -0.8864],
129 ];
130 let basis = BasisSet::sto3g(&elements, &positions);
131 let matrices = CoreMatrices::build(&basis, &elements, &positions);
132
133 for i in 0..matrices.n_basis {
134 assert!(matrices.overlap[(i, i)] > 0.0, "S[{i},{i}] should be > 0");
135 }
136 }
137
138 #[test]
139 fn test_core_matrices_water_dimensions() {
140 let elements = [8u8, 1, 1];
141 let positions = [[0.0, 0.0, 0.0], [1.43, 1.11, 0.0], [-1.43, 1.11, 0.0]];
142 let basis = BasisSet::sto3g(&elements, &positions);
143 let matrices = CoreMatrices::build(&basis, &elements, &positions);
144
145 assert_eq!(matrices.n_basis, 7);
146 assert_eq!(matrices.overlap.nrows(), 7);
147 assert_eq!(matrices.kinetic.nrows(), 7);
148 assert_eq!(matrices.nuclear.nrows(), 7);
149 assert_eq!(matrices.core_hamiltonian.nrows(), 7);
150 }
151}