use num_complex::Complex64;
use crate::backend::mps::svd_jacobi;
const ZERO: Complex64 = Complex64::new(0.0, 0.0);
const CLUSTER_TOLERANCE: f64 = 1e-9;
pub(crate) fn hermitian_eigen(matrix: &[Complex64], dim: usize) -> (Vec<f64>, Vec<Complex64>) {
let peak = matrix
.iter()
.fold(0.0f64, |peak, entry| peak.max(entry.norm()));
let shift = peak * dim as f64 + 1.0;
let mut shifted = matrix.to_vec();
for index in 0..dim {
shifted[index * dim + index] += shift;
}
let factored = svd_jacobi(&shifted, dim, dim);
let values: Vec<f64> = factored.s.iter().rev().map(|value| value - shift).collect();
let mut vectors = vec![ZERO; dim * dim];
for column in 0..dim {
let source = dim - 1 - column;
vectors[column * dim..column * dim + dim]
.copy_from_slice(&factored.u[source * dim..source * dim + dim]);
}
(values, vectors)
}
pub(crate) fn unitary_eigen(matrix: &[Complex64], dim: usize) -> (Vec<Complex64>, Vec<Complex64>) {
let mut hermitian = vec![ZERO; dim * dim];
let mut skew = vec![ZERO; dim * dim];
for column in 0..dim {
for row in 0..dim {
let entry = matrix[column * dim + row];
let adjoint = matrix[row * dim + column].conj();
hermitian[column * dim + row] = (entry + adjoint) * 0.5;
skew[column * dim + row] = (entry - adjoint) * Complex64::new(0.0, -0.5);
}
}
let (values, mut vectors) = hermitian_eigen(&hermitian, dim);
let mut start = 0;
while start < dim {
let mut end = start + 1;
while end < dim && values[end] - values[start] <= CLUSTER_TOLERANCE {
end += 1;
}
if end - start > 1 {
split_cluster(&skew, dim, &mut vectors, start, end);
}
start = end;
}
let eigenvalues = (0..dim)
.map(|column| {
let vector = &vectors[column * dim..column * dim + dim];
let applied = multiply_vector(matrix, dim, vector);
let value: Complex64 = (0..dim).map(|row| vector[row].conj() * applied[row]).sum();
value / value.norm()
})
.collect();
(eigenvalues, vectors)
}
pub(crate) fn unitary_power(matrix: &[Complex64], dim: usize, t: f64) -> Vec<Complex64> {
let (values, vectors) = unitary_eigen(matrix, dim);
let scaled: Vec<Complex64> = values
.iter()
.map(|value| Complex64::from_polar(1.0, t * crate::gates::principal_angle(value.arg())))
.collect();
let mut out = vec![ZERO; dim * dim];
for column in 0..dim {
for row in 0..dim {
out[column * dim + row] = (0..dim)
.map(|k| vectors[k * dim + row] * scaled[k] * vectors[k * dim + column].conj())
.sum();
}
}
out
}
fn split_cluster(
operator: &[Complex64],
dim: usize,
vectors: &mut [Complex64],
start: usize,
end: usize,
) {
let width = end - start;
let mut projected = vec![ZERO; width * width];
for column in 0..width {
let applied = multiply_vector(
operator,
dim,
&vectors[(start + column) * dim..(start + column) * dim + dim],
);
for (row, cell) in projected[column * width..column * width + width]
.iter_mut()
.enumerate()
{
let left = &vectors[(start + row) * dim..(start + row) * dim + dim];
*cell = (0..dim)
.map(|index| left[index].conj() * applied[index])
.sum();
}
}
let (_, rotation) = hermitian_eigen(&projected, width);
let block: Vec<Complex64> = vectors[start * dim..end * dim].to_vec();
for column in 0..width {
for row in 0..dim {
vectors[(start + column) * dim + row] = (0..width)
.map(|inner| block[inner * dim + row] * rotation[column * width + inner])
.sum();
}
}
}
fn multiply_vector(matrix: &[Complex64], dim: usize, vector: &[Complex64]) -> Vec<Complex64> {
(0..dim)
.map(|row| {
(0..dim)
.map(|column| matrix[column * dim + row] * vector[column])
.sum()
})
.collect()
}
#[cfg(test)]
#[path = "spectral_tests.rs"]
mod tests;