use crate::error::{QuantRS2Error, QuantRS2Result};
use scirs2_core::ndarray::{Array1, Array2};
use scirs2_core::Complex64 as Complex;
use std::f64::EPSILON;
#[derive(Debug, Clone)]
pub struct EigenDecomposition {
pub eigenvalues: Array1<Complex>,
pub eigenvectors: Array2<Complex>,
}
pub fn eigen_decompose_unitary(
matrix: &Array2<Complex>,
tolerance: f64,
) -> QuantRS2Result<EigenDecomposition> {
let n = matrix.nrows();
if n != matrix.ncols() {
return Err(QuantRS2Error::InvalidInput(
"Matrix must be square".to_string(),
));
}
match n {
1 => eigen_1x1(matrix),
2 => eigen_2x2(matrix, tolerance),
_ => eigen_general(matrix, tolerance),
}
}
fn eigen_1x1(matrix: &Array2<Complex>) -> QuantRS2Result<EigenDecomposition> {
let eigenvalues = Array1::from_vec(vec![matrix[(0, 0)]]);
let eigenvectors = Array2::from_shape_vec((1, 1), vec![Complex::new(1.0, 0.0)])
.map_err(|e| QuantRS2Error::ComputationError(e.to_string()))?;
Ok(EigenDecomposition {
eigenvalues,
eigenvectors,
})
}
fn eigen_2x2(matrix: &Array2<Complex>, tolerance: f64) -> QuantRS2Result<EigenDecomposition> {
let a = matrix[(0, 0)];
let b = matrix[(0, 1)];
let c = matrix[(1, 0)];
let d = matrix[(1, 1)];
let trace = a + d;
let det = a * d - b * c;
let discriminant = trace * trace - 4.0 * det;
let sqrt_disc = discriminant.sqrt();
let lambda1 = (trace + sqrt_disc) / 2.0;
let lambda2 = (trace - sqrt_disc) / 2.0;
let eigenvalues = Array1::from_vec(vec![lambda1, lambda2]);
let mut eigenvectors = Array2::zeros((2, 2));
for (i, &lambda) in eigenvalues.iter().enumerate() {
if b.norm() > tolerance {
let v1 = b;
let v2 = lambda - a;
let norm = (v1.norm_sqr() + v2.norm_sqr()).sqrt();
eigenvectors[(0, i)] = v1 / norm;
eigenvectors[(1, i)] = v2 / norm;
} else if c.norm() > tolerance {
let v1 = lambda - d;
let v2 = c;
let norm = (v1.norm_sqr() + v2.norm_sqr()).sqrt();
eigenvectors[(0, i)] = v1 / norm;
eigenvectors[(1, i)] = v2 / norm;
} else {
eigenvectors[(i, i)] = Complex::new(1.0, 0.0);
}
}
Ok(EigenDecomposition {
eigenvalues,
eigenvectors,
})
}
fn eigen_general(matrix: &Array2<Complex>, tolerance: f64) -> QuantRS2Result<EigenDecomposition> {
let n = matrix.nrows();
let max_iterations = 1000;
let mut h = matrix.clone();
hessenberg_reduction(&mut h)?;
let mut eigenvalues = Vec::with_capacity(n);
let mut eigenvectors = Array2::eye(n);
for _ in 0..max_iterations {
let mut converged = true;
for i in 1..n {
if h[(i, i - 1)].norm() > tolerance {
converged = false;
break;
}
}
if converged {
break;
}
let shift = wilkinson_shift(&h, n);
let mut h_shifted = h.clone();
for i in 0..n {
h_shifted[(i, i)] -= shift;
}
let (q, r) = qr_decompose(&h_shifted)?;
h = r.dot(&q);
for i in 0..n {
h[(i, i)] += shift;
}
eigenvectors = eigenvectors.dot(&q);
}
for i in 0..n {
eigenvalues.push(h[(i, i)]);
}
let refined_eigenvectors = refine_eigenvectors(matrix, &eigenvalues, tolerance)?;
Ok(EigenDecomposition {
eigenvalues: Array1::from_vec(eigenvalues),
eigenvectors: refined_eigenvectors,
})
}
fn hessenberg_reduction(matrix: &mut Array2<Complex>) -> QuantRS2Result<()> {
let n = matrix.nrows();
for k in 0..n - 2 {
let mut x = Array1::zeros(n - k - 1);
for i in k + 1..n {
x[i - k - 1] = matrix[(i, k)];
}
let alpha = x.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
if alpha < EPSILON {
continue;
}
let mut v = x.clone();
v[0] += alpha * Complex::new(x[0].re.signum(), x[0].im.signum());
let v_norm = v.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
if v_norm < EPSILON {
continue;
}
for i in 0..v.len() {
v[i] /= v_norm;
}
for j in k..n {
let mut sum = Complex::new(0.0, 0.0);
for i in k + 1..n {
sum += v[i - k - 1].conj() * matrix[(i, j)];
}
for i in k + 1..n {
matrix[(i, j)] -= 2.0 * v[i - k - 1] * sum;
}
}
for i in 0..n {
let mut sum = Complex::new(0.0, 0.0);
for j in k + 1..n {
sum += matrix[(i, j)] * v[j - k - 1];
}
for j in k + 1..n {
matrix[(i, j)] -= 2.0 * sum * v[j - k - 1].conj();
}
}
}
Ok(())
}
fn qr_decompose(matrix: &Array2<Complex>) -> QuantRS2Result<(Array2<Complex>, Array2<Complex>)> {
let n = matrix.nrows();
let mut r = matrix.clone();
let mut q = Array2::eye(n);
for j in 0..n - 1 {
for i in (j + 1)..n {
if r[(i, j)].norm() < EPSILON {
continue;
}
let (c, s) = givens_rotation(r[(j, j)], r[(i, j)]);
for k in j..n {
let rjk = r[(j, k)];
let rik = r[(i, k)];
r[(j, k)] = c * rjk + s * rik;
r[(i, k)] = -s.conj() * rjk + c * rik;
}
for k in 0..n {
let qkj = q[(k, j)];
let qki = q[(k, i)];
q[(k, j)] = c * qkj + s * qki;
q[(k, i)] = -s.conj() * qkj + c * qki;
}
}
}
Ok((q, r))
}
fn givens_rotation(a: Complex, b: Complex) -> (f64, Complex) {
let r = (a.norm_sqr() + b.norm_sqr()).sqrt();
if r < EPSILON {
(1.0, Complex::new(0.0, 0.0))
} else {
let c = a.norm() / r;
let s = (a.conj() * b) / (a.norm() * r);
(c, s)
}
}
fn wilkinson_shift(matrix: &Array2<Complex>, n: usize) -> Complex {
if n < 2 {
return Complex::new(0.0, 0.0);
}
let a = matrix[(n - 2, n - 2)];
let b = matrix[(n - 2, n - 1)];
let c = matrix[(n - 1, n - 2)];
let d = matrix[(n - 1, n - 1)];
let trace = a + d;
let det = a * d - b * c;
let discriminant = trace * trace - 4.0 * det;
let sqrt_disc = discriminant.sqrt();
let lambda1 = (trace + sqrt_disc) / 2.0;
let lambda2 = (trace - sqrt_disc) / 2.0;
if (lambda1 - d).norm() < (lambda2 - d).norm() {
lambda1
} else {
lambda2
}
}
fn refine_eigenvectors(
matrix: &Array2<Complex>,
eigenvalues: &[Complex],
tolerance: f64,
) -> QuantRS2Result<Array2<Complex>> {
let n = matrix.nrows();
let mut eigenvectors = Array2::zeros((n, n));
for (i, &lambda) in eigenvalues.iter().enumerate() {
let mut v = Array1::from_vec(vec![Complex::new(1.0, 0.0); n]);
v[i] = Complex::new(1.0, 0.0);
let norm = v.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
if norm > EPSILON {
for i in 0..v.len() {
v[i] /= norm;
}
}
let shifted = matrix - lambda * Array2::eye(n);
for _ in 0..10 {
let v_new = solve_system(&shifted, &v, tolerance)?;
let norm = v_new.iter().map(|c| c.norm_sqr()).sum::<f64>().sqrt();
if norm < EPSILON {
break;
}
let v_normalized = &v_new / norm;
let diff: f64 = (&v_normalized - &v)
.iter()
.map(|x| x.norm_sqr())
.sum::<f64>()
.sqrt();
if diff < tolerance {
v = v_normalized;
break;
}
v = v_normalized;
}
for j in 0..n {
eigenvectors[(j, i)] = v[j];
}
}
Ok(eigenvectors)
}
fn solve_system(
a: &Array2<Complex>,
b: &Array1<Complex>,
tolerance: f64,
) -> QuantRS2Result<Array1<Complex>> {
let n = a.nrows();
let mut regularized = a.clone();
for i in 0..n {
regularized[(i, i)] += Complex::new(tolerance, 0.0);
}
let (q, r) = qr_decompose(®ularized)?;
let qtb = q.t().dot(b);
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum = qtb[i];
for j in i + 1..n {
sum -= r[(i, j)] * x[j];
}
if r[(i, i)].norm() > tolerance {
x[i] = sum / r[(i, i)];
} else {
x[i] = Complex::new(0.0, 0.0);
}
}
Ok(x)
}
#[cfg(test)]
mod tests {
use super::*;
use std::f64::consts::PI;
#[test]
fn test_eigen_2x2_pauli_x() {
let matrix = Array2::from_shape_vec(
(2, 2),
vec![
Complex::new(0.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(1.0, 0.0),
Complex::new(0.0, 0.0),
],
)
.expect("Failed to create Pauli X matrix");
let result = eigen_decompose_unitary(&matrix, 1e-10).expect("Eigendecomposition failed");
let mut eigenvalues: Vec<_> = result.eigenvalues.iter().map(|&x| x.re).collect();
eigenvalues.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
assert!((eigenvalues[0] - (-1.0)).abs() < 1e-10);
assert!((eigenvalues[1] - 1.0).abs() < 1e-10);
}
#[test]
fn test_eigen_2x2_rotation() {
let theta = PI / 4.0;
let c = theta.cos();
let s = theta.sin();
let matrix = Array2::from_shape_vec(
(2, 2),
vec![
Complex::new(c, 0.0),
Complex::new(-s, 0.0),
Complex::new(s, 0.0),
Complex::new(c, 0.0),
],
)
.expect("Failed to create rotation matrix");
let result = eigen_decompose_unitary(&matrix, 1e-10).expect("Eigendecomposition failed");
for eigenvalue in result.eigenvalues.iter() {
assert!((eigenvalue.norm() - 1.0).abs() < 1e-10);
}
let phases: Vec<_> = result.eigenvalues.iter().map(|&x| x.arg()).collect();
assert!(phases.iter().any(|&p| (p - theta).abs() < 1e-10));
assert!(phases.iter().any(|&p| (p + theta).abs() < 1e-10));
}
#[test]
fn test_eigenvector_orthogonality() {
let sqrt2_inv = 1.0 / 2.0_f64.sqrt();
let matrix = Array2::from_shape_vec(
(2, 2),
vec![
Complex::new(sqrt2_inv, 0.0),
Complex::new(sqrt2_inv, 0.0),
Complex::new(sqrt2_inv, 0.0),
Complex::new(-sqrt2_inv, 0.0),
],
)
.expect("Failed to create Hadamard matrix");
let result = eigen_decompose_unitary(&matrix, 1e-10).expect("Eigendecomposition failed");
let v1 = result.eigenvectors.column(0);
let v2 = result.eigenvectors.column(1);
assert!((v1.dot(&v1).norm() - 1.0).abs() < 1e-10);
assert!((v2.dot(&v2).norm() - 1.0).abs() < 1e-10);
assert!(v1.dot(&v2).norm() < 1e-10);
}
}