1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
//! Direct Inversion in Iterative Subspace (DIIS) convergence accelerator.
//!
//! DIIS extrapolates the Fock matrix from a history of previous iterations
//! to accelerate SCF convergence. The DIIS error vector is:
//!
//! e_i = F_i P_i S - S P_i F_i
//!
//! The optimal linear combination minimizes ||Σ c_i e_i||² subject to Σ c_i = 1.
//!
//! # References
//!
//! P. Pulay, Chem. Phys. Lett. 73, 393 (1980).
//! P. Pulay, J. Comput. Chem. 3, 556 (1982).
use nalgebra::{DMatrix, DVector};
/// DIIS accelerator state.
pub struct DiisAccelerator {
/// History of Fock matrices.
fock_history: Vec<DMatrix<f64>>,
/// History of error vectors (flattened).
error_history: Vec<DVector<f64>>,
/// Maximum subspace size.
max_size: usize,
}
impl DiisAccelerator {
/// Create a new DIIS accelerator with the given subspace size.
pub fn new(max_size: usize) -> Self {
Self {
fock_history: Vec::with_capacity(max_size),
error_history: Vec::with_capacity(max_size),
max_size,
}
}
/// Add an SCF iteration to the DIIS history.
///
/// The error vector is computed as: e = FPS - SPF
pub fn add_iteration(
&mut self,
fock: &DMatrix<f64>,
density: &DMatrix<f64>,
overlap: &DMatrix<f64>,
) {
// Compute DIIS error: e = FPS - SPF
let fps = fock * density * overlap;
let spf = overlap * density * fock;
let error_matrix = &fps - &spf;
// Flatten error matrix to vector
let n = error_matrix.nrows();
let error_vec = DVector::from_iterator(n * n, error_matrix.iter().copied());
// Enforce max subspace size
if self.fock_history.len() >= self.max_size {
self.fock_history.remove(0);
self.error_history.remove(0);
}
self.fock_history.push(fock.clone());
self.error_history.push(error_vec);
}
/// Extrapolate the optimal Fock matrix from the DIIS subspace.
///
/// Returns the extrapolated Fock matrix, or None if not enough
/// history is available (need at least 2 iterations).
pub fn extrapolate(&self) -> Option<DMatrix<f64>> {
let m = self.error_history.len();
if m < 2 {
return None;
}
// Build the DIIS B matrix:
// B[i][j] = e_i · e_j
// With Lagrange constraint: last row/col = -1/1
let dim = m + 1;
let mut b = DMatrix::zeros(dim, dim);
for i in 0..m {
for j in 0..=i {
let bij = self.error_history[i].dot(&self.error_history[j]);
b[(i, j)] = bij;
b[(j, i)] = bij;
}
}
// Lagrange constraint
for i in 0..m {
b[(m, i)] = -1.0;
b[(i, m)] = -1.0;
}
b[(m, m)] = 0.0;
// Right-hand side: [0, 0, ..., 0, -1]
let mut rhs = DVector::zeros(dim);
rhs[m] = -1.0;
// Solve B · c = rhs
let coefficients = solve_linear_system(&b, &rhs)?;
// Extrapolate: F_diis = Σ c_i F_i
let n = self.fock_history[0].nrows();
let mut f_diis = DMatrix::zeros(n, n);
for i in 0..m {
f_diis += coefficients[i] * &self.fock_history[i];
}
Some(f_diis)
}
/// Maximum error norm in the current subspace.
pub fn max_error(&self) -> f64 {
self.error_history
.last()
.map(|e| e.norm())
.unwrap_or(f64::MAX)
}
/// Reset the DIIS subspace (useful when convergence stalls).
pub fn reset(&mut self) {
self.fock_history.clear();
self.error_history.clear();
}
/// Number of stored iterations.
pub fn size(&self) -> usize {
self.fock_history.len()
}
}
/// Solve a linear system Ax = b using LU decomposition.
fn solve_linear_system(a: &DMatrix<f64>, b: &DVector<f64>) -> Option<DVector<f64>> {
let lu = a.clone().lu();
lu.solve(b)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_diis_creation() {
let diis = DiisAccelerator::new(6);
assert_eq!(diis.size(), 0);
}
#[test]
fn test_diis_no_extrapolation_with_one_iteration() {
let mut diis = DiisAccelerator::new(6);
let f = DMatrix::identity(2, 2);
let p = DMatrix::identity(2, 2);
let s = DMatrix::identity(2, 2);
diis.add_iteration(&f, &p, &s);
assert!(diis.extrapolate().is_none());
}
#[test]
fn test_diis_extrapolation_converged() {
let mut diis = DiisAccelerator::new(6);
// When commutator [F,P] = 0 (converged), DIIS should return
// something close to the last Fock matrix
let f = DMatrix::from_row_slice(2, 2, &[-1.0, 0.0, 0.0, -0.5]);
let s = DMatrix::identity(2, 2);
// P that commutes with F when S = I: P must be diagonal too
let p = DMatrix::from_row_slice(2, 2, &[2.0, 0.0, 0.0, 0.0]);
diis.add_iteration(&f, &p, &s);
diis.add_iteration(&f, &p, &s);
if let Some(f_diis) = diis.extrapolate() {
// Should be close to f
for i in 0..2 {
for j in 0..2 {
assert!(
(f_diis[(i, j)] - f[(i, j)]).abs() < 1e-8,
"DIIS result differs at ({},{})",
i,
j
);
}
}
}
}
#[test]
fn test_diis_reset() {
let mut diis = DiisAccelerator::new(6);
let f = DMatrix::identity(2, 2);
let p = DMatrix::identity(2, 2);
let s = DMatrix::identity(2, 2);
diis.add_iteration(&f, &p, &s);
assert_eq!(diis.size(), 1);
diis.reset();
assert_eq!(diis.size(), 0);
}
}