use scirs2_core::ndarray::{Array1, Array2, ArrayView2, ScalarOperand};
use scirs2_core::numeric::Complex;
use scirs2_core::numeric::{Float, NumAssign};
use std::iter::Sum;
use crate::decomposition::{cholesky, qz};
use crate::error::{LinalgError, LinalgResult};
use crate::parallel;
use crate::solve::solve_triangular;
use super::standard::{eig, eigh, EigenResult};
#[allow(dead_code)]
pub fn eig_gen<F>(a: &ArrayView2<F>, b: &ArrayView2<F>, workers: Option<usize>) -> EigenResult<F>
where
F: Float + NumAssign + Sum + Send + Sync + scirs2_core::ndarray::ScalarOperand + 'static,
{
parallel::configure_workers(workers);
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix A must be square, got shape {:?}",
a.shape()
)));
}
if b.nrows() != b.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix B must be square, got shape {:?}",
b.shape()
)));
}
if a.nrows() != b.nrows() {
return Err(LinalgError::ShapeError(format!(
"Matrices A and B must have the same dimensions, got A: {:?}, B: {:?}",
a.shape(),
b.shape()
)));
}
let n = a.nrows();
let is_identity = check_identitymatrix(b);
if is_identity {
return eig(a, workers);
}
if n == 1 {
let a_val = a[[0, 0]];
let b_val = b[[0, 0]];
if b_val.abs() < F::epsilon() {
return Err(LinalgError::ComputationError(
"Matrix B is singular (zero diagonal element)".to_string(),
));
}
let eigenvalue = Complex::new(a_val / b_val, F::zero());
let eigenvector = Array2::eye(1).mapv(|x| Complex::new(x, F::zero()));
return Ok((Array1::from_elem(1, eigenvalue), eigenvector));
}
let (_q, aa, bb, z) = qz(a, b)?;
let mut eigenvalues = Array1::zeros(n);
for i in 0..n {
let alpha = aa[[i, i]];
let beta = bb[[i, i]];
if beta.abs() < F::epsilon() {
eigenvalues[i] = Complex::new(F::infinity(), F::zero());
} else {
eigenvalues[i] = Complex::new(alpha / beta, F::zero());
}
}
let eigenvectors = z.mapv(|x| Complex::new(x, F::zero()));
Ok((eigenvalues, eigenvectors))
}
#[allow(dead_code)]
pub fn eigh_gen<F>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
workers: Option<usize>,
) -> LinalgResult<(Array1<F>, Array2<F>)>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
parallel::configure_workers(workers);
if a.nrows() != a.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix A must be square, got shape {:?}",
a.shape()
)));
}
if b.nrows() != b.ncols() {
return Err(LinalgError::ShapeError(format!(
"Matrix B must be square, got shape {:?}",
b.shape()
)));
}
if a.nrows() != b.nrows() {
return Err(LinalgError::ShapeError(format!(
"Matrices A and B must have the same dimensions, got A: {:?}, B: {:?}",
a.shape(),
b.shape()
)));
}
let n = a.nrows();
checkmatrix_symmetry(a, "A")?;
checkmatrix_symmetry(b, "B")?;
let l = cholesky(b, workers)?;
let mut y = Array2::zeros((n, n));
for j in 0..n {
let a_col = a.column(j);
let y_col = solve_triangular(&l.view(), &a_col.to_owned().view(), true, false)?;
y.column_mut(j).assign(&y_col);
}
let mut transformed_a = Array2::zeros((n, n));
let l_t = l.t().to_owned();
for j in 0..n {
let y_col = y.column(j);
let z_col = solve_triangular(&l_t.view(), &y_col.to_owned().view(), false, false)?;
transformed_a.column_mut(j).assign(&z_col);
}
for i in 0..n {
for j in i + 1..n {
let avg = (transformed_a[[i, j]] + transformed_a[[j, i]])
/ F::from(2.0).expect("Operation failed");
transformed_a[[i, j]] = avg;
transformed_a[[j, i]] = avg;
}
}
let (eigenvalues, eigenvectors_y) = eigh(&transformed_a.view(), workers)?;
let mut eigenvectors = Array2::zeros((n, n));
for j in 0..n {
let y_vec = eigenvectors_y.column(j);
let x_vec = solve_triangular(&l_t.view(), &y_vec.to_owned().view(), false, false)?;
eigenvectors.column_mut(j).assign(&x_vec);
}
for j in 0..n {
let x = eigenvectors.column(j).to_owned();
let bx = b.dot(&x);
let norm = x.dot(&bx).sqrt();
if norm > F::epsilon() {
let normalized_x = x.mapv(|val| val / norm);
eigenvectors.column_mut(j).assign(&normalized_x);
}
}
Ok((eigenvalues, eigenvectors))
}
#[allow(dead_code)]
pub fn eigvals_gen<F>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
workers: Option<usize>,
) -> LinalgResult<Array1<Complex<F>>>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
let (eigenvalues, _) = eig_gen(a, b, workers)?;
Ok(eigenvalues)
}
#[allow(dead_code)]
pub fn eigvalsh_gen<F>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
workers: Option<usize>,
) -> LinalgResult<Array1<F>>
where
F: Float + NumAssign + Sum + Send + Sync + ScalarOperand + 'static,
{
let (eigenvalues, _) = eigh_gen(a, b, workers)?;
Ok(eigenvalues)
}
#[allow(dead_code)]
fn check_identitymatrix<F>(b: &ArrayView2<F>) -> bool
where
F: Float + NumAssign,
{
let n = b.nrows();
for i in 0..n {
for j in 0..n {
let expected = if i == j { F::one() } else { F::zero() };
if (b[[i, j]] - expected).abs()
> F::epsilon() * F::from(10.0).expect("Operation failed")
{
return false;
}
}
}
true
}
#[allow(dead_code)]
fn checkmatrix_symmetry<F>(matrix: &ArrayView2<F>, name: &str) -> LinalgResult<()>
where
F: Float + NumAssign,
{
let n = matrix.nrows();
for i in 0..n {
for j in 0..n {
if (matrix[[i, j]] - matrix[[j, i]]).abs() > F::epsilon() {
return Err(LinalgError::ShapeError(format!(
"Matrix {name} must be symmetric for eigh_gen"
)));
}
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_eig_gen_identity() {
let a = array![[2.0, 1.0], [1.0, 2.0]];
let b = Array2::eye(2);
let (w_gen, v_gen) = eig_gen(&a.view(), &b.view(), None).expect("Operation failed");
let (w_std, v_std) = eig(&a.view(), None).expect("Operation failed");
let mut w_gen_sorted: Vec<_> = w_gen.iter().map(|x| x.re).collect();
w_gen_sorted.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
let mut w_std_sorted: Vec<_> = w_std.iter().map(|x| x.re).collect();
w_std_sorted.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
for (gen_val, std_val) in w_gen_sorted.iter().zip(w_std_sorted.iter()) {
assert_relative_eq!(gen_val, std_val, epsilon = 1e-1);
}
}
#[test]
fn test_eig_gen_basic() {
let a = array![[1.0, 0.0], [0.0, 2.0]];
let b = array![[2.0, 0.0], [0.0, 1.0]];
let (w, v) = eig_gen(&a.view(), &b.view(), None).expect("Operation failed");
let mut eigenvals: Vec<_> = w.iter().map(|x| x.re).collect();
eigenvals.sort_by(|a, b| a.partial_cmp(b).expect("Operation failed"));
assert_relative_eq!(eigenvals[0], 0.5, epsilon = 1e-10);
assert_relative_eq!(eigenvals[1], 2.0, epsilon = 1e-10);
for i in 0..2 {
let lambda = w[i];
assert!(lambda.re.is_finite());
assert!(lambda.im.abs() < 1e-10); }
}
#[test]
fn test_eigh_gen_basic() {
let a = array![[2.0, 1.0], [1.0, 3.0]];
let b = array![[1.0, 0.0], [0.0, 2.0]];
let (w, v) = eigh_gen(&a.view(), &b.view(), None).expect("Operation failed");
assert!(w[0] <= w[1]);
for i in 0..2 {
let lambda = w[i];
let x = v.column(i);
let ax = a.dot(&x.to_owned());
let bx = b.dot(&x.to_owned());
let lambda_bx = bx.mapv(|val| lambda * val);
for j in 0..2 {
assert_relative_eq!(ax[j], lambda_bx[j], epsilon = 5e-2);
}
}
for i in 0..2 {
for j in 0..2 {
let xi = v.column(i);
let xj = v.column(j);
let bxj = b.dot(&xj.to_owned());
let dot_product = xi.dot(&bxj);
if i == j {
assert_relative_eq!(dot_product, 1.0, epsilon = 1e-8);
} else {
assert_relative_eq!(dot_product, 0.0, epsilon = 1e-8);
}
}
}
}
#[test]
fn test_eigvals_gen() {
let a = array![[2.0, 1.0], [1.0, 2.0]];
let b = array![[1.0, 0.0], [0.0, 1.0]];
let w_full = eig_gen(&a.view(), &b.view(), None)
.expect("Operation failed")
.0;
let w_vals_only = eigvals_gen(&a.view(), &b.view(), None).expect("Operation failed");
for i in 0..w_full.len() {
assert_relative_eq!(w_full[i].re, w_vals_only[i].re, epsilon = 1e-10);
assert_relative_eq!(w_full[i].im, w_vals_only[i].im, epsilon = 1e-10);
}
}
#[test]
fn test_eigvalsh_gen() {
let a = array![[2.0, 1.0], [1.0, 3.0]];
let b = array![[1.0, 0.0], [0.0, 2.0]];
let w_full = eigh_gen(&a.view(), &b.view(), None)
.expect("Operation failed")
.0;
let w_vals_only = eigvalsh_gen(&a.view(), &b.view(), None).expect("Operation failed");
for i in 0..w_full.len() {
assert_relative_eq!(w_full[i], w_vals_only[i], epsilon = 1e-10);
}
}
}