qualia_core_db/specialized_libs/chemistry_modeling/
dft.rs1use crate::specialized_libs::shared::zero_heap_algebra::ZeroHeapMatrix;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
14pub struct Dual {
15 pub v: f64, pub d: f64, }
18
19impl Dual {
20 pub fn new(v: f64, d: f64) -> Self {
21 Self { v, d }
22 }
23
24 pub fn add(self, other: Self) -> Self {
25 Self::new(self.v + other.v, self.d + other.d)
26 }
27
28 pub fn sub(self, other: Self) -> Self {
29 Self::new(self.v - other.v, self.d - other.d)
30 }
31
32 pub fn mul(self, other: Self) -> Self {
33 Self::new(self.v * other.v, self.d * other.v + self.v * other.d)
34 }
35
36 pub fn div(self, other: Self) -> Self {
37 Self::new(
38 self.v / other.v,
39 (self.d * other.v - self.v * other.d) / (other.v * other.v),
40 )
41 }
42
43 pub fn powf(self, power: f64) -> Self {
44 Self::new(
45 self.v.powf(power),
46 power * self.v.powf(power - 1.0) * self.d,
47 )
48 }
49
50 pub fn cbrt(self) -> Self {
51 self.powf(1.0 / 3.0)
52 }
53
54 pub fn scale(self, scalar: f64) -> Self {
55 Self::new(self.v * scalar, self.d * scalar)
56 }
57
58 pub fn ln(self) -> Self {
59 Self::new(self.v.ln(), self.d / self.v)
60 }
61
62 pub fn atan(self) -> Self {
63 Self::new(self.v.atan(), self.d / (1.0 + self.v * self.v))
64 }
65}
66
67pub fn lda_exchange(rho: f64) -> (f64, f64) {
71 if rho <= 1e-12 {
72 return (0.0, 0.0);
73 }
74
75 let r = Dual::new(rho, 1.0);
77 let factor = -0.75 * (3.0 / core::f64::consts::PI).powf(1.0 / 3.0);
78
79 let ex = r.cbrt().scale(factor); let vx = ex.scale(4.0 / 3.0); (ex.v, vx.v)
85}
86
87pub fn lda_correlation_vwn(rho: f64) -> (f64, f64) {
89 if rho <= 1e-12 {
90 return (0.0, 0.0);
91 }
92
93 let r_s = Dual::new(
95 (3.0 / (4.0 * core::f64::consts::PI * rho)).powf(1.0 / 3.0),
96 -1.0 / (3.0 * rho) * (3.0 / (4.0 * core::f64::consts::PI * rho)).powf(1.0 / 3.0),
97 );
98
99 let a = 0.0621814;
100 let x0 = -0.409286;
101 let b = 13.0720_f64;
102 let c = 42.7198_f64;
103
104 let x = r_s.powf(0.5);
105 let q = (4.0 * c - b * b).sqrt();
106
107 let x_func = x.mul(x).add(x.scale(b)).add(Dual::new(c, 0.0));
109 let x0_func = x0 * x0 + b * x0 + c;
110
111 let term1 = x.mul(x).div(x_func).ln();
114
115 let atan_arg = Dual::new(q, 0.0).div(x.scale(2.0).add(Dual::new(b, 0.0)));
116 let term2 = atan_arg.atan().scale(2.0 * b / q);
117
118 let term3_factor = (b * x0) / x0_func;
119
120 let x_minus_x0 = x.sub(Dual::new(x0, 0.0));
121 let term3_ln = x_minus_x0.mul(x_minus_x0).div(x_func).ln();
122 let term3_atan = atan_arg.atan().scale(2.0 * (b + 2.0 * x0) / q);
123
124 let term3 = term3_ln.add(term3_atan).scale(term3_factor);
125
126 let ec = term1.add(term2).sub(term3).scale(a);
127 let vc = ec.v + rho * ec.d; (ec.v, vc)
130}
131
132pub struct DftIntegrator {
134 pub grid_points: [[f64; 3]; 500],
136 pub grid_weights: [f64; 500],
137 pub n_points: usize,
138}
139
140impl DftIntegrator {
141 pub fn new() -> Self {
142 Self {
143 grid_points: [[0.0; 3]; 500],
144 grid_weights: [0.0; 500],
145 n_points: 0,
146 }
147 }
148
149 pub fn build_vxc<const N: usize>(
151 &self,
152 density: &ZeroHeapMatrix<f64, N, N>,
153 basis_values_at_grid: &[[f64; N]; 500],
154 ) -> (f64, ZeroHeapMatrix<f64, N, N>) {
155 let mut vxc = ZeroHeapMatrix::zeros();
156 let mut exc_total = 0.0;
157
158 for p in 0..self.n_points {
159 let weight = self.grid_weights[p];
160 let basis_vals = &basis_values_at_grid[p];
161
162 let mut rho_p = 0.0;
164 for mu in 0..N {
165 for nu in 0..N {
166 rho_p += density.get(mu, nu) * basis_vals[mu] * basis_vals[nu];
167 }
168 }
169
170 let (ex, vx) = lda_exchange(rho_p);
172 let (ec, vc) = lda_correlation_vwn(rho_p);
173
174 let e_xc = ex + ec;
175 let v_xc = vx + vc;
176
177 exc_total += e_xc * rho_p * weight;
178
179 for mu in 0..N {
181 for nu in 0..N {
182 let val = vxc.get(mu, nu) + v_xc * basis_vals[mu] * basis_vals[nu] * weight;
183 vxc.set(mu, nu, val);
184 }
185 }
186 }
187
188 (exc_total, vxc)
189 }
190}
191
192#[cfg(test)]
193mod tests {
194 use super::*;
195
196 #[test]
197 fn test_lda_exchange() {
198 let rho = 0.5;
199 let (ex, vx) = lda_exchange(rho);
200 let expected_ex =
204 -0.75 * (3.0 / core::f64::consts::PI).powf(1.0 / 3.0) * rho.powf(1.0 / 3.0);
205 let expected_vx = 4.0 / 3.0 * expected_ex;
206
207 assert!((ex - expected_ex).abs() < 1e-10);
208 assert!((vx - expected_vx).abs() < 1e-10);
209 }
210
211 #[test]
212 fn test_lda_correlation_vwn() {
213 let rho = 0.5;
214 let (ec, vc) = lda_correlation_vwn(rho);
215
216 assert!(!ec.is_nan());
218 assert!(!vc.is_nan());
219 }
220
221 #[test]
222 fn test_dual_number_ops() {
223 let x = Dual::new(2.0, 1.0);
227 let x2 = x.mul(x);
228 let x3 = x2.mul(x);
229
230 assert!((x3.v - 8.0).abs() < 1e-10);
231 assert!((x3.d - 12.0).abs() < 1e-10);
232 }
233}