use std::collections::HashMap;
use faer::col::Col;
use faer::linalg::solvers::Solve;
use faer::sparse::{SparseColMat, Triplet};
use faer::Side;
use nalgebra::{DMatrix, DVector};
use crate::network::Contact;
use crate::Error;
const SHIFT_FRACTION: f64 = 1e-6;
const ZERO_FRACTION: f64 = 1e-7;
pub(crate) fn lowest_nonzero_modes(
n_atoms: usize,
gamma: f64,
weights: &[f64],
contacts: &[Contact],
k: usize,
) -> Result<(Vec<f64>, DMatrix<f64>), Error> {
let dof = 3 * n_atoms;
let scale = crate::hessian::dof_scale(weights);
let entries = hessian_entries(gamma, &scale, contacts);
let max_diag = (0..dof)
.map(|d| entries.get(&(d, d)).copied().unwrap_or(0.0))
.fold(0.0_f64, f64::max);
let shift = SHIFT_FRACTION * max_diag.max(f64::MIN_POSITIVE);
let mut triplets: Vec<Triplet<usize, usize, f64>> = entries
.iter()
.filter(|(&(r, c), _)| r >= c)
.map(|(&(r, c), &v)| Triplet::new(r, c, if r == c { v + shift } else { v }))
.collect();
for d in 0..dof {
if !entries.contains_key(&(d, d)) {
triplets.push(Triplet::new(d, d, shift));
}
}
let a = SparseColMat::<usize, f64>::try_new_from_triplets(dof, dof, &triplets)
.map_err(|_| Error::SparseSolverFailed)?;
let llt = a
.sp_cholesky(Side::Lower)
.map_err(|_| Error::SparseSolverFailed)?;
let apply_inverse = |v: &DVector<f64>| -> DVector<f64> {
let rhs = Col::from_fn(dof, |i| v[i]);
let y = llt.solve(&rhs);
DVector::from_fn(dof, |i, _| y[i])
};
let want = k + 6;
let steps = (2 * want + 20).min(dof);
let (mu, ritz) = lanczos(dof, steps, apply_inverse);
let zero_tol = ZERO_FRACTION * max_diag.max(f64::MIN_POSITIVE);
let mut modes: Vec<(f64, usize)> = mu
.iter()
.enumerate()
.map(|(c, &m)| (1.0 / m - shift, c))
.filter(|&(lambda, _)| lambda > zero_tol)
.collect();
modes.sort_by(|x, y| x.0.total_cmp(&y.0));
modes.truncate(k);
let eigenvalues: Vec<f64> = modes.iter().map(|&(l, _)| l).collect();
let vectors = DMatrix::from_fn(dof, modes.len(), |r, c| ritz[(r, modes[c].1)]);
Ok((eigenvalues, vectors))
}
fn hessian_entries(
gamma: f64,
scale: &[f64],
contacts: &[Contact],
) -> HashMap<(usize, usize), f64> {
let mut acc: HashMap<(usize, usize), f64> = HashMap::new();
for c in contacts {
let s = -gamma / c.dist2;
for a in 0..3 {
for b in 0..3 {
let raw = s * c.delta[a] * c.delta[b];
let (ia, jb) = (3 * c.i + a, 3 * c.j + b);
let off = raw * scale[ia] * scale[jb];
*acc.entry((ia, jb)).or_default() += off;
*acc.entry((jb, ia)).or_default() += off;
let (ii_a, ii_b) = (3 * c.i + a, 3 * c.i + b);
let (jj_a, jj_b) = (3 * c.j + a, 3 * c.j + b);
*acc.entry((ii_a, ii_b)).or_default() -= raw * scale[ii_a] * scale[ii_b];
*acc.entry((jj_a, jj_b)).or_default() -= raw * scale[jj_a] * scale[jj_b];
}
}
}
acc
}
fn gershgorin_bound(acc: &HashMap<(usize, usize), f64>, dof: usize) -> f64 {
let mut row_abs = vec![0.0_f64; dof];
for (&(r, _), &v) in acc {
row_abs[r] += v.abs();
}
row_abs.iter().copied().fold(0.0_f64, f64::max)
}
pub(crate) type RtbModes = (Vec<f64>, DMatrix<f64>, DMatrix<f64>);
pub(crate) fn lowest_rtb_modes(
positions: &[[f64; 3]],
weights: &[f64],
blocks: &[usize],
gamma: f64,
contacts: &[Contact],
k: usize,
) -> Result<RtbModes, Error> {
let dof = 3 * positions.len();
let scale = crate::hessian::dof_scale(weights);
let acc = hessian_entries(gamma, &scale, contacts);
let bound = gershgorin_bound(&acc, dof);
let fail = |_| Error::SparseSolverFailed;
let k_triplets: Vec<Triplet<usize, usize, f64>> = acc
.iter()
.map(|(&(r, c), &v)| Triplet::new(r, c, v))
.collect();
let k_mat = SparseColMat::try_new_from_triplets(dof, dof, &k_triplets).map_err(fail)?;
let (entries, nb6) = crate::rtb::projection_entries(positions, weights, blocks)?;
let (p_triplets, pt_triplets): (Vec<_>, Vec<_>) = entries
.iter()
.map(|&(r, c, v)| (Triplet::new(r, c, v), Triplet::new(c, r, v)))
.unzip();
let p = SparseColMat::try_new_from_triplets(dof, nb6, &p_triplets).map_err(fail)?;
let pt = SparseColMat::try_new_from_triplets(nb6, dof, &pt_triplets).map_err(fail)?;
let op = |y: &DVector<f64>| -> DVector<f64> {
let py = &p * &Col::from_fn(nb6, |i| y[i]);
let r_y = &pt * &(&k_mat * &py);
DVector::from_fn(nb6, |i, _| bound * y[i] - r_y[i])
};
let steps = (4 * (k + 6) + 40).min(nb6);
let (mu, ritz) = lanczos(nb6, steps, op);
let zero_tol = ZERO_FRACTION * bound.max(f64::MIN_POSITIVE);
let mut modes: Vec<(f64, usize)> = mu
.iter()
.enumerate()
.map(|(c, &m)| (bound - m, c))
.filter(|&(lambda, _)| lambda > zero_tol)
.collect();
modes.sort_by(|x, y| x.0.total_cmp(&y.0));
modes.truncate(k);
let eigenvalues: Vec<f64> = modes.iter().map(|&(l, _)| l).collect();
let mut reduced = DMatrix::zeros(nb6, modes.len());
let mut vectors = DMatrix::zeros(dof, modes.len());
for (out, &(_, ritz_col)) in modes.iter().enumerate() {
let reduced_mode = Col::from_fn(nb6, |i| ritz[(i, ritz_col)]);
let lifted = &p * &reduced_mode;
for r in 0..nb6 {
reduced[(r, out)] = reduced_mode[r];
}
for r in 0..dof {
vectors[(r, out)] = lifted[r];
}
}
Ok((eigenvalues, vectors, reduced))
}
fn lanczos(
dof: usize,
steps: usize,
op: impl Fn(&DVector<f64>) -> DVector<f64>,
) -> (Vec<f64>, DMatrix<f64>) {
let mut v = DVector::from_fn(dof, |i, _| ((i + 1) as f64).sin());
v /= v.norm();
let mut basis: Vec<DVector<f64>> = Vec::with_capacity(steps);
let mut alpha = Vec::with_capacity(steps);
let mut beta = Vec::with_capacity(steps);
for _ in 0..steps {
basis.push(v.clone());
let mut w = op(&v);
let a = w.dot(&v);
alpha.push(a);
w -= &v * a;
if basis.len() >= 2 {
w -= &basis[basis.len() - 2] * *beta.last().unwrap();
}
for _ in 0..2 {
for q in &basis {
let proj = w.dot(q);
w -= q * proj;
}
}
let b = w.norm();
if b < 1e-12 {
break; }
beta.push(b);
v = w / b;
}
let m = basis.len();
let mut t = DMatrix::zeros(m, m);
for i in 0..m {
t[(i, i)] = alpha[i];
if i + 1 < m {
t[(i, i + 1)] = beta[i];
t[(i + 1, i)] = beta[i];
}
}
let eig = t.symmetric_eigen();
let basis_mat = DMatrix::from_fn(dof, m, |r, c| basis[c][r]);
let ritz = basis_mat * eig.eigenvectors;
(eig.eigenvalues.iter().copied().collect(), ritz)
}