use crate::scf::eigensolver::diagonalize_symmetric;
use crate::types::{AlignedMatrix, AlignedVec64};
#[derive(Debug, Clone)]
pub struct CampKingWorkspace {
pub norbs: usize,
pub u: AlignedMatrix<f64>,
pub h: AlignedMatrix<f64>,
pub h_evals: AlignedVec64<f64>,
pub h_evecs: AlignedMatrix<f64>,
pub theta: AlignedVec64<f64>,
pub c_corr: AlignedMatrix<f64>,
pub f_mo: AlignedMatrix<f64>,
pub tmp: AlignedMatrix<f64>,
}
impl CampKingWorkspace {
pub fn allocate(norbs: usize) -> Self {
Self {
norbs,
u: AlignedMatrix::zeroed(norbs, norbs),
h: AlignedMatrix::zeroed(norbs, norbs),
h_evals: AlignedVec64::zeroed(norbs),
h_evecs: AlignedMatrix::zeroed(norbs, norbs),
theta: AlignedVec64::zeroed(norbs),
c_corr: AlignedMatrix::zeroed(norbs, norbs),
f_mo: AlignedMatrix::zeroed(norbs, norbs),
tmp: AlignedMatrix::zeroed(norbs, norbs),
}
}
}
pub fn spline_minimize(x: &[f64], f: &[f64], df: &[f64], x_low: f64, x_high: f64) -> (f64, f64) {
let n = x.len();
assert!(n >= 2, "Spline minimization requires at least 2 points");
let mut x_min = x[0];
let mut f_min = f[0];
for i in 0..n {
if f[i] < f_min {
f_min = f[i];
x_min = x[i];
}
}
let close = 1.0e-8;
for k in 0..(n - 1) {
let dx = x[k + 1] - x[k];
if dx.abs() <= close {
continue;
}
let dum = (f[k + 1] - f[k]) / dx;
let a = (df[k] + df[k + 1] - 2.0 * dum) / (dx * dx);
let b = (3.0 * dum - 2.0 * df[k] - df[k + 1]) / dx;
let c = df[k];
let x1 = if k == 0 { x_low - x[0] } else { 0.0 };
let x2 = if k == n - 2 { x_high - x[k] } else { dx };
let bb = b * b;
let ac3 = 3.0 * a * c;
if bb >= ac3 {
let mut candidates = [x1, x2, 0.0, 0.0];
let mut num_cand = 2;
if a.abs() > 1e-12 {
let disc = (bb - ac3).sqrt();
let r1 = (-b + disc) / (3.0 * a);
let r2 = (-b - disc) / (3.0 * a);
if r1 >= x1 && r1 <= x2 {
candidates[num_cand] = r1;
num_cand += 1;
}
if r2 >= x1 && r2 <= x2 {
candidates[num_cand] = r2;
num_cand += 1;
}
} else if b.abs() > 1e-12 {
let r = -c / (2.0 * b);
if r >= x1 && r <= x2 {
candidates[num_cand] = r;
num_cand += 1;
}
}
for &cand in &candidates[..num_cand] {
let f_val = ((a * cand + b) * cand + c) * cand + f[k];
if f_val < f_min {
f_min = f_val;
x_min = cand + x[k];
}
}
}
}
(x_min, f_min)
}
#[derive(Debug, Clone)]
pub struct CampKingResult {
pub x_min: f64,
pub predicted_energy: f64,
pub max_rotation_angle: f64,
pub rotated: bool,
}
pub fn interpolate_camp_king(
c_prev: &AlignedMatrix<f64>,
c_curr: &mut AlignedMatrix<f64>,
fock: &AlignedMatrix<f64>,
e_prev: f64,
e_curr: f64,
nocc: usize,
ws: &mut CampKingWorkspace,
) -> CampKingResult {
let norbs = c_curr.rows;
assert_eq!(c_curr.cols, norbs);
assert_eq!(c_prev.rows, norbs);
assert_eq!(c_prev.cols, norbs);
assert_eq!(fock.rows, norbs);
assert_eq!(fock.cols, norbs);
assert!(nocc > 0 && nocc < norbs);
let nvirt = norbs - nocc;
let min_pq = nocc.min(nvirt);
for i in 0..norbs {
for j in 0..norbs {
let mut dot = 0.0;
for mu in 0..norbs {
dot += c_curr.get(mu, i) * c_prev.get(mu, j);
}
ws.u.set(i, j, dot);
}
}
let mut h_mat = AlignedMatrix::zeroed(nocc, nocc);
for i in 0..nocc {
for j in 0..nocc {
let mut sum = 0.0;
for a in nocc..norbs {
sum += ws.u.get(i, a) * ws.u.get(j, a);
}
h_mat.set(i, j, sum);
}
}
let mut h_evals = AlignedVec64::zeroed(nocc);
let mut h_evecs = AlignedMatrix::zeroed(nocc, nocc);
diagonalize_symmetric(&h_mat, &mut h_evals, &mut h_evecs);
for k in 0..min_pq {
let val = h_evals[nocc - 1 - k].clamp(0.0, 1.0);
ws.theta[k] = val.sqrt().asin();
}
let max_theta = ws.theta[0];
if max_theta < 1e-6 {
return CampKingResult {
x_min: 0.0,
predicted_energy: e_curr,
max_rotation_angle: max_theta,
rotated: false,
};
}
ws.c_corr.fill_zero();
for mu in 0..norbs {
for k in 0..min_pq {
let orig_idx = nocc - 1 - k;
let mut sum = 0.0;
for i in 0..nocc {
sum += c_curr.get(mu, i) * h_evecs.get(i, orig_idx);
}
ws.c_corr.set(mu, k, sum);
}
for k in min_pq..nocc {
ws.c_corr.set(mu, k, c_curr.get(mu, k));
}
}
for mu in 0..norbs {
for k in 0..min_pq {
let sk = ws.theta[k].sin();
if sk > 1e-8 {
let orig_idx = nocc - 1 - k;
let mut sum = 0.0;
for a in 0..nvirt {
let mut u_proj = 0.0;
for i in 0..nocc {
u_proj += ws.u.get(i, nocc + a) * h_evecs.get(i, orig_idx);
}
sum += c_curr.get(mu, nocc + a) * u_proj;
}
ws.c_corr.set(mu, nocc + k, sum / sk);
} else {
ws.c_corr.set(mu, nocc + k, c_curr.get(mu, nocc + k));
}
}
for k in min_pq..nvirt {
ws.c_corr.set(mu, nocc + k, c_curr.get(mu, nocc + k));
}
}
for mu in 0..norbs {
for j in 0..norbs {
let mut sum = 0.0;
for nu in 0..norbs {
sum += fock.get(mu, nu) * ws.c_corr.get(nu, j);
}
ws.tmp.set(mu, j, sum);
}
}
for i in 0..norbs {
for j in 0..norbs {
let mut sum = 0.0;
for mu in 0..norbs {
sum += ws.c_corr.get(mu, i) * ws.tmp.get(mu, j);
}
ws.f_mo.set(i, j, sum);
}
}
let mut dedx_curr = 0.0;
for k in 0..min_pq {
dedx_curr += ws.theta[k] * ws.f_mo.get(k, nocc + k);
}
let de_now = -4.0 * dedx_curr;
let pi = std::f64::consts::PI;
let x_high = (pi / (2.0 * max_theta)).min(2.0);
let x_low = -0.5 * x_high;
let de_old = (e_curr - e_prev) * 1.5;
let x_pts = [0.0, 1.0];
let f_pts = [e_curr, e_prev];
let df_pts = [de_now, de_old];
let (x_min, f_min) = spline_minimize(&x_pts, &f_pts, &df_pts, x_low, x_high);
for k in 0..min_pq {
let angle = x_min * ws.theta[k];
let ck = angle.cos();
let sk = angle.sin();
for mu in 0..norbs {
let phi_occ = ws.c_corr.get(mu, k);
let phi_virt = ws.c_corr.get(mu, nocc + k);
c_curr.set(mu, k, ck * phi_occ - sk * phi_virt);
c_curr.set(mu, nocc + k, sk * phi_occ + ck * phi_virt);
}
}
CampKingResult {
x_min,
predicted_energy: f_min,
max_rotation_angle: max_theta,
rotated: true,
}
}