use crate::error::{LinalgError, LinalgResult};
use scirs2_core::ndarray::{Array2, ArrayView2, ScalarOperand};
use scirs2_core::numeric::{Complex, Float, NumAssign, One};
use std::iter::Sum;
pub trait AdvFloat:
Float + NumAssign + Sum + ScalarOperand + Send + Sync + 'static
{
}
impl<T> AdvFloat for T where
T: Float + NumAssign + Sum + ScalarOperand + Send + Sync + 'static
{
}
fn frobenius_norm<F: AdvFloat>(m: &Array2<F>) -> F {
let mut acc = F::zero();
for &v in m.iter() {
acc = acc + v * v;
}
acc.sqrt()
}
fn matmul<F: AdvFloat>(a: &Array2<F>, b: &Array2<F>) -> LinalgResult<Array2<F>> {
let (m, k) = (a.nrows(), a.ncols());
let (k2, n) = (b.nrows(), b.ncols());
if k != k2 {
return Err(LinalgError::ShapeError(format!(
"Inner dimensions mismatch: {} vs {}",
k, k2
)));
}
let mut c = Array2::<F>::zeros((m, n));
for i in 0..m {
for l in 0..k {
let aik = a[[i, l]];
for j in 0..n {
c[[i, j]] = c[[i, j]] + aik * b[[l, j]];
}
}
}
Ok(c)
}
pub fn matrix_sign<F: AdvFloat>(
a: &ArrayView2<F>,
max_iter: Option<usize>,
tol: Option<F>,
) -> LinalgResult<Array2<F>> {
let n = a.nrows();
if n != a.ncols() {
return Err(LinalgError::ShapeError(
"matrix_sign requires a square matrix".to_string(),
));
}
if n == 0 {
return Ok(Array2::zeros((0, 0)));
}
let max_iter = max_iter.unwrap_or(100);
let tol = tol.unwrap_or_else(|| F::from(1e-12).expect("convert"));
let mut is_diag = true;
for i in 0..n {
for j in 0..n {
if i != j && a[[i, j]].abs() > F::epsilon() {
is_diag = false;
break;
}
}
if !is_diag {
break;
}
}
if is_diag {
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
let v = a[[i, i]];
if v > F::zero() {
result[[i, i]] = F::one();
} else if v < F::zero() {
result[[i, i]] = -F::one();
} else {
return Err(LinalgError::InvalidInputError(
"matrix_sign: zero eigenvalue encountered; sign is undefined".to_string(),
));
}
}
return Ok(result);
}
let mut x = a.to_owned();
let half = F::from(0.5).expect("convert");
for _iter in 0..max_iter {
let x_inv = crate::basic::inv(&x.view(), None).map_err(|e| {
LinalgError::ComputationError(format!(
"matrix_sign Newton step: matrix inversion failed at iteration {_iter}: {e}"
))
})?;
let mut x_next = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
x_next[[i, j]] = (x[[i, j]] + x_inv[[i, j]]) * half;
}
}
let mut diff = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
diff[[i, j]] = x_next[[i, j]] - x[[i, j]];
}
}
let res = frobenius_norm(&diff);
x = x_next;
if res < tol {
return Ok(x);
}
}
Ok(x)
}
pub fn matrix_sector<F: AdvFloat>(
a: &ArrayView2<F>,
m: usize,
max_iter: Option<usize>,
tol: Option<F>,
) -> LinalgResult<Array2<F>> {
let n = a.nrows();
if n != a.ncols() {
return Err(LinalgError::ShapeError(
"matrix_sector requires a square matrix".to_string(),
));
}
if m < 2 {
return Err(LinalgError::InvalidInputError(
"m must be >= 2 for matrix sector function".to_string(),
));
}
if n == 0 {
return Ok(Array2::zeros((0, 0)));
}
let max_iter = max_iter.unwrap_or(100);
let tol = tol.unwrap_or_else(|| F::from(1e-12).expect("convert"));
if m == 2 {
return matrix_sign(a, Some(max_iter), Some(tol));
}
let symmetric = is_symmetric(a);
if symmetric {
let (eigenvalues, eigenvectors) =
crate::eigen::eigh(a, None).map_err(|e| {
LinalgError::ComputationError(format!(
"matrix_sector eigendecomposition failed: {e}"
))
})?;
let n_eig = eigenvalues.len();
let mut sector_diag = Array2::<F>::zeros((n_eig, n_eig));
for i in 0..n_eig {
let lam = eigenvalues[i];
if lam.abs() < F::epsilon() {
return Err(LinalgError::InvalidInputError(
"matrix_sector: zero eigenvalue encountered".to_string(),
));
}
if lam < F::zero() && m % 2 == 0 {
return Err(LinalgError::InvalidInputError(format!(
"matrix_sector: negative eigenvalue {} with even m={} produces complex result; \
use complex arithmetic",
lam.to_f64().expect("convert"),
m
)));
}
let abs_lam = lam.abs();
let sign = if lam >= F::zero() { F::one() } else { -F::one() };
let root = abs_lam.powf(F::one() / F::from(m).expect("convert"));
sector_diag[[i, i]] = sign * (root / lam.abs()) * root;
sector_diag[[i, i]] = if lam > F::zero() { F::one() } else { -F::one() };
}
let result = reconstruct_symmetric(eigenvectors, sector_diag, n)?;
return Ok(result);
}
let m_f = F::from(m).expect("convert");
let m1_f = F::from(m - 1).expect("convert");
let mut x = a.to_owned();
for _iter in 0..max_iter {
let mut x_power = Array2::<F>::eye(n);
for _ in 0..(m - 1) {
x_power = matmul(&x_power, &x)?;
}
let x_power_inv = crate::basic::inv(&x_power.view(), None).map_err(|e| {
LinalgError::ComputationError(format!(
"matrix_sector: inversion failed at iteration {_iter}: {e}"
))
})?;
let ax_inv = matmul(&a.to_owned(), &x_power_inv)?;
let mut x_next = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
x_next[[i, j]] = (m1_f * x[[i, j]] + ax_inv[[i, j]]) / m_f;
}
}
let mut diff = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
diff[[i, j]] = x_next[[i, j]] - x[[i, j]];
}
}
let res = frobenius_norm(&diff);
x = x_next;
if res < tol {
break;
}
}
let norm = frobenius_norm(&x);
if norm < F::epsilon() {
return Err(LinalgError::ComputationError(
"matrix_sector: degenerate result".to_string(),
));
}
Ok(x)
}
pub fn matrix_geometric_mean<F: AdvFloat>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
) -> LinalgResult<Array2<F>> {
let (m, n) = (a.nrows(), a.ncols());
if m != n {
return Err(LinalgError::ShapeError(
"matrix_geometric_mean: matrix A must be square".to_string(),
));
}
if b.nrows() != m || b.ncols() != n {
return Err(LinalgError::ShapeError(format!(
"matrix_geometric_mean: A and B must have the same shape; A: {}x{}, B: {}x{}",
m, n, b.nrows(), b.ncols()
)));
}
if n == 0 {
return Ok(Array2::zeros((0, 0)));
}
use crate::matrix_functions::fractional::spdmatrix_function;
let a_sqrt = spdmatrix_function(a, |x| x.sqrt(), true)?;
let a_neg_half = spdmatrix_function(a, |x| F::one() / x.sqrt(), true)?;
let tmp = matmul(&a_neg_half, &b.to_owned())?;
let c = matmul(&tmp, &a_neg_half)?;
let c_sqrt = spdmatrix_function(&c.view(), |x| x.sqrt(), true)?;
let tmp2 = matmul(&a_sqrt, &c_sqrt)?;
let g = matmul(&tmp2, &a_sqrt)?;
Ok(g)
}
pub fn bregman_divergence<F: AdvFloat>(
a: &ArrayView2<F>,
b: &ArrayView2<F>,
) -> LinalgResult<F> {
let (m, n) = (a.nrows(), a.ncols());
if m != n {
return Err(LinalgError::ShapeError(
"bregman_divergence: matrix A must be square".to_string(),
));
}
if b.nrows() != m || b.ncols() != n {
return Err(LinalgError::ShapeError(
"bregman_divergence: A and B must have the same shape".to_string(),
));
}
let b_inv = crate::basic::inv(b, None)?;
let ab_inv = matmul(&a.to_owned(), &b_inv)?;
let mut trace_val = F::zero();
for i in 0..n {
trace_val = trace_val + ab_inv[[i, i]];
}
let (eigs_a, _) = crate::eigen::eigh(a, None)?;
let (eigs_b, _) = crate::eigen::eigh(b, None)?;
let mut log_det_a = F::zero();
for &e in eigs_a.iter() {
if e <= F::zero() {
return Err(LinalgError::InvalidInputError(
"bregman_divergence: matrix A is not positive definite".to_string(),
));
}
log_det_a = log_det_a + e.ln();
}
let mut log_det_b = F::zero();
for &e in eigs_b.iter() {
if e <= F::zero() {
return Err(LinalgError::InvalidInputError(
"bregman_divergence: matrix B is not positive definite".to_string(),
));
}
log_det_b = log_det_b + e.ln();
}
let log_det_ratio = log_det_a - log_det_b;
let n_f = F::from(n).expect("convert");
Ok(trace_val - log_det_ratio - n_f)
}
pub fn matrix_pth_root<F: AdvFloat>(a: &ArrayView2<F>, p: u32) -> LinalgResult<Array2<F>> {
let n = a.nrows();
if n != a.ncols() {
return Err(LinalgError::ShapeError(
"matrix_pth_root requires a square matrix".to_string(),
));
}
if p == 0 {
return Err(LinalgError::InvalidInputError(
"matrix_pth_root: p must be >= 1 (p=0 is undefined)".to_string(),
));
}
if p == 1 {
return Ok(a.to_owned());
}
if n == 0 {
return Ok(Array2::zeros((0, 0)));
}
let p_f = F::from(p).expect("convert");
let symmetric = is_symmetric(a);
if symmetric {
let (eigenvalues, eigenvectors) =
crate::eigen::eigh(a, None).map_err(|e| {
LinalgError::ComputationError(format!(
"matrix_pth_root: eigendecomposition failed: {e}"
))
})?;
let n_eig = eigenvalues.len();
let mut root_diag = Array2::<F>::zeros((n_eig, n_eig));
for i in 0..n_eig {
let lam = eigenvalues[i];
if lam < F::zero() && p % 2 == 0 {
return Err(LinalgError::InvalidInputError(format!(
"matrix_pth_root: negative eigenvalue {} with even p={} yields complex root",
lam.to_f64().expect("convert"),
p
)));
}
let sign = if lam >= F::zero() { F::one() } else { -F::one() };
root_diag[[i, i]] = sign * lam.abs().powf(F::one() / p_f);
}
return reconstruct_symmetric(eigenvectors, root_diag, n);
}
let max_elem = a.iter().fold(F::zero(), |acc, &x| if x.abs() > acc { x.abs() } else { acc });
let scale = if max_elem > F::epsilon() { max_elem } else { F::one() };
let a_scaled = a.mapv(|x| x / scale);
let scale_root = scale.powf(F::one() / p_f);
let max_iter = 200usize;
let tol = F::from(1e-12).expect("convert");
let p1 = p - 1;
let p1_f = F::from(p1).expect("convert");
let mut x = a_scaled.to_owned();
for _iter in 0..max_iter {
let mut xp1 = Array2::<F>::eye(n);
for _ in 0..p1 {
xp1 = matmul(&xp1, &x)?;
}
let xp1_inv = crate::basic::inv(&xp1.view(), None).map_err(|e| {
LinalgError::ComputationError(format!(
"matrix_pth_root Newton inversion failed at iteration {_iter}: {e}"
))
})?;
let ax_inv = matmul(&a_scaled, &xp1_inv)?;
let mut x_next = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
x_next[[i, j]] = (p1_f * x[[i, j]] + ax_inv[[i, j]]) / p_f;
}
}
let mut diff_arr = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
diff_arr[[i, j]] = x_next[[i, j]] - x[[i, j]];
}
}
let res = frobenius_norm(&diff_arr);
x = x_next;
if res < tol {
break;
}
}
let result = x.mapv(|v| v * scale_root);
Ok(result)
}
fn is_symmetric<F: AdvFloat>(a: &ArrayView2<F>) -> bool {
let n = a.nrows();
if n != a.ncols() {
return false;
}
let eps = F::epsilon() * F::from(1000.0).expect("convert");
for i in 0..n {
for j in (i + 1)..n {
if (a[[i, j]] - a[[j, i]]).abs() > eps {
return false;
}
}
}
true
}
fn reconstruct_symmetric<F: AdvFloat>(
eigenvectors: Array2<F>,
diag: Array2<F>,
n: usize,
) -> LinalgResult<Array2<F>> {
let vd = matmul(&eigenvectors, &diag)?;
let mut vt = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
vt[[i, j]] = eigenvectors[[j, i]];
}
}
matmul(&vd, &vt)
}
pub fn signm_newton<F: AdvFloat>(
a: &ArrayView2<F>,
max_iter: Option<usize>,
tol: Option<F>,
) -> LinalgResult<Array2<F>> {
matrix_sign(a, max_iter, tol)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use scirs2_core::ndarray::array;
#[test]
fn test_matrix_sign_diagonal() {
let a = array![[3.0_f64, 0.0], [0.0, -2.0]];
let s = matrix_sign(&a.view(), None, None).expect("matrix_sign");
assert_relative_eq!(s[[0, 0]], 1.0, epsilon = 1e-10);
assert_relative_eq!(s[[0, 1]], 0.0, epsilon = 1e-10);
assert_relative_eq!(s[[1, 0]], 0.0, epsilon = 1e-10);
assert_relative_eq!(s[[1, 1]], -1.0, epsilon = 1e-10);
}
#[test]
fn test_matrix_sign_idempotent() {
let a = array![[2.0_f64, 1.0], [1.0, 3.0]];
let s = matrix_sign(&a.view(), None, None).expect("matrix_sign");
let s2 = matmul(&s, &s).expect("matmul");
assert_relative_eq!(s2[[0, 0]], 1.0, epsilon = 1e-6);
assert_relative_eq!(s2[[0, 1]], 0.0, epsilon = 1e-6);
assert_relative_eq!(s2[[1, 0]], 0.0, epsilon = 1e-6);
assert_relative_eq!(s2[[1, 1]], 1.0, epsilon = 1e-6);
}
#[test]
fn test_matrix_geometric_mean_diagonal() {
let a = array![[4.0_f64, 0.0], [0.0, 1.0]];
let b = array![[1.0_f64, 0.0], [0.0, 4.0]];
let g = matrix_geometric_mean(&a.view(), &b.view()).expect("geometric_mean");
assert_relative_eq!(g[[0, 0]], 2.0, epsilon = 1e-8);
assert_relative_eq!(g[[1, 1]], 2.0, epsilon = 1e-8);
assert_relative_eq!(g[[0, 1]], 0.0, epsilon = 1e-8);
assert_relative_eq!(g[[1, 0]], 0.0, epsilon = 1e-8);
}
#[test]
fn test_bregman_divergence_self() {
let a = array![[2.0_f64, 0.0], [0.0, 3.0]];
let d = bregman_divergence(&a.view(), &a.view()).expect("bregman");
assert_relative_eq!(d, 0.0, epsilon = 1e-8);
}
#[test]
fn test_bregman_divergence_nonnegative() {
let a = array![[4.0_f64, 1.0], [1.0, 3.0]];
let b = array![[2.0_f64, 0.5], [0.5, 1.5]];
let d = bregman_divergence(&a.view(), &b.view()).expect("bregman");
assert!(d >= 0.0, "Bregman divergence must be non-negative, got {d}");
}
#[test]
fn test_matrix_pth_root_cubic() {
let a = array![[8.0_f64, 0.0], [0.0, 27.0]];
let r = matrix_pth_root(&a.view(), 3).expect("pth_root");
assert_relative_eq!(r[[0, 0]], 2.0, epsilon = 1e-6);
assert_relative_eq!(r[[1, 1]], 3.0, epsilon = 1e-6);
}
#[test]
fn test_matrix_pth_root_square() {
let a = array![[4.0_f64, 0.0], [0.0, 9.0]];
let r = matrix_pth_root(&a.view(), 2).expect("pth_root");
assert_relative_eq!(r[[0, 0]], 2.0, epsilon = 1e-8);
assert_relative_eq!(r[[1, 1]], 3.0, epsilon = 1e-8);
}
#[test]
fn test_matrix_pth_root_identity() {
let a = array![[5.0_f64, 0.0], [0.0, 7.0]];
let r = matrix_pth_root(&a.view(), 1).expect("pth_root p=1");
assert_relative_eq!(r[[0, 0]], 5.0, epsilon = 1e-12);
assert_relative_eq!(r[[1, 1]], 7.0, epsilon = 1e-12);
}
#[test]
fn test_matrix_sector_identity_m2() {
let a = array![[4.0_f64, 0.0], [0.0, 9.0]];
let s = matrix_sector(&a.view(), 2, None, None).expect("sector");
assert_relative_eq!(s[[0, 0]], 1.0, epsilon = 1e-8);
assert_relative_eq!(s[[1, 1]], 1.0, epsilon = 1e-8);
}
}