use scirs2_core::ndarray::{Array2, ArrayView2};
use scirs2_core::numeric::{Float, NumAssign, One};
use std::iter::Sum;
use crate::eigen::eigh;
use crate::error::{LinalgError, LinalgResult};
use crate::solve::solve_multiple;
use crate::validation::validate_decomposition;
#[allow(dead_code)]
pub fn fractionalmatrix_power<F>(a: &ArrayView2<F>, p: F, method: &str) -> LinalgResult<Array2<F>>
where
F: Float
+ NumAssign
+ Sum
+ One
+ 'static
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Display,
{
validate_decomposition(a, "Fractional matrix power computation", true)?;
let n = a.nrows();
if p.abs() < F::epsilon() {
return Ok(Array2::eye(n));
}
if (p - F::one()).abs() < F::epsilon() {
return Ok(a.to_owned());
}
match method {
"eigen" => {
eigendecomposition_power(a, p)
}
"pade" => {
pade_fractional_power(a, p)
}
"schur" => {
eigendecomposition_power(a, p)
}
_ => Err(LinalgError::InvalidInputError(format!(
"Unknown method '{}'. Supported methods: 'eigen', 'pade', 'schur'",
method
))),
}
}
fn eigendecomposition_power<F>(a: &ArrayView2<F>, p: F) -> LinalgResult<Array2<F>>
where
F: Float
+ NumAssign
+ Sum
+ One
+ 'static
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Display,
{
let n = a.nrows();
let mut is_diagonal = true;
for i in 0..n {
for j in 0..n {
if i != j && a[[i, j]].abs() > F::epsilon() {
is_diagonal = false;
break;
}
}
if !is_diagonal {
break;
}
}
if is_diagonal {
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
let val = a[[i, i]];
if val < F::zero() && !is_integer(p) {
return Err(LinalgError::InvalidInputError(
"Cannot compute real fractional power of negative number".to_string(),
));
}
result[[i, i]] = val.powf(p);
}
return Ok(result);
}
let mut is_symmetric = true;
let sym_tol = F::epsilon() * F::from(n as f64 * 100.0).unwrap_or(F::one());
for i in 0..n {
for j in (i + 1)..n {
if (a[[i, j]] - a[[j, i]]).abs() > sym_tol {
is_symmetric = false;
break;
}
}
if !is_symmetric {
break;
}
}
if is_symmetric {
return symmetric_eigendecomposition_power(a, p);
}
general_schur_power(a, p)
}
fn symmetric_eigendecomposition_power<F>(a: &ArrayView2<F>, p: F) -> LinalgResult<Array2<F>>
where
F: Float
+ NumAssign
+ Sum
+ One
+ 'static
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Display,
{
let n = a.nrows();
let (eigenvalues, eigenvectors) = eigh(a, None)?;
if !is_integer(p) {
for i in 0..eigenvalues.len() {
if eigenvalues[i] < F::zero() {
return Err(LinalgError::InvalidInputError(
"Cannot compute real fractional power of matrix with negative eigenvalues"
.to_string(),
));
}
}
}
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut sum = F::zero();
for kk in 0..eigenvalues.len() {
let lam_p = eigenvalues[kk].powf(p);
sum += eigenvectors[[i, kk]] * lam_p * eigenvectors[[j, kk]];
}
result[[i, j]] = sum;
}
}
Ok(result)
}
fn general_schur_power<F>(a: &ArrayView2<F>, p: F) -> LinalgResult<Array2<F>>
where
F: Float
+ NumAssign
+ Sum
+ One
+ 'static
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Display,
{
let n = a.nrows();
let (q_mat, t_mat) = crate::schur(a)?;
let mut f_t = Array2::<F>::zeros((n, n));
for i in 0..n {
let t_ii = t_mat[[i, i]];
if t_ii < F::zero() && !is_integer(p) {
return Err(LinalgError::InvalidInputError(format!(
"Cannot compute real fractional power: Schur diagonal element {} is negative",
t_ii
)));
}
if t_ii.abs() < F::epsilon() {
if p > F::zero() {
f_t[[i, i]] = F::zero();
} else {
return Err(LinalgError::SingularMatrixError(
"Cannot compute negative power of singular matrix".to_string(),
));
}
} else {
f_t[[i, i]] = t_ii.powf(p);
}
}
for j in 1..n {
for i in (0..j).rev() {
let diff = t_mat[[j, j]] - t_mat[[i, i]];
if diff.abs() > F::epsilon() * F::from(100.0).unwrap_or(F::one()) {
let mut val = t_mat[[i, j]] * (f_t[[j, j]] - f_t[[i, i]]) / diff;
for kk in (i + 1)..j {
val += (f_t[[i, kk]] * t_mat[[kk, j]] - t_mat[[i, kk]] * f_t[[kk, j]]) / diff;
}
f_t[[i, j]] = val;
} else {
let t_ii = t_mat[[i, i]];
if t_ii.abs() > F::epsilon() {
f_t[[i, j]] = t_mat[[i, j]] * p * t_ii.powf(p - F::one());
} else {
f_t[[i, j]] = F::zero();
}
}
}
}
let mut temp = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut sum = F::zero();
for kk in 0..n {
sum += q_mat[[i, kk]] * f_t[[kk, j]];
}
temp[[i, j]] = sum;
}
}
let mut result = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
let mut sum = F::zero();
for kk in 0..n {
sum += temp[[i, kk]] * q_mat[[j, kk]];
}
result[[i, j]] = sum;
}
}
Ok(result)
}
fn pade_fractional_power<F>(a: &ArrayView2<F>, p: F) -> LinalgResult<Array2<F>>
where
F: Float
+ NumAssign
+ Sum
+ One
+ 'static
+ Send
+ Sync
+ scirs2_core::ndarray::ScalarOperand
+ std::fmt::Display,
{
if is_integer(p) {
return integer_power(a, p.to_i32().unwrap_or(0));
}
if (p - F::from(0.5).expect("Operation failed")).abs() < F::epsilon() {
use super::exponential::sqrtm;
return sqrtm(a, 50, F::from(1e-12).expect("Operation failed"));
}
eigendecomposition_power(a, p)
}
fn integer_power<F>(a: &ArrayView2<F>, p: i32) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + Sum + One + 'static + Send + Sync + scirs2_core::ndarray::ScalarOperand,
{
let n = a.nrows();
if p == 0 {
return Ok(Array2::eye(n));
}
if p == 1 {
return Ok(a.to_owned());
}
if p < 0 {
let a_inv = solve_multiple(a, &Array2::eye(n).view(), None)?;
return integer_power(&a_inv.view(), -p);
}
let mut result = Array2::eye(n);
let mut base = a.to_owned();
let mut exp = p as u32;
while exp > 0 {
if exp % 2 == 1 {
let mut temp = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
for k in 0..n {
temp[[i, j]] += result[[i, k]] * base[[k, j]];
}
}
}
result = temp;
}
let mut temp = Array2::<F>::zeros((n, n));
for i in 0..n {
for j in 0..n {
for k in 0..n {
temp[[i, j]] += base[[i, k]] * base[[k, j]];
}
}
}
base = temp;
exp /= 2;
}
Ok(result)
}
#[allow(dead_code)]
pub fn spdmatrix_function<F, Func>(
a: &ArrayView2<F>,
f: Func,
check_spd: bool,
) -> LinalgResult<Array2<F>>
where
F: Float + NumAssign + Sum + One + 'static + Send + Sync + scirs2_core::ndarray::ScalarOperand,
Func: Fn(F) -> F,
{
validate_decomposition(a, "SPD matrix function computation", true)?;
let n = a.nrows();
for i in 0..n {
for j in 0..n {
if (a[[i, j]] - a[[j, i]]).abs() > F::epsilon() {
return Err(LinalgError::InvalidInputError(
"Matrix must be symmetric for SPD matrix function".to_string(),
));
}
}
}
if check_spd {
for i in 0..n {
if a[[i, i]] <= F::zero() {
return Err(LinalgError::InvalidInputError(
"Matrix must be positive definite".to_string(),
));
}
}
}
let mut is_diagonal = true;
for i in 0..n {
for j in 0..n {
if i != j && a[[i, j]].abs() > F::epsilon() {
is_diagonal = false;
break;
}
}
if !is_diagonal {
break;
}
}
if is_diagonal {
let mut result = Array2::zeros((n, n));
for i in 0..n {
result[[i, i]] = f(a[[i, i]]);
}
return Ok(result);
}
let (eigenvalues, eigenvectors) = eigh(a, None)?;
let mut diag = Array2::zeros((n, n));
for i in 0..n {
let eigenval = eigenvalues[i];
if check_spd && eigenval <= F::zero() {
return Err(LinalgError::InvalidInputError(
"Matrix is not positive definite (negative eigenvalue found)".to_string(),
));
}
diag[[i, i]] = f(eigenval);
}
let temp = eigenvectors.dot(&diag);
let v_t = eigenvectors.t();
let result = temp.dot(&v_t);
Ok(result)
}
fn is_integer<F: Float>(x: F) -> bool {
(x - x.round()).abs() < F::from(1e-10).unwrap_or(F::epsilon())
}