use crate::error::{CoreError, ErrorContext};
pub fn condition_number_linear_system(a: &[f64], n: usize) -> Result<f64, CoreError> {
validate_square(a, n)?;
if n == 1 {
let v = a[0];
if v == 0.0 {
return Ok(f64::INFINITY);
}
return Ok(1.0); }
let sigma_max = power_iteration_sigma_max(a, n, 200)?;
let sigma_min = inverse_power_iteration_sigma_min(a, n, 200)?;
if sigma_min < f64::EPSILON * sigma_max {
return Ok(f64::INFINITY);
}
Ok(sigma_max / sigma_min)
}
pub fn forward_error_bound(a: &[f64], n: usize, rel_error_b: f64) -> Result<f64, CoreError> {
if rel_error_b < 0.0 {
return Err(CoreError::InvalidInput(ErrorContext::new(
"forward_error_bound: rel_error_b must be non-negative".to_string(),
)));
}
let kappa = condition_number_linear_system(a, n)?;
Ok(kappa * rel_error_b)
}
pub fn backward_error_bound(
a: &[f64],
n: usize,
b: &[f64],
x_approx: &[f64],
) -> Result<f64, CoreError> {
validate_square(a, n)?;
if b.len() != n {
return Err(CoreError::InvalidInput(ErrorContext::new(format!(
"backward_error_bound: b has length {}, expected {n}",
b.len()
))));
}
if x_approx.len() != n {
return Err(CoreError::InvalidInput(ErrorContext::new(format!(
"backward_error_bound: x_approx has length {}, expected {n}",
x_approx.len()
))));
}
let r = compute_residual(a, n, b, x_approx);
let norm_r = l2_norm(&r);
let norm_a = frobenius_norm(a);
let norm_x = l2_norm(x_approx);
let norm_b = l2_norm(b);
let denominator = norm_a * norm_x + norm_b;
if denominator < f64::EPSILON {
if norm_r < f64::EPSILON {
return Ok(0.0);
}
return Ok(f64::INFINITY);
}
Ok(norm_r / denominator)
}
pub fn mixed_error_bound(
a: &[f64],
n: usize,
b: &[f64],
x_approx: &[f64],
) -> Result<f64, CoreError> {
let eta = backward_error_bound(a, n, b, x_approx)?;
let kappa = condition_number_linear_system(a, n)?;
Ok(eta * kappa)
}
pub fn significant_digits(relative_error: f64) -> u32 {
if relative_error <= 0.0 {
return 15; }
if relative_error >= 1.0 {
return 0;
}
let d = (-relative_error.log10()).floor() as i32;
d.clamp(0, 15) as u32
}
pub fn catastrophic_cancellation_check(a: f64, b: f64) -> u32 {
if a.is_nan() || b.is_nan() || a.is_infinite() || b.is_infinite() {
return 0;
}
let result = a - b;
if result == 0.0 {
if a != 0.0 {
return 52;
}
return 0;
}
if a == 0.0 && b == 0.0 {
return 0;
}
let max_operand = a.abs().max(b.abs());
if max_operand == 0.0 {
return 0;
}
let ratio = result.abs() / max_operand;
if ratio >= 1.0 {
return 0;
}
let bits_lost = (-ratio.log2()).floor() as i32;
bits_lost.clamp(0, 52) as u32
}
fn validate_square(a: &[f64], n: usize) -> Result<(), CoreError> {
if n == 0 {
return Err(CoreError::InvalidInput(ErrorContext::new(
"condition: matrix dimension is 0".to_string(),
)));
}
if a.len() != n * n {
return Err(CoreError::InvalidInput(ErrorContext::new(format!(
"condition: expected {}×{} = {} elements, got {}",
n,
n,
n * n,
a.len()
))));
}
Ok(())
}
fn frobenius_norm(a: &[f64]) -> f64 {
a.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
fn l2_norm(v: &[f64]) -> f64 {
v.iter().map(|&x| x * x).sum::<f64>().sqrt()
}
fn matvec(a: &[f64], n: usize, x: &[f64]) -> Vec<f64> {
let mut y = vec![0.0_f64; n];
for i in 0..n {
for j in 0..n {
y[i] += a[i * n + j] * x[j];
}
}
y
}
fn matvec_t(a: &[f64], n: usize, x: &[f64]) -> Vec<f64> {
let mut y = vec![0.0_f64; n];
for i in 0..n {
for j in 0..n {
y[j] += a[i * n + j] * x[i];
}
}
y
}
fn compute_residual(a: &[f64], n: usize, b: &[f64], x: &[f64]) -> Vec<f64> {
let ax = matvec(a, n, x);
b.iter().zip(ax.iter()).map(|(&bi, &axi)| bi - axi).collect()
}
fn normalize_inplace(v: &mut Vec<f64>) -> f64 {
let norm = l2_norm(v);
if norm > 0.0 {
for vi in v.iter_mut() {
*vi /= norm;
}
}
norm
}
fn power_iteration_sigma_max(
a: &[f64],
n: usize,
max_iter: usize,
) -> Result<f64, CoreError> {
let mut v: Vec<f64> = (0..n).map(|i| ((i + 1) as f64).recip()).collect();
normalize_inplace(&mut v);
let mut sigma = 0.0_f64;
for _ in 0..max_iter {
let av = matvec(a, n, &v);
let mut w = matvec_t(a, n, &av);
let norm = normalize_inplace(&mut w);
if (norm - sigma).abs() < f64::EPSILON * norm.max(1.0) {
sigma = norm;
break;
}
sigma = norm;
v = w;
}
Ok(sigma.sqrt())
}
fn inverse_power_iteration_sigma_min(
a: &[f64],
n: usize,
max_iter: usize,
) -> Result<f64, CoreError> {
let b = ata(a, n);
let frob_b = frobenius_norm(&b);
if frob_b < f64::EPSILON {
return Ok(0.0);
}
let mut v: Vec<f64> = vec![1.0 / (n as f64).sqrt(); n];
let mut mu = 0.0_f64;
for _ in 0..max_iter {
let w = match gaussian_elim(&b, n, &v) {
Some(w) => w,
None => return Ok(0.0), };
let mut w = w;
let norm = normalize_inplace(&mut w);
if norm < f64::EPSILON {
return Ok(0.0);
}
let lambda_inv = norm;
let lambda = lambda_inv.recip();
if (lambda - mu).abs() < f64::EPSILON * mu.max(1.0) {
mu = lambda;
break;
}
mu = lambda;
v = w;
}
Ok(mu.max(0.0).sqrt())
}
fn ata(a: &[f64], n: usize) -> Vec<f64> {
let mut b = vec![0.0_f64; n * n];
for i in 0..n {
for j in 0..n {
let mut s = 0.0_f64;
for k in 0..n {
s += a[k * n + i] * a[k * n + j];
}
b[i * n + j] = s;
}
}
b
}
fn gaussian_elim(a: &[f64], n: usize, b: &[f64]) -> Option<Vec<f64>> {
let mut mat: Vec<f64> = vec![0.0; n * (n + 1)];
for i in 0..n {
for j in 0..n {
mat[i * (n + 1) + j] = a[i * n + j];
}
mat[i * (n + 1) + n] = b[i];
}
let m = n + 1;
for col in 0..n {
let mut max_val = mat[col * m + col].abs();
let mut max_row = col;
for row in (col + 1)..n {
let v = mat[row * m + col].abs();
if v > max_val {
max_val = v;
max_row = row;
}
}
if max_val < f64::EPSILON {
return None; }
if max_row != col {
for j in 0..m {
mat.swap(col * m + j, max_row * m + j);
}
}
let pivot = mat[col * m + col];
for row in (col + 1)..n {
let factor = mat[row * m + col] / pivot;
for j in col..m {
let v = mat[col * m + j] * factor;
mat[row * m + j] -= v;
}
}
}
let mut x = vec![0.0_f64; n];
for i in (0..n).rev() {
let mut s = mat[i * m + n];
for j in (i + 1)..n {
s -= mat[i * m + j] * x[j];
}
let diag = mat[i * m + i];
if diag.abs() < f64::EPSILON {
return None;
}
x[i] = s / diag;
}
Some(x)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn condition_identity() {
let id = vec![1.0_f64, 0.0, 0.0, 1.0];
let kappa = condition_number_linear_system(&id, 2).expect("valid");
assert!((kappa - 1.0).abs() < 1e-8, "kappa={kappa}");
}
#[test]
fn condition_diagonal() {
let a = vec![2.0_f64, 0.0, 0.0, 8.0];
let kappa = condition_number_linear_system(&a, 2).expect("valid");
assert!((kappa - 4.0).abs() < 1e-6, "kappa={kappa}");
}
#[test]
fn condition_singular_is_inf() {
let a = vec![1.0_f64, 1.0, 1.0, 1.0]; let kappa = condition_number_linear_system(&a, 2).expect("valid");
assert!(kappa.is_infinite() || kappa > 1e14, "kappa={kappa}");
}
#[test]
fn condition_wrong_size() {
assert!(condition_number_linear_system(&[1.0, 2.0], 2).is_err());
}
#[test]
fn backward_error_exact_solution() {
let a = vec![1.0_f64, 0.0, 0.0, 1.0];
let b = vec![3.0_f64, 4.0];
let x = vec![3.0_f64, 4.0];
let eta = backward_error_bound(&a, 2, &b, &x).expect("valid");
assert!(eta < 1e-14, "eta={eta}");
}
#[test]
fn significant_digits_exact() {
assert_eq!(significant_digits(0.0), 15);
}
#[test]
fn significant_digits_total_loss() {
assert_eq!(significant_digits(1.5), 0);
}
#[test]
fn significant_digits_middle() {
assert_eq!(significant_digits(1e-6), 6);
}
#[test]
fn cancellation_check_identical() {
let bits = catastrophic_cancellation_check(1.0, 1.0);
assert_eq!(bits, 52);
}
#[test]
fn cancellation_check_none() {
let bits = catastrophic_cancellation_check(2.0, 1.0);
assert_eq!(bits, 0);
}
#[test]
fn forward_error_bound_positive() {
let a = vec![1.0_f64, 0.0, 0.0, 1.0];
let fwd = forward_error_bound(&a, 2, 1e-10).expect("valid");
assert!(fwd >= 0.0);
assert!(fwd.is_finite() || fwd == f64::INFINITY);
}
}