use super::householder::norm2;
use super::schur::hessenberg;
use super::svd::{svd, svd_scaled};
use super::{Matrix, Scalar};
use crate::math::complex::Float;
pub(crate) fn range_exponent<T: Scalar>(data: &[T]) -> i32 {
let zero = T::Real::ZERO;
let small = T::Real::MIN_POSITIVE.sqrt() / T::Real::EPSILON;
let big = data
.iter()
.fold(zero, |s, x| s.max(x.re().abs().max(x.im().abs())));
if big >= small && big * T::Real::TWO.sqrt() <= T::Real::ONE / small {
return 0;
}
let max = data.iter().fold(zero, |s, x| s.max(x.abs()));
let log2 = |x: T::Real| x.ln().to_f64() / core::f64::consts::LN_2;
let top = if max.is_finite() {
log2(max)
} else {
log2(big) + 0.5
};
if max > zero && max < small {
(log2(small) - top).ceil() as i32
} else if max > T::Real::ONE / small {
(-log2(small) - top).floor() as i32
} else {
0
}
}
fn max_nan<R: Float>(m: R, x: R) -> R {
if m.is_nan() || x.is_nan() {
R::NAN
} else {
m.max(x)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Norm {
One,
Two,
Infinity,
Frobenius,
Max,
Nuclear,
}
impl<T: Scalar> Matrix<T> {
pub fn norm(&self, norm: Norm) -> T::Real {
let zero = T::Real::ZERO;
let (m, n) = self.shape();
match norm {
Norm::One => (0..n)
.map(|j| self.column(j).iter().map(|x| x.abs()).sum::<T::Real>())
.fold(zero, max_nan),
Norm::Infinity => {
let mut rows = vec![zero; m];
for j in 0..n {
for (r, x) in rows.iter_mut().zip(self.column(j)) {
*r += x.abs();
}
}
rows.into_iter().fold(zero, max_nan)
}
Norm::Frobenius => norm2(&self.data),
Norm::Max => self.data.iter().map(|x| x.abs()).fold(zero, max_nan),
Norm::Two => svd(self, false).0.first().copied().unwrap_or(zero),
Norm::Nuclear => svd(self, false).0.into_iter().sum(),
}
}
#[track_caller]
pub fn cond(&self, norm: Norm) -> T::Real {
let zero = T::Real::ZERO;
match norm {
Norm::Two => {
let s = svd_scaled(self, false).0;
match (s.first(), s.last()) {
(Some(&max), Some(&min)) if max.is_nan() || min.is_nan() => T::Real::NAN,
(Some(&max), Some(&min)) if min > zero => max / min,
(Some(_), Some(_)) => T::Real::INFINITY,
_ => zero,
}
}
Norm::Nuclear => {
assert!(self.is_square(), "the matrix is not square");
let s = svd_scaled(self, false).0;
if s.last().is_some_and(|&x| x == zero) {
return T::Real::INFINITY;
}
let top = s.first().copied().unwrap_or(T::Real::ONE);
let sum: T::Real = s.iter().map(|&x| x / top).sum();
let inv: T::Real = s.iter().map(|&x| top / x).sum();
sum * inv
}
_ => {
assert!(self.is_square(), "the matrix is not square");
match self.inverse() {
Some(inv) => self.norm(norm) * inv.norm(norm),
None => T::Real::INFINITY,
}
}
}
}
pub fn rank(&self) -> usize {
let s = svd_scaled(self, false).0;
let tol = self.rank_tolerance(&s);
s.iter().filter(|&&x| x > tol).count()
}
pub fn null_space(&self) -> Matrix<T> {
let (m, n) = self.shape();
let a = if m < n {
Matrix::from_fn(n, n, |i, j| if i < m { self[(i, j)] } else { T::zero() })
} else {
self.clone()
};
let (s, uv, _) = svd_scaled(&a, true);
let v = uv.map(|(_, v)| v).unwrap_or_default();
let tol = self.rank_tolerance(&s);
let rank = s.iter().filter(|&&x| x > tol).count();
Matrix::from_fn(n, n - rank, |i, j| v[(i, rank + j)])
}
fn rank_tolerance(&self, s: &[T::Real]) -> T::Real {
let (m, n) = self.shape();
let max = s.first().copied().unwrap_or(T::Real::ZERO);
T::Real::from_f64(m.max(n) as f64) * T::Real::EPSILON * max
}
#[track_caller]
pub fn characteristic_polynomial(&self) -> Vec<T> {
assert!(self.is_square(), "the matrix is not square");
let n = self.rows();
let mut h = self.clone();
hessenberg(&mut h, false);
let at = |i: usize, j: usize| h.data[i + j * n];
let mut p: Vec<Vec<T>> = vec![vec![T::one()]];
for k in 0..n {
let prev = &p[k];
let mut next = vec![T::zero(); k + 2];
for (i, &c) in prev.iter().enumerate() {
next[i + 1] += c;
next[i] -= at(k, k) * c;
}
let mut product = T::one();
for m in 1..=k {
product *= at(k - m + 1, k - m);
let f = at(k - m, k) * product;
if f == T::zero() {
continue;
}
for (i, &c) in p[k - m].iter().enumerate() {
next[i] -= f * c;
}
}
p.push(next);
}
let mut c = p.pop().unwrap_or_default();
c.reverse();
c
}
}