#![cfg(feature = "lapack")]
use crate::array::Array;
use crate::error::{NumRs2Error, Result};
use crate::new_modules::matrix_decomp::svd;
use num_traits::{Float, Zero};
use std::fmt::Debug;
type LstsqResult<T> = Result<(
Array<T>,
Array<T>, // Residuals are same type as matrix elements
usize,
Array<T>, // Singular values are same type as matrix elements
)>;
pub fn condition_number<T>(a: &Array<T>) -> Result<T>
where
T: Float + Clone + Debug,
{
let (_, s, _) = svd(a)?;
let s_vec = s.to_vec();
if s_vec.is_empty() {
return Err(NumRs2Error::ComputationError(
"Cannot compute condition number of empty matrix".to_string(),
));
}
let max_sv = s_vec
.iter()
.cloned()
.fold(T::zero(), |a, b| if a > b { a } else { b });
let eps = T::epsilon();
let threshold = max_sv
* eps
* T::from(std::cmp::max(a.shape()[0], a.shape()[1])).unwrap_or_else(|| T::one());
let non_zero_sv = s_vec
.iter()
.cloned()
.filter(|&sv| sv > threshold)
.collect::<Vec<_>>();
if non_zero_sv.is_empty() || max_sv == T::zero() {
return Ok(T::infinity());
}
let min_sv_all = s_vec
.iter()
.cloned()
.fold(max_sv, |a, b| if a < b { a } else { b });
if max_sv / min_sv_all > T::from(1e16).unwrap_or_else(|| T::max_value()) {
return Ok(T::infinity());
}
let min_sv = non_zero_sv
.iter()
.cloned()
.fold(max_sv, |a, b| if a < b { a } else { b });
let cond = max_sv / min_sv;
if cond.is_infinite() || cond.is_nan() {
return Ok(T::max_value());
}
Ok(cond)
}
pub fn rcond<T>(a: &Array<T>) -> Result<T>
where
T: Float + Clone + Debug,
{
let cond = condition_number(a)?;
if cond.is_infinite() {
Ok(T::zero())
} else {
Ok(T::one() / cond)
}
}
pub fn slogdet<T>(a: &Array<T>) -> Result<(i8, T)>
where
T: Float
+ Clone
+ Debug
+ std::ops::AddAssign
+ std::ops::MulAssign
+ std::ops::DivAssign
+ std::ops::SubAssign
+ std::fmt::Display,
{
let shape = a.shape();
if shape.len() != 2 || shape[0] != shape[1] {
return Err(NumRs2Error::DimensionMismatch(
"slogdet requires a square matrix".to_string(),
));
}
let n = shape[0];
if n <= 3 {
let det = a.det()?;
if det == T::zero() {
return Ok((0, T::neg_infinity()));
}
let sign = if det > T::zero() { 1 } else { -1 };
let logdet = num_traits::Float::ln(num_traits::Float::abs(det));
return Ok((sign, logdet));
}
let lu_result = crate::new_modules::matrix_decomp::lu::lu(a);
match lu_result {
Ok((_l, u, p)) => {
let p_vec = p.to_vec();
let mut visited = vec![false; n];
let mut num_swaps = 0;
for i in 0..n {
if !visited[i] {
let mut j = i;
let mut cycle_length = 0;
while !visited[j] {
visited[j] = true;
j = p_vec[j];
cycle_length += 1;
}
if cycle_length > 1 {
num_swaps += cycle_length - 1;
}
}
}
let mut sign = if num_swaps % 2 == 0 { 1i8 } else { -1i8 };
let mut logdet = T::zero();
for i in 0..n {
let u_diag = u.get(&[i, i])?;
if u_diag == T::zero() {
return Ok((0, T::neg_infinity()));
}
if u_diag < T::zero() {
sign = -sign;
}
logdet += num_traits::Float::ln(num_traits::Float::abs(u_diag));
}
Ok((sign, logdet))
}
Err(_) => {
let (_, s, _) = svd(a)?;
let s_vec = s.to_vec();
let eps = T::epsilon();
let threshold = eps
* T::from(n).unwrap_or_else(|| T::one())
* s_vec
.iter()
.cloned()
.fold(T::zero(), |a, b| if a > b { a } else { b });
let mut logdet = T::zero();
let mut zero_count = 0;
for &sv in &s_vec {
if sv <= threshold {
zero_count += 1;
} else {
logdet += num_traits::Float::ln(sv);
}
}
if zero_count > 0 {
Ok((0, T::neg_infinity()))
} else {
let sign = 1; Ok((sign, logdet))
}
}
}
}
pub fn lstsq<T>(a: &Array<T>, b: &Array<T>, rcond: Option<T>) -> LstsqResult<T>
where
T: Float + Clone + Debug,
{
let a_shape = a.shape();
let b_shape = b.shape();
if a_shape.len() != 2 {
return Err(NumRs2Error::DimensionMismatch(
"lstsq requires a 2D coefficient matrix".to_string(),
));
}
let m = a_shape[0]; let n = a_shape[1];
if b_shape[0] != m {
return Err(NumRs2Error::ShapeMismatch {
expected: vec![m],
actual: b_shape.clone(),
});
}
let k = if b_shape.len() == 1 { 1 } else { b_shape[1] };
let b_is_vector = b_shape.len() == 1;
let (u, s, vt) = svd(a)?;
let s_vec = s.to_vec();
let max_sv = s_vec
.iter()
.cloned()
.fold(T::zero(), |a, b| if a > b { a } else { b });
let cutoff = match rcond {
Some(rc) => rc * max_sv,
None => {
let eps = T::epsilon();
eps * T::from(std::cmp::max(m, n)).unwrap_or_else(|| T::one()) * max_sv
}
};
let rank = s_vec.iter().filter(|&&sv| sv > cutoff).count();
let ut_b = u.transpose().matmul(b)?;
let mut s_pinv_ut_b = ut_b.clone();
let min_dim = std::cmp::min(m, n);
for i in 0..min_dim {
for j in 0..k {
let ut_b_val = ut_b.get(&[i, j])?;
if i < s_vec.len() && s_vec[i] > cutoff {
let sv_as_t = <T as num_traits::NumCast>::from(s_vec[i])
.expect("singular value should convert to float type");
let new_val = ut_b_val / sv_as_t;
s_pinv_ut_b.set(&[i, j], new_val)?;
} else {
s_pinv_ut_b.set(&[i, j], T::zero())?;
}
}
}
let v = vt.transpose();
let x = v.matmul(&s_pinv_ut_b)?;
let x_final = if b_is_vector && x.shape().len() == 2 && x.shape()[1] == 1 {
let x_vec: Vec<T> = (0..x.shape()[0])
.map(|i| x.get(&[i, 0]).expect("index should be valid"))
.collect();
Array::from_vec(x_vec)
} else {
x
};
let residuals = if m > n && rank == n {
let ax = a.matmul(&x_final)?;
let diff = if b_is_vector {
let ax_vec: Vec<T> = (0..ax.shape()[0])
.map(|i| ax.get(&[i]).expect("index should be valid"))
.collect();
let b_vec = b.to_vec();
let diff_vec: Vec<T> = ax_vec
.iter()
.zip(b_vec.iter())
.map(|(&ax_val, &b_val)| ax_val - b_val)
.collect();
Array::from_vec(diff_vec)
} else {
let mut diff_result = Array::zeros(&b.shape());
for i in 0..b.shape()[0] {
for j in 0..k {
let ax_val = ax.get(&[i, j])?;
let b_val = b.get(&[i, j])?;
diff_result.set(&[i, j], ax_val - b_val)?;
}
}
diff_result
};
let mut residuals_vec = Vec::with_capacity(k);
for j in 0..k {
let mut sum_sq = T::zero();
for i in 0..m {
let val = if b_is_vector && k == 1 {
diff.get(&[i])?
} else {
diff.get(&[i, j])?
};
sum_sq = sum_sq + val * val;
}
residuals_vec.push(sum_sq);
}
Array::from_vec(residuals_vec)
} else {
Array::from_vec(vec![])
};
Ok((x_final, residuals, rank, s))
}