use crate::error::QuantRS2Result;
use scirs2_core::ndarray::Array2;
fn householder_tridiagonalize(a: &Array2<f64>) -> QuantRS2Result<(Vec<f64>, Vec<f64>)> {
let n = a.nrows();
let mut mat = a.to_owned();
for k in 0..n.saturating_sub(2) {
let col: Vec<f64> = (k + 1..n).map(|i| mat[[i, k]]).collect();
let sigma: f64 = col.iter().map(|x| x * x).sum::<f64>().sqrt();
if sigma < 1e-14 {
continue;
}
let mut v = col.clone();
v[0] += if col[0] >= 0.0 { sigma } else { -sigma };
let v_norm_sq: f64 = v.iter().map(|x| x * x).sum();
if v_norm_sq < 1e-28 {
continue;
}
let m = v.len();
for j in k..n {
let dot: f64 = (0..m).map(|i| v[i] * mat[[k + 1 + i, j]]).sum::<f64>();
let scale = 2.0 * dot / v_norm_sq;
for i in 0..m {
mat[[k + 1 + i, j]] -= scale * v[i];
}
}
for i in 0..n {
let dot: f64 = (0..m).map(|j| mat[[i, k + 1 + j]] * v[j]).sum::<f64>();
let scale = 2.0 * dot / v_norm_sq;
for j in 0..m {
mat[[i, k + 1 + j]] -= scale * v[j];
}
}
}
let mut diag = vec![0.0f64; n];
let mut off = vec![0.0f64; n.saturating_sub(1)];
for i in 0..n {
diag[i] = mat[[i, i]];
}
for i in 0..n.saturating_sub(1) {
off[i] = (mat[[i, i + 1]] + mat[[i + 1, i]]) * 0.5;
}
Ok((diag, off))
}
fn sturm_count(diag: &[f64], off_sq: &[f64], lambda: f64) -> usize {
let n = diag.len();
if n == 0 {
return 0;
}
let mut count = 0usize;
let mut p_prev = 1.0f64; let mut p_curr = diag[0] - lambda;
if p_curr < 0.0 {
count += 1;
}
for i in 1..n {
let p_next = if p_curr.abs() < f64::MIN_POSITIVE {
-(off_sq[i - 1] / f64::EPSILON)
} else {
(diag[i] - lambda) * p_curr - off_sq[i - 1] * p_prev
};
if (p_next < 0.0) != (p_curr < 0.0) && p_curr != 0.0 {
count += 1;
}
p_prev = p_curr;
p_curr = p_next;
}
count
}
fn qr_iteration_tridiagonal(diag: &[f64], off: &[f64]) -> QuantRS2Result<Vec<f64>> {
let n = diag.len();
if n == 0 {
return Ok(vec![]);
}
if n == 1 {
return Ok(vec![diag[0]]);
}
let off_sq: Vec<f64> = off.iter().map(|e| e * e).collect();
let lo_bound = diag
.iter()
.enumerate()
.map(|(i, &d)| {
let r = (if i > 0 { off_sq[i - 1].sqrt() } else { 0.0 })
+ (if i < n - 1 { off_sq[i].sqrt() } else { 0.0 });
d - r
})
.fold(f64::INFINITY, f64::min);
let hi_bound = diag
.iter()
.enumerate()
.map(|(i, &d)| {
let r = (if i > 0 { off_sq[i - 1].sqrt() } else { 0.0 })
+ (if i < n - 1 { off_sq[i].sqrt() } else { 0.0 });
d + r
})
.fold(f64::NEG_INFINITY, f64::max);
let mut eigenvalues = Vec::with_capacity(n);
let tol = 1e-11 * (hi_bound - lo_bound + 1.0);
for k in 0..n {
let mut a = lo_bound - 1.0;
let mut b = hi_bound + 1.0;
let count_a_target = k;
for _ in 0..200 {
let mid = (a + b) * 0.5;
let cnt = sturm_count(diag, &off_sq, mid);
if cnt <= count_a_target {
a = mid;
} else {
b = mid;
}
if b - a < tol {
break;
}
}
eigenvalues.push((a + b) * 0.5);
}
Ok(eigenvalues)
}
pub(crate) fn compute_laplacian_eigenvalues_impl(
laplacian: &Array2<f64>,
) -> QuantRS2Result<Vec<f64>> {
let n = laplacian.nrows();
if n == 0 {
return Ok(vec![]);
}
if n == 1 {
return Ok(vec![laplacian[[0, 0]]]);
}
let (tri_diag, tri_off) = householder_tridiagonalize(laplacian)?;
let mut eigenvalues = qr_iteration_tridiagonal(&tri_diag, &tri_off)?;
for ev in &mut eigenvalues {
if *ev < 0.0 {
*ev = 0.0;
}
}
eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(eigenvalues)
}
pub(crate) fn estimate_fiedler_value_impl(laplacian: &Array2<f64>) -> f64 {
let n = laplacian.nrows();
if n <= 1 {
return 0.0;
}
let inv_sqrt_n = 1.0 / (n as f64).sqrt();
let mut v: Vec<f64> = (0..n)
.map(|i| if i % 2 == 0 { inv_sqrt_n } else { -inv_sqrt_n })
.collect();
let mean: f64 = v.iter().sum::<f64>() / n as f64;
for x in &mut v {
*x -= mean;
}
let norm: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm < 1e-14 {
return 0.0;
}
for x in &mut v {
*x /= norm;
}
let mut rayleigh = 0.0_f64;
for _ in 0..200 {
let mut w = vec![0.0f64; n];
for i in 0..n {
let mut acc = 0.0f64;
for j in 0..n {
acc += laplacian[[i, j]] * v[j];
}
w[i] = acc;
}
let vw: f64 = v.iter().zip(w.iter()).map(|(a, b)| a * b).sum();
let vv: f64 = v.iter().map(|x| x * x).sum();
let new_rayleigh = if vv > 1e-28 { vw / vv } else { 0.0 };
if (new_rayleigh - rayleigh).abs() < 1e-12 * (1.0 + new_rayleigh.abs()) {
rayleigh = new_rayleigh;
break;
}
rayleigh = new_rayleigh;
let w_norm: f64 = w.iter().map(|x| x * x).sum::<f64>().sqrt();
if w_norm < 1e-14 {
break;
}
v = w.iter().map(|x| x / w_norm).collect();
let proj: f64 = v.iter().sum::<f64>() / n as f64;
for x in &mut v {
*x -= proj;
}
let norm2: f64 = v.iter().map(|x| x * x).sum::<f64>().sqrt();
if norm2 < 1e-14 {
break;
}
for x in &mut v {
*x /= norm2;
}
}
rayleigh.max(0.0)
}