use crate::types::AlignedMatrix;
pub const MAX_DIIS_CAPACITY: usize = 8;
pub const DEFAULT_MAX_DIIS: usize = 6;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DiisStepResult {
pub extrapolated: bool,
pub subspace_size: usize,
pub max_error: f64,
pub rms_error: f64,
}
#[derive(Debug, Clone)]
pub struct DiisWorkspace {
pub norbs: usize,
pub max_subspace: usize,
pub num_stored: usize,
pub active_slots: [usize; MAX_DIIS_CAPACITY],
pub fock_history: Vec<AlignedMatrix<f64>>,
pub error_history: Vec<AlignedMatrix<f64>>,
pub b_mat: [[f64; MAX_DIIS_CAPACITY]; MAX_DIIS_CAPACITY],
}
impl DiisWorkspace {
pub fn allocate(norbs: usize, max_subspace: usize) -> Self {
let cap = max_subspace.clamp(2, MAX_DIIS_CAPACITY);
let mut fock_history = Vec::with_capacity(cap);
let mut error_history = Vec::with_capacity(cap);
let mut active_slots = [0usize; MAX_DIIS_CAPACITY];
for (i, slot) in active_slots.iter_mut().enumerate().take(cap) {
fock_history.push(AlignedMatrix::zeroed(norbs, norbs));
error_history.push(AlignedMatrix::zeroed(norbs, norbs));
*slot = i;
}
Self {
norbs,
max_subspace: cap,
num_stored: 0,
active_slots,
fock_history,
error_history,
b_mat: [[0.0; MAX_DIIS_CAPACITY]; MAX_DIIS_CAPACITY],
}
}
pub fn reset(&mut self) {
self.num_stored = 0;
for i in 0..self.max_subspace {
self.active_slots[i] = i;
for j in 0..self.max_subspace {
self.b_mat[i][j] = 0.0;
}
}
}
pub fn drop_oldest(&mut self) {
if self.num_stored <= 1 {
self.reset();
return;
}
let oldest_slot = self.active_slots[0];
let n = self.num_stored;
for i in 0..(n - 1) {
self.active_slots[i] = self.active_slots[i + 1];
for j in 0..(n - 1) {
self.b_mat[i][j] = self.b_mat[i + 1][j + 1];
}
}
self.active_slots[n - 1] = oldest_slot;
self.num_stored -= 1;
}
pub fn push_and_extrapolate(
&mut self,
fock: &mut AlignedMatrix<f64>,
density: &AlignedMatrix<f64>,
tmp_mult: &mut AlignedMatrix<f64>,
) -> DiisStepResult {
let norbs = self.norbs;
for i in 0..norbs {
let f_row = fock.row(i);
let m_row = tmp_mult.row_mut(i);
m_row.fill(0.0);
for (k, &f_ik) in f_row.iter().enumerate().take(norbs) {
let p_row = density.row(k);
for j in 0..norbs {
m_row[j] += f_ik * p_row[j];
}
}
}
let (new_idx, target_slot) = if self.num_stored < self.max_subspace {
let idx = self.num_stored;
let slot = self.active_slots[idx];
self.num_stored += 1;
(idx, slot)
} else {
let oldest_slot = self.active_slots[0];
let max_s = self.max_subspace;
for i in 0..(max_s - 1) {
self.active_slots[i] = self.active_slots[i + 1];
for j in 0..(max_s - 1) {
self.b_mat[i][j] = self.b_mat[i + 1][j + 1];
}
}
self.active_slots[max_s - 1] = oldest_slot;
(max_s - 1, oldest_slot)
};
let mut max_err = 0.0f64;
let mut sum_sq_err = 0.0f64;
{
let err_mat = &mut self.error_history[target_slot];
for i in 0..norbs {
for j in 0..norbs {
let val = tmp_mult.get(i, j) - tmp_mult.get(j, i);
err_mat.set(i, j, val);
let abs_val = val.abs();
if abs_val > max_err {
max_err = abs_val;
}
sum_sq_err += val * val;
}
}
}
let rms_err = (sum_sq_err / (norbs * norbs).max(1) as f64).sqrt();
self.fock_history[target_slot]
.data
.copy_from_slice(&fock.data);
let m = self.num_stored;
for j in 0..m {
let other_slot = self.active_slots[j];
let other_err = &self.error_history[other_slot];
let new_err = &self.error_history[target_slot];
let mut dot = 0.0f64;
for idx in 0..(norbs * norbs) {
dot += new_err.data[idx] * other_err.data[idx];
}
self.b_mat[new_idx][j] = dot;
self.b_mat[j][new_idx] = dot;
}
if m < 2 {
return DiisStepResult {
extrapolated: false,
subspace_size: m,
max_error: max_err,
rms_error: rms_err,
};
}
let mut current_m = m;
let mut coeffs = [0.0f64; MAX_DIIS_CAPACITY];
let mut solved = false;
while current_m >= 2 {
if solve_pulay_system(&self.b_mat, current_m, &mut coeffs) {
solved = true;
break;
}
self.drop_oldest();
current_m = self.num_stored;
}
if !solved {
self.reset();
self.active_slots[0] = target_slot;
self.num_stored = 1;
return DiisStepResult {
extrapolated: false,
subspace_size: 1,
max_error: max_err,
rms_error: rms_err,
};
}
fock.fill_zero();
for (j, &slot_idx) in self.active_slots.iter().take(current_m).enumerate() {
let c = coeffs[j];
let past_fock = &self.fock_history[slot_idx];
for idx in 0..(norbs * norbs) {
fock.data[idx] += c * past_fock.data[idx];
}
}
DiisStepResult {
extrapolated: true,
subspace_size: current_m,
max_error: max_err,
rms_error: rms_err,
}
}
}
#[allow(clippy::needless_range_loop)]
pub fn solve_pulay_system(
b_mat: &[[f64; MAX_DIIS_CAPACITY]; MAX_DIIS_CAPACITY],
m: usize,
coeffs: &mut [f64; MAX_DIIS_CAPACITY],
) -> bool {
assert!((2..=MAX_DIIS_CAPACITY).contains(&m));
let k_dim = m + 1;
const MAX_K: usize = MAX_DIIS_CAPACITY + 1;
let mut b_max = 0.0f64;
for (i, row) in b_mat.iter().enumerate().take(m) {
let d = row[i];
if d > b_max {
b_max = d;
}
}
if b_max < 1e-16 {
return false;
}
let scale = 1.0 / b_max;
let mut a = [[0.0f64; MAX_K]; MAX_K];
let mut b = [0.0f64; MAX_K];
for i in 0..m {
for j in 0..m {
a[i][j] = b_mat[i][j] * scale;
}
a[i][m] = -1.0;
a[m][i] = -1.0;
b[i] = 0.0;
}
a[m][m] = 0.0;
b[m] = -1.0;
for k in 0..k_dim {
let mut max_val = a[k][k].abs();
let mut pivot_row = k;
for (p, row) in a.iter().enumerate().take(k_dim).skip(k + 1) {
let val = row[k].abs();
if val > max_val {
max_val = val;
pivot_row = p;
}
}
if max_val < 1e-12 {
return false;
}
if pivot_row != k {
for col in 0..k_dim {
let tmp = a[k][col];
a[k][col] = a[pivot_row][col];
a[pivot_row][col] = tmp;
}
b.swap(k, pivot_row);
}
let pivot = a[k][k];
for row in (k + 1)..k_dim {
let factor = a[row][k] / pivot;
a[row][k] = 0.0;
for col in (k + 1)..k_dim {
let ak_col = a[k][col];
a[row][col] -= factor * ak_col;
}
b[row] -= factor * b[k];
}
}
let mut sol = [0.0f64; MAX_K];
for row in (0..k_dim).rev() {
let mut sum = b[row];
for col in (row + 1)..k_dim {
sum -= a[row][col] * sol[col];
}
sol[row] = sum / a[row][row];
}
let mut sum_c = 0.0f64;
for i in 0..m {
let c = sol[i];
if c.is_nan() || c.is_infinite() || c.abs() > 50.0 {
return false;
}
coeffs[i] = c;
sum_c += c;
}
if (sum_c - 1.0).abs() > 1e-3 {
return false;
}
true
}