use super::factorize::SparseFactors;
use super::solve::solve_sparse;
use crate::error::FeralError;
use crate::sparse::csc::CscMatrix;
const MAX_ITER: usize = 5;
pub fn matrix_norm_1(matrix: &CscMatrix) -> f64 {
if matrix.n == 0 {
return 0.0;
}
let mut col_sums = vec![0.0f64; matrix.n];
for j in 0..matrix.n {
for k in matrix.col_ptr[j]..matrix.col_ptr[j + 1] {
let i = matrix.row_idx[k];
let a_abs = matrix.values[k].abs();
col_sums[j] += a_abs;
if i != j {
col_sums[i] += a_abs;
}
}
}
col_sums.into_iter().fold(0.0f64, f64::max)
}
pub fn estimate_inverse_norm_1(factors: &SparseFactors) -> Result<f64, FeralError> {
let n = factors.n;
if n == 0 {
return Ok(0.0);
}
let mut x = vec![1.0 / (n as f64); n];
let mut est_old = 0.0f64;
let mut est = 0.0f64;
let mut prev_j: Option<usize> = None;
for _iter in 0..MAX_ITER {
let y = solve_sparse(factors, &x)?;
est = y.iter().map(|v| v.abs()).sum();
if _iter > 0 && est <= est_old {
est = est_old;
break;
}
let xi: Vec<f64> = y
.iter()
.map(|&v| if v >= 0.0 { 1.0 } else { -1.0 })
.collect();
let z = solve_sparse(factors, &xi)?;
let zx: f64 = z.iter().zip(x.iter()).map(|(zi, xi)| zi * xi).sum();
let z_inf = z.iter().map(|v| v.abs()).fold(0.0f64, f64::max);
if z_inf <= zx {
break;
}
let mut j = 0usize;
let mut zmax = 0.0f64;
for (i, &zi) in z.iter().enumerate() {
let zabs = zi.abs();
if zabs > zmax {
zmax = zabs;
j = i;
}
}
if Some(j) == prev_j {
break;
}
prev_j = Some(j);
for slot in x.iter_mut() {
*slot = 0.0;
}
x[j] = 1.0;
est_old = est;
}
let mut b = vec![0.0f64; n];
if n == 1 {
b[0] = 1.0;
} else {
let denom = (n - 1) as f64;
for (i, slot) in b.iter_mut().enumerate() {
let mag = 1.0 + (i as f64) / denom;
let sign = if i % 2 == 0 { 1.0 } else { -1.0 };
*slot = sign * mag;
}
}
let yb = solve_sparse(factors, &b)?;
let yb_l1: f64 = yb.iter().map(|v| v.abs()).sum();
let refined = 2.0 * yb_l1 / (3.0 * n as f64);
if refined > est {
est = refined;
}
Ok(est)
}
pub fn estimate_condition_1norm(
matrix: &CscMatrix,
factors: &SparseFactors,
) -> Result<f64, FeralError> {
if matrix.n != factors.n {
return Err(FeralError::DimensionMismatch {
expected: factors.n,
got: matrix.n,
});
}
if matrix.n == 0 {
return Ok(0.0);
}
let anorm = matrix_norm_1(matrix);
let inv_norm = estimate_inverse_norm_1(factors)?;
Ok(anorm * inv_norm)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::numeric::factorize::factorize_multifrontal;
use crate::symbolic::{symbolic_factorize, SupernodeParams};
fn factor(m: &CscMatrix) -> SparseFactors {
let sym = symbolic_factorize(m, &SupernodeParams::default()).unwrap();
let (factors, _) = factorize_multifrontal(
m,
&sym,
&crate::numeric::factorize::NumericParams::default(),
)
.unwrap();
factors
}
fn diag(values: &[f64]) -> CscMatrix {
let n = values.len();
let rows: Vec<usize> = (0..n).collect();
let cols: Vec<usize> = (0..n).collect();
CscMatrix::from_triplets(n, &rows, &cols, values).unwrap()
}
#[test]
fn matrix_norm_1_diagonal() {
let m = diag(&[1.0, -3.0, 2.0]);
assert!((matrix_norm_1(&m) - 3.0).abs() < 1e-15);
}
#[test]
fn matrix_norm_1_symmetric_off_diagonal() {
let m = CscMatrix::from_triplets(2, &[0, 1, 1], &[0, 0, 1], &[1.0, 2.0, 5.0]).unwrap();
assert!((matrix_norm_1(&m) - 7.0).abs() < 1e-15);
}
#[test]
fn condition_diagonal_spectrum() {
let m = diag(&[1.0, 1e3, 1e6]);
let factors = factor(&m);
let kappa = estimate_condition_1norm(&m, &factors).unwrap();
assert!(
(0.5e6..=2.0e6).contains(&kappa),
"diagonal kappa {} not within 2x of 1e6",
kappa
);
}
#[test]
fn condition_well_conditioned_lower_bound() {
let m = diag(&[1.0; 5]);
let factors = factor(&m);
let kappa = estimate_condition_1norm(&m, &factors).unwrap();
assert!(kappa >= 1.0 - 1e-8, "kappa {} below 1 for identity", kappa);
assert!(
kappa <= 2.0,
"kappa {} unexpectedly large for identity",
kappa
);
}
#[test]
fn condition_n_zero() {
let m = CscMatrix::from_triplets(0, &[], &[], &[]).unwrap();
let factors = factor(&m);
let kappa = estimate_condition_1norm(&m, &factors).unwrap();
assert_eq!(kappa, 0.0);
}
#[test]
fn condition_dimension_mismatch_rejected() {
let m1 = diag(&[1.0, 2.0]);
let m2 = diag(&[1.0, 2.0, 3.0]);
let factors = factor(&m1);
let r = estimate_condition_1norm(&m2, &factors);
assert!(r.is_err());
}
fn hilbert(n: usize) -> CscMatrix {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for j in 0..n {
for i in j..n {
rows.push(i);
cols.push(j);
vals.push(1.0 / ((i + j + 1) as f64));
}
}
CscMatrix::from_triplets(n, &rows, &cols, &vals).unwrap()
}
#[test]
fn condition_hilbert_n4() {
let m = hilbert(4);
let factors = factor(&m);
let kappa = estimate_condition_1norm(&m, &factors).unwrap();
let true_kappa = 2.84e4;
assert!(
kappa >= true_kappa / 10.0,
"H_4 kappa {} below 10% of true {}",
kappa,
true_kappa
);
assert!(
kappa <= 10.0 * true_kappa,
"H_4 kappa {} above 10x true {}",
kappa,
true_kappa
);
}
#[test]
fn condition_hilbert_n6() {
let m = hilbert(6);
let factors = factor(&m);
let kappa = estimate_condition_1norm(&m, &factors).unwrap();
let true_kappa = 2.91e7;
assert!(
kappa >= true_kappa / 10.0,
"H_6 kappa {} below 10% of true {}",
kappa,
true_kappa
);
assert!(
kappa <= 10.0 * true_kappa,
"H_6 kappa {} above 10x true {}",
kappa,
true_kappa
);
}
}