use super::{DimensionMismatch, QR_ITERATIONS, n_as_scalar, svd_qr_iteration};
use crate::scalar::{FloatTolerance, 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 + FloatTolerance + PartialOrd,
{
let tolerance = n_as_scalar::<T>(rows * QR_ITERATIONS).mul(T::epsilon());
condition_number_svd(a, rows, cols, scratch, tolerance)
}
pub fn condition_number_svd<S, T>(
a: &S,
rows: usize,
cols: usize,
scratch: &mut [T],
tolerance: 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_qr_iteration(a, n, n, u_buf, sigma_buf, v_buf, svd_scratch, tolerance)?;
let (Some(&sigma_max), Some(&sigma_min)) = (sigma_buf.first(), sigma_buf.last()) else {
return Err(ConditionNumberError::DimensionMismatch);
};
if sigma_min <= tolerance.mul(sigma_max) {
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, 1e-9).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, 1e-9).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, 1e-9),
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, 1e-9),
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, 1e-9),
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, 1e-9).unwrap();
assert_eq!(kappa_high_level, kappa_explicit);
}
#[test]
fn condition_number_default_tolerance_flags_an_extremely_ill_conditioned_matrix() {
let a = StaticStorage::new([1.0_f64, 0.0, 0.0, 1e-20]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
assert_eq!(
condition_number(&a, 2, 2, &mut scratch),
Err(ConditionNumberError::Singular)
);
}
#[test]
fn condition_number_explicit_tolerance_too_strict_misses_the_same_case() {
let a = StaticStorage::new([1.0_f64, 0.0, 0.0, 1e-20]);
let mut scratch = [0.0; 7 * 2 * 2 + 3 * 2];
assert!(condition_number_svd(&a, 2, 2, &mut scratch, 1e-25).is_ok());
}
}