use scirs2_core::ndarray::{Array1, Array2, ArrayView2};
use scirs2_core::numeric::Complex;
use scirs2_core::numeric::{Float, NumAssign};
use crate::error::{LinalgError, LinalgResult};
#[allow(dead_code)]
pub fn lu<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>, Array2<F>)>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute LU decomposition of an empty matrix".to_string(),
));
}
let n = a.nrows();
let m = a.ncols();
let _a_owned = a.to_owned();
let mut l = Array2::<F>::zeros((n, n));
let mut u = Array2::<F>::zeros((n, m));
let mut p = Array2::<F>::eye(n);
for i in 0..n {
for j in 0..m {
u[[i, j]] = a[[i, j]];
}
}
for k in 0..std::cmp::min(n, m) {
let mut pivot_row = k;
let mut max_val = u[[k, k]].abs();
for i in (k + 1)..n {
if u[[i, k]].abs() > max_val {
pivot_row = i;
max_val = u[[i, k]].abs();
}
}
if max_val < F::epsilon() {
continue; }
if pivot_row != k {
for j in 0..m {
let temp = u[[k, j]];
u[[k, j]] = u[[pivot_row, j]];
u[[pivot_row, j]] = temp;
}
for j in 0..n {
let temp = p[[k, j]];
p[[k, j]] = p[[pivot_row, j]];
p[[pivot_row, j]] = temp;
}
for j in 0..k {
let temp = l[[k, j]];
l[[k, j]] = l[[pivot_row, j]];
l[[pivot_row, j]] = temp;
}
}
for i in (k + 1)..n {
l[[i, k]] = u[[i, k]] / u[[k, k]];
}
for i in (k + 1)..n {
for j in k..m {
u[[i, j]] = u[[i, j]] - l[[i, k]] * u[[k, j]];
}
}
l[[k, k]] = F::one();
}
Ok((p, l, u))
}
#[allow(dead_code)]
pub fn qr<F>(a: &ArrayView2<F>) -> LinalgResult<(Array2<F>, Array2<F>)>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute QR decomposition of an empty matrix".to_string(),
));
}
let _a_owned = a.to_owned();
let n = a.nrows();
let m = a.ncols();
let mut q = Array2::<F>::zeros((n, m));
let mut r = Array2::<F>::zeros((m, m));
for j in 0..m {
let mut a_j = Array1::<F>::zeros(n);
for i in 0..n {
a_j[i] = a[[i, j]];
}
for k in 0..j {
let mut q_k = Array1::<F>::zeros(n);
for i in 0..n {
q_k[i] = q[[i, k]];
}
let mut r_kj = F::zero();
for i in 0..n {
r_kj += q_k[i] * a_j[i];
}
r[[k, j]] = r_kj;
for i in 0..n {
a_j[i] -= r_kj * q_k[i];
}
}
let mut r_jj = F::zero();
for i in 0..n {
r_jj += a_j[i] * a_j[i];
}
r_jj = r_jj.sqrt();
r[[j, j]] = r_jj;
if r_jj < F::epsilon() {
continue;
}
for i in 0..n {
q[[i, j]] = a_j[i] / r_jj;
}
}
Ok((q, r))
}
#[allow(dead_code)]
pub fn svd<F>(
a: &ArrayView2<F>,
full_matrices: bool,
) -> LinalgResult<(Array2<F>, Array1<F>, Array2<F>)>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute SVD of an empty matrix".to_string(),
));
}
let n = a.nrows();
let m = a.ncols();
let k = n.min(m);
let mut u: Array2<F>;
let mut s: Array1<F> = Array1::zeros(k);
let mut vt: Array2<F>;
if full_matrices {
u = Array2::<F>::zeros((n, n));
vt = Array2::<F>::zeros((m, m));
} else {
u = Array2::<F>::zeros((n, k));
vt = Array2::<F>::zeros((k, m));
}
let mut ata = Array2::<F>::zeros((m, m));
for i in 0..m {
for j in 0..m {
for l in 0..n {
ata[[i, j]] += a[[l, i]] * a[[l, j]];
}
}
}
for i in 0..k {
let mut v = Array1::<F>::zeros(m);
for j in 0..m {
v[j] = F::from(0.1 * (j as f64)).unwrap_or(F::one());
}
let mut norm = F::zero();
for j in 0..m {
norm += v[j] * v[j];
}
norm = norm.sqrt();
for j in 0..m {
v[j] /= norm;
}
for _ in 0..20 {
let mut new_v = Array1::<F>::zeros(m);
for j in 0..m {
for l in 0..m {
new_v[j] += ata[[j, l]] * v[l];
}
}
let mut norm = F::zero();
for j in 0..m {
norm += new_v[j] * new_v[j];
}
norm = norm.sqrt();
if norm < F::epsilon() {
break; }
for j in 0..m {
v[j] = new_v[j] / norm;
}
}
let mut u_i = Array1::<F>::zeros(n);
for j in 0..n {
for l in 0..m {
u_i[j] += a[[j, l]] * v[l];
}
}
let mut sigma = F::zero();
for j in 0..n {
sigma += u_i[j] * u_i[j];
}
sigma = sigma.sqrt();
s[i] = sigma;
if sigma > F::epsilon() {
for j in 0..n {
u[[j, i]] = u_i[j] / sigma;
}
for j in 0..m {
vt[[i, j]] = v[j];
}
}
}
Ok((u, s, vt))
}
type EigenResult<F> = (Array1<Complex<F>>, Array2<Complex<F>>);
#[allow(clippy::type_complexity)]
#[allow(dead_code)]
pub fn eig<F>(a: &ArrayView2<F>) -> LinalgResult<EigenResult<F>>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute eigendecomposition of an empty matrix".to_string(),
));
}
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for eigendecomposition, got shape {:?}",
a.shape()
)));
}
let n = a.nrows();
let mut eigenvalues: Array1<Complex<F>> = Array1::zeros(n);
let _real_eigenvectors: Array2<F> = Array2::zeros((n, n));
let mut complex_eigenvectors: Array2<Complex<F>> = Array2::zeros((n, n));
let _a_copy = a.to_owned();
if let Ok((real_eigenvalues, eigvecs)) = eigh(a) {
for i in 0..n {
eigenvalues[i] = Complex::new(real_eigenvalues[i], F::zero());
for j in 0..n {
complex_eigenvectors[[j, i]] = Complex::new(eigvecs[[j, i]], F::zero());
}
}
Ok((eigenvalues, complex_eigenvectors))
} else {
if n == 2 {
let trace = a[[0, 0]] + a[[1, 1]];
let det = a[[0, 0]] * a[[1, 1]] - a[[0, 1]] * a[[1, 0]];
let disc = (trace * trace - F::from(4.0).expect("Operation failed") * det).sqrt();
let lambda1 = (trace + disc) / F::from(2.0).expect("Operation failed");
let lambda2 = (trace - disc) / F::from(2.0).expect("Operation failed");
eigenvalues[0] = Complex::new(lambda1, F::zero());
eigenvalues[1] = Complex::new(lambda2, F::zero());
let mut v1 = Array1::<F>::zeros(n);
if a[[0, 1]].abs() > F::epsilon() {
v1[0] = a[[0, 1]];
v1[1] = lambda1 - a[[0, 0]];
} else if a[[1, 0]].abs() > F::epsilon() {
v1[0] = lambda1 - a[[1, 1]];
v1[1] = a[[1, 0]];
} else {
v1[0] = F::one();
v1[1] = F::zero();
}
let norm1 = (v1[0] * v1[0] + v1[1] * v1[1]).sqrt();
v1[0] /= norm1;
v1[1] /= norm1;
let mut v2 = Array1::<F>::zeros(n);
if a[[0, 1]].abs() > F::epsilon() {
v2[0] = a[[0, 1]];
v2[1] = lambda2 - a[[0, 0]];
} else if a[[1, 0]].abs() > F::epsilon() {
v2[0] = lambda2 - a[[1, 1]];
v2[1] = a[[1, 0]];
} else {
v2[0] = F::zero();
v2[1] = F::one();
}
let norm2 = (v2[0] * v2[0] + v2[1] * v2[1]).sqrt();
v2[0] /= norm2;
v2[1] /= norm2;
complex_eigenvectors[[0, 0]] = Complex::new(v1[0], F::zero());
complex_eigenvectors[[1, 0]] = Complex::new(v1[1], F::zero());
complex_eigenvectors[[0, 1]] = Complex::new(v2[0], F::zero());
complex_eigenvectors[[1, 1]] = Complex::new(v2[1], F::zero());
Ok((eigenvalues, complex_eigenvectors))
} else {
Err(LinalgError::NotImplementedError(
"General eigendecomposition for matrices larger than 2x2 requires more sophisticated algorithms".to_string(),
))
}
}
}
#[allow(dead_code)]
pub fn eigh<F>(a: &ArrayView2<F>) -> LinalgResult<(Array1<F>, Array2<F>)>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute eigendecomposition of an empty matrix".to_string(),
));
}
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for eigendecomposition, got shape {:?}",
a.shape()
)));
}
for i in 0..a.nrows() {
for j in (i + 1)..a.ncols() {
if Float::abs(a[[i, j]] - a[[j, i]])
> F::epsilon() * F::from(100.0).expect("Operation failed")
{
return Err(LinalgError::ValueError(
"Matrix must be symmetric for specialized eigendecomposition".to_string(),
));
}
}
}
let n = a.nrows();
let mut eigenvalues: Array1<F> = Array1::zeros(n);
let mut eigenvectors: Array2<F> = Array2::zeros((n, n));
let mut a_copy = a.to_owned();
for k in 0..n {
let mut v: Array1<F> = Array1::zeros(n);
for i in 0..n {
v[i] = F::from(0.1 * ((i + 1) as f64)).unwrap_or(F::one());
}
let mut norm = F::zero();
for i in 0..n {
norm += v[i] * v[i];
}
norm = norm.sqrt();
for i in 0..n {
v[i] /= norm;
}
let max_iter = 100;
let mut lambda: F = F::zero();
let mut prev_lambda: F = F::zero();
for _iter in 0..max_iter {
let mut av: Array1<F> = Array1::zeros(n);
for i in 0..n {
for j in 0..n {
av[i] += a_copy[[i, j]] * v[j];
}
}
let mut numerator = F::zero();
for i in 0..n {
numerator += v[i] * av[i];
}
let mut denominator = F::zero();
for i in 0..n {
denominator += v[i] * v[i];
}
lambda = numerator / denominator;
if (lambda - prev_lambda).abs()
< F::epsilon() * F::from(10.0).expect("Operation failed")
{
break;
}
prev_lambda = lambda;
norm = F::zero();
for i in 0..n {
norm += av[i] * av[i];
}
norm = norm.sqrt();
if norm < F::epsilon() {
break; }
for i in 0..n {
v[i] = av[i] / norm;
}
}
eigenvalues[k] = lambda;
for i in 0..n {
eigenvectors[[i, k]] = v[i];
}
for i in 0..n {
for j in 0..n {
a_copy[[i, j]] -= lambda * v[i] * v[j];
}
}
}
for j in 0..n {
let mut norm = F::zero();
for i in 0..n {
norm += eigenvectors[[i, j]] * eigenvectors[[i, j]];
}
norm = norm.sqrt();
if norm > F::epsilon() {
for i in 0..n {
eigenvectors[[i, j]] /= norm;
}
}
for k in (j + 1)..n {
let mut dot_product = F::zero();
for i in 0..n {
dot_product += eigenvectors[[i, j]] * eigenvectors[[i, k]];
}
for i in 0..n {
eigenvectors[[i, k]] = eigenvectors[[i, k]] - dot_product * eigenvectors[[i, j]];
}
}
}
Ok((eigenvalues, eigenvectors))
}
#[allow(dead_code)]
pub fn cholesky<F>(a: &ArrayView2<F>) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + 'static,
{
if a.is_empty() {
return Err(LinalgError::ValueError(
"Cannot compute Cholesky decomposition of an empty matrix".to_string(),
));
}
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix must be square for Cholesky decomposition, got shape {:?}",
a.shape()
)));
}
for i in 0..a.nrows() {
for j in (i + 1)..a.ncols() {
if Float::abs(a[[i, j]] - a[[j, i]])
> F::epsilon() * F::from(100.0).expect("Operation failed")
{
return Err(LinalgError::ValueError(
"Matrix must be symmetric for Cholesky decomposition".to_string(),
));
}
}
}
let n = a.nrows();
let mut l = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..=i {
let mut sum = F::zero();
if j == i {
for k in 0..j {
sum += l[[j, k]] * l[[j, k]];
}
let diag_val = a[[j, j]] - sum;
if diag_val <= F::zero() {
return Err(LinalgError::NonPositiveDefiniteError(
"Matrix is not positive definite for Cholesky decomposition".to_string(),
));
}
l[[j, j]] = diag_val.sqrt();
} else {
for k in 0..j {
sum += l[[i, k]] * l[[j, k]];
}
if l[[j, j]] == F::zero() {
return Err(LinalgError::ComputationError(
"Division by zero in Cholesky decomposition".to_string(),
));
}
l[[i, j]] = (a[[i, j]] - sum) / l[[j, j]];
}
}
}
Ok(l)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_lu() {
let a = array![[2.0, 1.0], [4.0, 3.0]];
let (p, l, u) = lu(&a.view()).expect("Operation failed");
assert_eq!(p.shape(), &[2, 2]);
assert_eq!(l.shape(), &[2, 2]);
assert_eq!(u.shape(), &[2, 2]);
let pa = p.dot(&a);
let lu = l.dot(&u);
assert_relative_eq!(pa[[0, 0]], lu[[0, 0]], epsilon = 1e-10);
assert_relative_eq!(pa[[0, 1]], lu[[0, 1]], epsilon = 1e-10);
assert_relative_eq!(pa[[1, 0]], lu[[1, 0]], epsilon = 1e-10);
assert_relative_eq!(pa[[1, 1]], lu[[1, 1]], epsilon = 1e-10);
}
#[test]
fn test_qr() {
let a = array![[2.0, 1.0], [4.0, 3.0], [8.0, 7.0]];
let (q, r) = qr(&a.view()).expect("Operation failed");
assert_eq!(q.shape(), &[3, 2]);
assert_eq!(r.shape(), &[2, 2]);
let qt = q.t();
let qtq = qt.dot(&q);
for i in 0..2 {
for j in 0..2 {
let expected = if i == j { 1.0 } else { 0.0 };
assert_relative_eq!(qtq[[i, j]], expected, epsilon = 1e-10);
}
}
let qr = q.dot(&r);
for i in 0..a.nrows() {
for j in 0..a.ncols() {
assert_relative_eq!(a[[i, j]], qr[[i, j]], epsilon = 1e-10);
}
}
}
#[test]
fn test_svd() {
let a = array![[1.0, 2.0], [3.0, 4.0], [5.0, 6.0]];
let (u, s, vt) = svd(&a.view(), false).expect("Operation failed");
assert_eq!(u.shape(), &[3, 2]);
assert_eq!(s.len(), 2);
assert_eq!(vt.shape(), &[2, 2]);
let mut s_diag = Array2::zeros((2, 2));
s_diag[[0, 0]] = s[0];
s_diag[[1, 1]] = s[1];
assert!(s[0] > 0.0, "First singular value should be positive");
assert!(s[1] > 0.0, "Second singular value should be positive");
assert_eq!(u.shape(), &[3, 2], "U matrix should have shape 3x2");
assert_eq!(vt.shape(), &[2, 2], "V^T matrix should have shape 2x2");
}
#[test]
fn test_eig() {
let a = array![[1.0, 2.0], [3.0, 4.0]];
let (eigenvalues, eigenvectors) = eig(&a.view()).expect("Operation failed");
assert_eq!(eigenvalues.len(), 2);
assert_eq!(eigenvectors.shape(), &[2, 2]);
let sorted_eigenvalues = if eigenvalues[0].re < eigenvalues[1].re {
eigenvalues
} else {
array![eigenvalues[1], eigenvalues[0]]
};
assert_relative_eq!(
sorted_eigenvalues[0].re,
-0.3722813232690143,
epsilon = 1e-10
);
assert_relative_eq!(sorted_eigenvalues[1].re, 5.372281323269014, epsilon = 1e-10);
}
#[test]
fn test_eigh() {
let a = array![[1.0, 2.0], [2.0, 4.0]];
let (eigenvalues, eigenvectors) = eigh(&a.view()).expect("Operation failed");
assert_eq!(eigenvalues.len(), 2);
assert_eq!(eigenvectors.shape(), &[2, 2]);
let sorted_eigenvalues = if eigenvalues[0] < eigenvalues[1] {
eigenvalues
} else {
array![eigenvalues[1], eigenvalues[0]]
};
assert_relative_eq!(sorted_eigenvalues[0], 0.0, epsilon = 1e-10);
assert_relative_eq!(sorted_eigenvalues[1], 5.0, epsilon = 1e-10);
let dot_product = eigenvectors[[0, 0]] * eigenvectors[[0, 1]]
+ eigenvectors[[1, 0]] * eigenvectors[[1, 1]];
assert_relative_eq!(Float::abs(dot_product), 0.0, epsilon = 1e-10);
}
#[test]
fn test_cholesky() {
let a = array![[4.0, 2.0], [2.0, 5.0]];
let l = cholesky(&a.view()).expect("Operation failed");
assert_eq!(l.shape(), &[2, 2]);
let lt = l.t();
let llt = l.dot(<);
assert_relative_eq!(llt[[0, 0]], a[[0, 0]], epsilon = 1e-10);
assert_relative_eq!(llt[[0, 1]], a[[0, 1]], epsilon = 1e-10);
assert_relative_eq!(llt[[1, 0]], a[[1, 0]], epsilon = 1e-10);
assert_relative_eq!(llt[[1, 1]], a[[1, 1]], epsilon = 1e-10);
}
}