use crate::error::{self, Result};
use crate::math::Complex;
#[derive(Debug, Clone)]
pub struct CMatrix {
data: Vec<Complex>,
n: usize,
}
impl CMatrix {
pub const MAX_DIM: usize = 64;
pub fn zeros(n: usize) -> Self {
Self {
data: vec![Complex::ZERO; n * n],
n,
}
}
pub fn eye(n: usize) -> Self {
let mut m = Self::zeros(n);
for i in 0..n {
m.set(i, i, Complex::ONE);
}
m
}
pub fn from_rows(rows: Vec<Vec<Complex>>) -> Result<Self> {
let n = rows.len();
if n == 0 {
return Err(error::invalid_param("rows", "matrix must be non-empty"));
}
if n > Self::MAX_DIM {
return Err(error::invalid_param(
"n",
"matrix dimension exceeds CMatrix::MAX_DIM (64)",
));
}
for row in rows.iter() {
if row.len() != n {
return Err(error::invalid_param(
"rows",
"all rows must have length equal to the number of rows (square matrix)",
));
}
}
let mut data = Vec::with_capacity(n * n);
for row in &rows {
data.extend_from_slice(row);
}
Ok(Self { data, n })
}
#[inline]
pub fn n(&self) -> usize {
self.n
}
#[inline]
pub fn get(&self, i: usize, j: usize) -> Complex {
self.data[i * self.n + j]
}
#[inline]
pub fn set(&mut self, i: usize, j: usize, v: Complex) {
self.data[i * self.n + j] = v;
}
#[inline]
fn add_to(&mut self, i: usize, j: usize, v: Complex) {
let cur = self.get(i, j);
self.set(i, j, cur.add(&v));
}
pub fn trace(&self) -> Complex {
let mut t = Complex::ZERO;
for i in 0..self.n {
t = t.add(&self.get(i, i));
}
t
}
pub fn conj_transpose(&self) -> Self {
let mut out = Self::zeros(self.n);
for i in 0..self.n {
for j in 0..self.n {
out.set(j, i, self.get(i, j).conj());
}
}
out
}
pub fn add(&self, other: &Self) -> Result<Self> {
if self.n != other.n {
return Err(error::invalid_param(
"other",
"matrix dimensions must match for addition",
));
}
let mut out = Self::zeros(self.n);
for k in 0..self.data.len() {
out.data[k] = self.data[k].add(&other.data[k]);
}
Ok(out)
}
pub fn sub(&self, other: &Self) -> Result<Self> {
if self.n != other.n {
return Err(error::invalid_param(
"other",
"matrix dimensions must match for subtraction",
));
}
let mut out = Self::zeros(self.n);
for k in 0..self.data.len() {
out.data[k] = self.data[k].sub(&other.data[k]);
}
Ok(out)
}
pub fn scale(&self, s: Complex) -> Self {
let mut out = self.clone();
for k in 0..out.data.len() {
out.data[k] = out.data[k].mul(&s);
}
out
}
pub fn scale_real(&self, s: f64) -> Self {
self.scale(Complex::from_real(s))
}
pub fn matmul(&self, other: &Self) -> Result<Self> {
if self.n != other.n {
return Err(error::invalid_param(
"other",
"matrix dimensions must match for multiplication",
));
}
let n = self.n;
let mut out = Self::zeros(n);
for i in 0..n {
for k in 0..n {
let aik = self.get(i, k);
if aik.re == 0.0 && aik.im == 0.0 {
continue;
}
for j in 0..n {
let v = aik.mul(&other.get(k, j));
out.add_to(i, j, v);
}
}
}
Ok(out)
}
pub fn frobenius_norm(&self) -> f64 {
self.data.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt()
}
pub fn inverse(&self) -> Result<Self> {
let n = self.n;
let mut aug: Vec<Vec<Complex>> = (0..n)
.map(|i| {
let mut row: Vec<Complex> = (0..n).map(|j| self.get(i, j)).collect();
for j in 0..n {
row.push(if i == j { Complex::ONE } else { Complex::ZERO });
}
row
})
.collect();
let max_elem = aug
.iter()
.flat_map(|row| row.iter().take(n))
.map(|c| c.norm())
.fold(0.0_f64, f64::max);
let pivot_thresh = 1e-14 * max_elem.max(1.0);
for col in 0..n {
let mut max_row = col;
let mut max_val = aug[col][col].norm();
for (row, aug_row) in aug.iter().enumerate().skip(col + 1) {
let v = aug_row[col].norm();
if v > max_val {
max_val = v;
max_row = row;
}
}
if max_val < pivot_thresh {
return Err(error::numerical_error(
"matrix is singular or nearly singular (Gauss-Jordan inverse)",
));
}
aug.swap(col, max_row);
let pivot = aug[col][col];
let pivot_inv = Complex::ONE.div(&pivot);
for v in &mut aug[col] {
*v = v.mul(&pivot_inv);
}
for row in 0..n {
if row == col {
continue;
}
let factor = aug[row][col];
if factor.re == 0.0 && factor.im == 0.0 {
continue;
}
let col_vals: Vec<Complex> = aug[col].clone();
for (dst, src) in aug[row].iter_mut().zip(col_vals.iter()) {
*dst = dst.sub(&factor.mul(src));
}
}
}
let mut result = Self::zeros(n);
for (i, aug_row) in aug.iter().enumerate() {
for (j, val) in aug_row[n..].iter().enumerate() {
result.set(i, j, *val);
}
}
Ok(result)
}
pub fn hermitian_eigendecomposition(&self) -> Result<(Vec<f64>, Self)> {
hermitian_eig_impl(self)
}
pub fn from_diagonal(diag: &[f64]) -> Self {
let n = diag.len();
let mut m = Self::zeros(n);
for (i, &v) in diag.iter().enumerate() {
m.set(i, i, Complex::from_real(v));
}
m
}
pub fn column(&self, j: usize) -> Vec<Complex> {
(0..self.n).map(|i| self.get(i, j)).collect()
}
pub fn row(&self, i: usize) -> Vec<Complex> {
(0..self.n).map(|j| self.get(i, j)).collect()
}
}
fn hermitian_householder_tridiag(h: &CMatrix, n: usize) -> (Vec<f64>, Vec<f64>, CMatrix) {
let mut a: Vec<Vec<Complex>> = (0..n)
.map(|i| (0..n).map(|j| h.get(i, j)).collect())
.collect();
let mut q: Vec<Vec<Complex>> = (0..n)
.map(|i| {
(0..n)
.map(|j| if i == j { Complex::ONE } else { Complex::ZERO })
.collect()
})
.collect();
let mut d = vec![0.0_f64; n];
let mut e = vec![0.0_f64; n];
for k in 0..n.saturating_sub(2) {
let m = n - k - 1;
let x_orig: Vec<Complex> = (0..m).map(|i| a[k + 1 + i][k]).collect();
let sigma = x_orig.iter().map(|c| c.norm_sq()).sum::<f64>().sqrt();
d[k] = a[k][k].re;
if sigma < 1e-15 {
e[k + 1] = 0.0;
continue;
}
e[k + 1] = sigma;
let x0 = x_orig[0];
let phase = if x0.norm_sq() < 1e-300 {
Complex::ONE
} else {
Complex::from_polar(1.0, x0.phase())
};
let mut v: Vec<Complex> = x_orig.clone();
v[0] = x0.add(&phase.scale(sigma));
let v_norm_sq = v.iter().map(|c| c.norm_sq()).sum::<f64>();
let beta = if v_norm_sq < 1e-28 {
0.0
} else {
2.0 / v_norm_sq
};
let mut w = vec![Complex::ZERO; m];
for i in 0..m {
for j in 0..m {
w[i] = w[i].add(&a[k + 1 + i][k + 1 + j].mul(&v[j]));
}
}
let vt_w: Complex = v
.iter()
.zip(w.iter())
.map(|(vi, wi)| vi.conj().mul(wi))
.fold(Complex::ZERO, |acc, c| acc.add(&c));
let p_vec: Vec<Complex> = w
.iter()
.zip(v.iter())
.map(|(wi, vi)| wi.scale(beta).sub(&vt_w.scale(beta * beta * 0.5).mul(vi)))
.collect();
for i in 0..m {
for j in 0..m {
let delta = v[i].mul(&p_vec[j].conj()).add(&p_vec[i].mul(&v[j].conj()));
a[k + 1 + i][k + 1 + j] = a[k + 1 + i][k + 1 + j].sub(&delta);
}
}
a[k + 1][k] = Complex::from_real(-sigma);
a[k][k + 1] = Complex::from_real(-sigma);
for i in 2..m + 1 {
a[k + i][k] = Complex::ZERO;
a[k][k + i] = Complex::ZERO;
}
for q_row in q.iter_mut() {
let dot_r: Complex = (0..m)
.map(|j| q_row[k + 1 + j].mul(&v[j].conj()))
.fold(Complex::ZERO, |acc, c| acc.add(&c))
.scale(beta);
for j in 0..m {
q_row[k + 1 + j] = q_row[k + 1 + j].sub(&dot_r.mul(&v[j]));
}
}
}
if n >= 2 {
d[n - 2] = a[n - 2][n - 2].re;
let sub = a[n - 1][n - 2]; let sub_norm = sub.norm();
e[n - 1] = sub_norm;
if sub_norm > 1e-15 {
let phase_conj = Complex::from_polar(1.0, -sub.phase());
for q_row in q.iter_mut() {
q_row[n - 1] = q_row[n - 1].mul(&phase_conj);
}
}
}
d[n - 1] = a[n - 1][n - 1].re;
let mut q_mat = CMatrix::zeros(n);
for (i, q_row) in q.iter().enumerate() {
for (j, &val) in q_row.iter().enumerate() {
q_mat.set(i, j, val);
}
}
(d, e, q_mat)
}
fn tridiag_ql_in_place(
d: &mut [f64],
e: &mut [f64],
z: &mut CMatrix,
n: usize,
) -> crate::error::Result<()> {
if n <= 1 {
return Ok(());
}
for l in 0..n {
let mut num_iter = 0_usize;
loop {
let mut m = l;
while m < n - 1 {
let dd = d[m].abs() + d[m + 1].abs();
if (e[m].abs() + dd) == dd {
break; }
m += 1;
}
if m == l {
break;
}
if num_iter >= 60 {
return Err(crate::error::numerical_error(
"tridiagonal QL iteration did not converge within 60 iterations",
));
}
num_iter += 1;
let gg = (d[l + 1] - d[l]) / (2.0 * e[l]);
let r = gg.hypot(1.0);
let g_var_init = d[m] - d[l] + e[l] / (gg + if gg >= 0.0 { r } else { -r });
let mut g_var = g_var_init;
let mut s = 1.0_f64;
let mut c = 1.0_f64;
let mut p = 0.0_f64;
let mut i = m;
while i > l {
i -= 1;
let f = s * e[i];
let b = c * e[i];
let r_hyp = f.hypot(g_var);
e[i + 1] = r_hyp;
if r_hyp.abs() < 1e-300 {
d[i + 1] -= p;
e[m] = 0.0;
break;
}
s = f / r_hyp;
c = g_var / r_hyp;
g_var = d[i + 1] - p;
let r_var = (d[i] - g_var) * s + 2.0 * c * b;
p = s * r_var;
d[i + 1] = g_var + p;
g_var = c * r_var - b;
for row in 0..n {
let zi = z.get(row, i);
let zi1 = z.get(row, i + 1);
z.set(row, i + 1, zi.scale(s).add(&zi1.scale(c)));
z.set(row, i, zi.scale(c).sub(&zi1.scale(s)));
}
}
d[l] -= p;
e[l] = g_var;
e[m] = 0.0;
}
}
Ok(())
}
fn hermitian_eig_impl(h: &CMatrix) -> Result<(Vec<f64>, CMatrix)> {
let n = h.n;
if n == 0 {
return Ok((vec![], CMatrix::zeros(0)));
}
if n == 1 {
let e = h.get(0, 0).re;
let mut v = CMatrix::zeros(1);
v.set(0, 0, Complex::ONE);
return Ok((vec![e], v));
}
let (mut d, e_h, mut q) = hermitian_householder_tridiag(h, n);
let mut e: Vec<f64> = (0..n)
.map(|i| if i + 1 < n { e_h[i + 1] } else { 0.0 })
.collect();
tridiag_ql_in_place(&mut d, &mut e, &mut q, n)?;
let mut pairs: Vec<(f64, usize)> = d.iter().copied().enumerate().map(|(i, v)| (v, i)).collect();
pairs.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
let eigenvalues: Vec<f64> = pairs.iter().map(|(v, _)| *v).collect();
let mut eigenvectors = CMatrix::zeros(n);
for (col_out, (_, col_in)) in pairs.iter().enumerate() {
for row in 0..n {
eigenvectors.set(row, col_out, q.get(row, *col_in));
}
}
Ok((eigenvalues, eigenvectors))
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: f64, b: f64, tol: f64) -> bool {
(a - b).abs() < tol
}
fn cx(re: f64, im: f64) -> Complex {
Complex::new(re, im)
}
#[test]
fn test_eye_trace() {
let m = CMatrix::eye(4);
assert!((m.trace().re - 4.0).abs() < 1e-14);
assert!((m.trace().im).abs() < 1e-14);
}
#[test]
fn test_matmul_identity() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 0.0), cx(2.0, 1.0)],
vec![cx(3.0, -1.0), cx(4.0, 0.0)],
])
.unwrap();
let i = CMatrix::eye(2);
let r = a.matmul(&i).unwrap();
for row in 0..2 {
for col in 0..2 {
let diff = r.get(row, col).sub(&a.get(row, col));
assert!(diff.norm() < 1e-13);
}
}
}
#[test]
fn test_matmul_2x2() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 0.0), cx(0.0, 1.0)],
vec![cx(0.0, -1.0), cx(1.0, 0.0)],
])
.unwrap();
let r = a.matmul(&a).unwrap();
assert!(r.trace().re.is_finite());
}
#[test]
fn test_conj_transpose() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 2.0), cx(3.0, 4.0)],
vec![cx(5.0, 6.0), cx(7.0, 8.0)],
])
.unwrap();
let ah = a.conj_transpose();
assert!((ah.get(0, 1).re - 5.0).abs() < 1e-14);
assert!((ah.get(0, 1).im + 6.0).abs() < 1e-14);
}
#[test]
fn test_inverse_2x2() {
let a = CMatrix::from_rows(vec![
vec![cx(2.0, 0.0), cx(1.0, 0.0)],
vec![cx(1.0, 0.0), cx(1.0, 0.0)],
])
.unwrap();
let inv = a.inverse().unwrap();
let prod = a.matmul(&inv).unwrap();
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(approx_eq(prod.get(i, j).re, expected, 1e-12));
assert!(approx_eq(prod.get(i, j).im, 0.0, 1e-12));
}
}
}
#[test]
fn test_inverse_complex() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 1.0), cx(0.0, 1.0)],
vec![cx(0.0, -1.0), cx(1.0, -1.0)],
])
.unwrap();
let inv = a.inverse().unwrap();
let prod = a.matmul(&inv).unwrap();
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert!(approx_eq(prod.get(i, j).re, expected, 1e-12));
}
}
}
#[test]
fn test_inverse_singular_errors() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 0.0), cx(2.0, 0.0)],
vec![cx(2.0, 0.0), cx(4.0, 0.0)],
])
.unwrap();
assert!(a.inverse().is_err());
}
#[test]
fn test_hermitian_eigendecomposition_identity() {
let h = CMatrix::eye(3);
let (vals, _vecs) = h.hermitian_eigendecomposition().unwrap();
for v in &vals {
assert!(approx_eq(*v, 1.0, 1e-10));
}
}
#[test]
fn test_hermitian_eigendecomposition_2x2_real() {
let h = CMatrix::from_rows(vec![
vec![cx(2.0, 0.0), cx(1.0, 0.0)],
vec![cx(1.0, 0.0), cx(2.0, 0.0)],
])
.unwrap();
let (vals, vecs) = h.hermitian_eigendecomposition().unwrap();
assert!(approx_eq(vals[0], 1.0, 1e-10));
assert!(approx_eq(vals[1], 3.0, 1e-10));
let hv = h.matmul(&vecs).unwrap();
for i in 0..2 {
let hv_i = hv.get(i, 0);
let lv_i = vecs.get(i, 0).scale(vals[0]);
assert!(approx_eq(hv_i.re, lv_i.re, 1e-9));
assert!(approx_eq(hv_i.im, lv_i.im, 1e-9));
}
}
#[test]
fn test_hermitian_eigendecomposition_complex_offdiag() {
let h = CMatrix::from_rows(vec![
vec![cx(1.0, 0.0), cx(0.0, 1.0)],
vec![cx(0.0, -1.0), cx(1.0, 0.0)],
])
.unwrap();
let (vals, _) = h.hermitian_eigendecomposition().unwrap();
assert!(approx_eq(vals[0], 0.0, 1e-9));
assert!(approx_eq(vals[1], 2.0, 1e-9));
}
#[test]
fn test_eigendecomposition_3x3_diagonal() {
let h = CMatrix::from_diagonal(&[3.0, 1.0, 2.0]);
let (vals, _) = h.hermitian_eigendecomposition().unwrap();
assert!(approx_eq(vals[0], 1.0, 1e-10));
assert!(approx_eq(vals[1], 2.0, 1e-10));
assert!(approx_eq(vals[2], 3.0, 1e-10));
}
#[test]
fn test_eigendecomposition_eigenvectors_orthonormal() {
let h = CMatrix::from_rows(vec![
vec![cx(2.0, 0.0), cx(1.0, 0.0)],
vec![cx(1.0, 0.0), cx(2.0, 0.0)],
])
.unwrap();
let (_, vecs) = h.hermitian_eigendecomposition().unwrap();
let v0: Vec<Complex> = (0..2).map(|i| vecs.get(i, 0)).collect();
let v1: Vec<Complex> = (0..2).map(|i| vecs.get(i, 1)).collect();
let dot: Complex = v0
.iter()
.zip(v1.iter())
.map(|(a, b)| a.conj().mul(b))
.fold(Complex::ZERO, |acc, x| acc.add(&x));
assert!(dot.norm() < 1e-10);
let norm0_sq: f64 = v0.iter().map(|c| c.norm_sq()).sum();
assert!(approx_eq(norm0_sq, 1.0, 1e-10));
}
#[test]
fn test_from_rows_inconsistent_size() {
let result = CMatrix::from_rows(vec![vec![cx(1.0, 0.0), cx(0.0, 0.0)], vec![cx(0.0, 0.0)]]);
assert!(result.is_err());
}
#[test]
fn test_add_and_sub() {
let a = CMatrix::eye(2);
let b = CMatrix::eye(2);
let s = a.add(&b).unwrap();
assert!((s.get(0, 0).re - 2.0).abs() < 1e-14);
let d = a.sub(&b).unwrap();
assert!((d.get(0, 0).re).abs() < 1e-14);
}
#[test]
fn test_scale() {
let m = CMatrix::eye(3);
let s = m.scale(Complex::new(2.0, 0.0));
assert!((s.get(0, 0).re - 2.0).abs() < 1e-14);
assert!((s.get(0, 1).re).abs() < 1e-14);
}
#[test]
fn test_column_and_row() {
let a = CMatrix::from_rows(vec![
vec![cx(1.0, 0.0), cx(2.0, 0.0)],
vec![cx(3.0, 0.0), cx(4.0, 0.0)],
])
.unwrap();
let col = a.column(1);
assert!((col[0].re - 2.0).abs() < 1e-14);
assert!((col[1].re - 4.0).abs() < 1e-14);
let row = a.row(0);
assert!((row[0].re - 1.0).abs() < 1e-14);
assert!((row[1].re - 2.0).abs() < 1e-14);
}
}