use super::{DimensionMismatch, svd};
use crate::scalar::Scalar;
use crate::storage::Storage;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ConditionNumberError {
DimensionMismatch,
Singular,
}
impl From<DimensionMismatch> for ConditionNumberError {
fn from(_: DimensionMismatch) -> Self {
ConditionNumberError::DimensionMismatch
}
}
pub fn condition_number<S, T>(
a: &S,
rows: usize,
cols: usize,
scratch: &mut [T],
) -> Result<T, ConditionNumberError>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
condition_number_svd(a, rows, cols, scratch)
}
pub fn condition_number_svd<S, T>(
a: &S,
rows: usize,
cols: usize,
scratch: &mut [T],
) -> Result<T, ConditionNumberError>
where
S: Storage<Item = T>,
T: Scalar + PartialOrd,
{
if rows != cols {
return Err(ConditionNumberError::DimensionMismatch);
}
let n = rows;
let nn = n * n;
if a.len() != nn || scratch.len() != 7 * nn + 3 * n {
return Err(ConditionNumberError::DimensionMismatch);
}
let (u_buf, rest) = scratch.split_at_mut(nn);
let (v_buf, rest) = rest.split_at_mut(nn);
let (sigma_buf, svd_scratch) = rest.split_at_mut(n);
svd(a, n, n, u_buf, sigma_buf, v_buf, svd_scratch)?;
let (Some(&sigma_max), Some(&sigma_min)) = (sigma_buf.first(), sigma_buf.last()) else {
return Err(ConditionNumberError::DimensionMismatch);
};
if sigma_min == T::zero() {
return Err(ConditionNumberError::Singular);
}
Ok(sigma_max.div(sigma_min))
}
#[cfg(test)]
mod tests {
use super::{ConditionNumberError, condition_number, condition_number_svd};
use crate::storage::StaticStorage;
#[test]
fn condition_number_of_identity_matrix_is_one() {
#[rustfmt::skip]
let a = StaticStorage::new([
1.0_f64, 0.0, 0.0,
0.0, 1.0, 0.0,
0.0, 0.0, 1.0,
]);
let mut scratch = [0.0; 7 * 3 * 3 + 3 * 3];
let kappa = condition_number_svd(&a, 3, 3, &mut scratch).unwrap();
assert!((kappa - 1.0).abs() < 1e-9);
}
#[test]
fn condition_number_of_ill_conditioned_diagonal_matrix_matches_known_ratio() {
let a = StaticStorage::new([100.0_f64, 0.0, 0.0, 1.0]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
let kappa = condition_number_svd(&a, 2, 2, &mut scratch).unwrap();
assert!((kappa - 100.0).abs() < 1e-6);
}
#[test]
fn condition_number_of_singular_matrix_is_an_error() {
let a = StaticStorage::new([1.0_f64, 2.0, 2.0, 4.0]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
assert_eq!(
condition_number_svd(&a, 2, 2, &mut scratch),
Err(ConditionNumberError::Singular)
);
}
#[test]
fn condition_number_of_non_square_matrix_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 2.0, 3.0, 4.0, 5.0, 6.0]);
let mut scratch = [0.0; 7 * 3 * 3 + 3 * 3];
assert_eq!(
condition_number_svd(&a, 2, 3, &mut scratch),
Err(ConditionNumberError::DimensionMismatch)
);
}
#[test]
fn condition_number_mismatched_scratch_length_is_an_error_not_a_panic() {
let a = StaticStorage::new([1.0, 0.0, 0.0, 1.0]);
let mut scratch = [0.0; 4];
assert_eq!(
condition_number_svd(&a, 2, 2, &mut scratch),
Err(ConditionNumberError::DimensionMismatch)
);
}
#[test]
fn condition_number_matches_condition_number_svd() {
let a = StaticStorage::new([100.0_f64, 0.0, 0.0, 1.0]);
let mut scratch_high_level = [0.0; 7 * 2 * 2 + 3 * 2];
let kappa_high_level = condition_number(&a, 2, 2, &mut scratch_high_level).unwrap();
let mut scratch_explicit = [0.0; 7 * 2 * 2 + 3 * 2];
let kappa_explicit = condition_number_svd(&a, 2, 2, &mut scratch_explicit).unwrap();
assert_eq!(kappa_high_level, kappa_explicit);
}
}