use crate::dense::matrix::SymmetricMatrix;
use crate::scaling::ScalingInfo;
use crate::sparse::csc::CscMatrix;
pub fn compute_infnorm(matrix: &CscMatrix) -> (Vec<f64>, ScalingInfo) {
let n = matrix.n;
if n == 0 {
return (Vec::new(), ScalingInfo::Applied);
}
let mut d = vec![1.0f64; n];
let max_iter = 10;
let tol = 1e-8;
let mut row_max = vec![0.0f64; n];
for _ in 0..max_iter {
for r in row_max.iter_mut() {
*r = 0.0;
}
for j in 0..n {
let col_start = matrix.col_ptr[j];
let col_end = matrix.col_ptr[j + 1];
let dj = d[j];
let mut col_max = row_max[j];
for k in col_start..col_end {
let i = matrix.row_idx[k];
let v = (d[i] * matrix.values[k] * dj).abs();
if i != j && v > row_max[i] {
row_max[i] = v;
}
if v > col_max {
col_max = v;
}
}
row_max[j] = col_max;
}
let mut max_dev = 0.0f64;
for i in 0..n {
let m = row_max[i];
if m > 0.0 {
d[i] /= m.sqrt();
let dev = (m - 1.0).abs();
if dev > max_dev {
max_dev = dev;
}
}
}
if max_dev < tol {
break;
}
}
(d, ScalingInfo::Applied)
}
pub fn compute_infnorm_dense(sym: &SymmetricMatrix) -> (Vec<f64>, ScalingInfo) {
let n = sym.n;
if n == 0 {
return (Vec::new(), ScalingInfo::Applied);
}
let mut d = vec![1.0f64; n];
let max_iter = 10;
let tol = 1e-8;
let mut row_max = vec![0.0f64; n];
for _ in 0..max_iter {
for r in row_max.iter_mut() {
*r = 0.0;
}
for j in 0..n {
let col = j * n;
let dj = d[j];
let v_diag = (dj * sym.data[col + j] * dj).abs();
let mut col_max = row_max[j].max(v_diag);
if j + 1 < n {
let d_off = &d[j + 1..n];
let data_off = &sym.data[col + j + 1..col + n];
let (_lhs, rm_rhs) = row_max.split_at_mut(j + 1);
let off_max = scan_offdiag_simd(d_off, data_off, dj, rm_rhs);
if off_max > col_max {
col_max = off_max;
}
}
row_max[j] = col_max;
}
let mut max_dev = 0.0f64;
for i in 0..n {
let m = row_max[i];
if m > 0.0 {
d[i] /= m.sqrt();
let dev = (m - 1.0).abs();
if dev > max_dev {
max_dev = dev;
}
}
}
if max_dev < tol {
break;
}
}
(d, ScalingInfo::Applied)
}
fn scan_offdiag_simd(d_off: &[f64], data_off: &[f64], dj: f64, row_max_off: &mut [f64]) -> f64 {
assert_eq!(d_off.len(), data_off.len());
assert_eq!(d_off.len(), row_max_off.len());
if d_off.is_empty() {
return 0.0;
}
struct K<'a> {
dj: f64,
d_off: &'a [f64],
data_off: &'a [f64],
row_max_off: &'a mut [f64],
}
impl pulp::WithSimd for K<'_> {
type Output = f64;
#[inline(always)]
fn with_simd<S: pulp::Simd>(self, simd: S) -> f64 {
let Self {
dj,
d_off,
data_off,
row_max_off,
} = self;
let dj_v = simd.splat_f64s(dj);
let mut col_max_v = simd.splat_f64s(0.0);
let (d_body, d_tail) = S::as_simd_f64s(d_off);
let (da_body, da_tail) = S::as_simd_f64s(data_off);
let (rm_body, rm_tail) = S::as_mut_simd_f64s(row_max_off);
for ((dv, dav), rmv) in d_body.iter().zip(da_body).zip(rm_body.iter_mut()) {
let prod = simd.mul_f64s(simd.mul_f64s(*dv, *dav), dj_v);
let v = simd.abs_f64s(prod);
*rmv = simd.max_f64s(*rmv, v);
col_max_v = simd.max_f64s(col_max_v, v);
}
let mut col_max = simd.reduce_max_f64s(col_max_v);
if !d_tail.is_empty() {
let dv = simd.partial_load_f64s(d_tail);
let dav = simd.partial_load_f64s(da_tail);
let rmv = simd.partial_load_f64s(rm_tail);
let prod = simd.mul_f64s(simd.mul_f64s(dv, dav), dj_v);
let v = simd.abs_f64s(prod);
let new_rm = simd.max_f64s(rmv, v);
simd.partial_store_f64s(rm_tail, new_rm);
let tail_max = simd.reduce_max_f64s(v);
if tail_max > col_max {
col_max = tail_max;
}
}
col_max
}
}
pulp::Arch::new().dispatch(K {
dj,
d_off,
data_off,
row_max_off,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sparse::csc::CscMatrix;
#[test]
fn diag_3x3() {
let m = CscMatrix::from_triplets(3, &[0, 1, 2], &[0, 1, 2], &[2.0, 3.0, 5.0]).unwrap();
let (d, _info) = compute_infnorm(&m);
let expected = [1.0 / 2f64.sqrt(), 1.0 / 3f64.sqrt(), 1.0 / 5f64.sqrt()];
for i in 0..3 {
assert!(
(d[i] - expected[i]).abs() < 1e-12,
"d[{}] = {} != {}",
i,
d[i],
expected[i]
);
}
}
#[test]
fn sym_2x2() {
let m = CscMatrix::from_triplets(2, &[0, 1, 1], &[0, 0, 1], &[4.0, 2.0, 9.0]).unwrap();
let (d, _) = compute_infnorm(&m);
let a00 = d[0] * d[0] * 4.0;
let a01 = d[0] * d[1] * 2.0;
let a11 = d[1] * d[1] * 9.0;
let row0 = a00.abs().max(a01.abs());
let row1 = a01.abs().max(a11.abs());
assert!((row0 - 1.0).abs() < 1e-6, "row0 max = {}", row0);
assert!((row1 - 1.0).abs() < 1e-6, "row1 max = {}", row1);
}
#[test]
fn dense_matches_sparse_on_arrow_6x6() {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for j in 0..6 {
rows.push(j);
cols.push(j);
vals.push((j + 2) as f64);
}
for j in 0..5 {
rows.push(5);
cols.push(j);
vals.push(1.0);
}
let m = CscMatrix::from_triplets(6, &rows, &cols, &vals).unwrap();
let sym = m.to_dense();
let (d_sparse, _) = compute_infnorm(&m);
let (d_dense, _) = compute_infnorm_dense(&sym);
assert_eq!(d_sparse.len(), d_dense.len());
for i in 0..d_sparse.len() {
assert_eq!(
d_sparse[i].to_bits(),
d_dense[i].to_bits(),
"dense-vs-sparse KR parity broke at i={}: sparse={} dense={}",
i,
d_sparse[i],
d_dense[i],
);
}
}
#[test]
fn dense_matches_sparse_on_dense_5x5() {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for j in 0..5 {
for i in j..5 {
rows.push(i);
cols.push(j);
vals.push(if i == j {
10.0 * (i as f64 + 1.0)
} else {
1.0 + 0.1 * (i - j) as f64
});
}
}
let m = CscMatrix::from_triplets(5, &rows, &cols, &vals).unwrap();
let sym = m.to_dense();
let (d_sparse, _) = compute_infnorm(&m);
let (d_dense, _) = compute_infnorm_dense(&sym);
for i in 0..5 {
assert_eq!(
d_sparse[i].to_bits(),
d_dense[i].to_bits(),
"dense KR diverged at i={}: sparse={} dense={}",
i,
d_sparse[i],
d_dense[i],
);
}
}
#[test]
fn arrow_6x6() {
let mut rows = Vec::new();
let mut cols = Vec::new();
let mut vals = Vec::new();
for j in 0..6 {
rows.push(j);
cols.push(j);
vals.push((j + 2) as f64);
}
for j in 0..5 {
rows.push(5);
cols.push(j);
vals.push(1.0);
}
let m = CscMatrix::from_triplets(6, &rows, &cols, &vals).unwrap();
let (d, _) = compute_infnorm(&m);
for i in 0..6 {
let mut row_max = 0.0f64;
for j in 0..6 {
let (ii, jj) = if i >= j { (i, j) } else { (j, i) };
let mut v = 0.0;
for k in m.col_ptr[jj]..m.col_ptr[jj + 1] {
if m.row_idx[k] == ii {
v = m.values[k];
break;
}
}
let scaled = (d[i] * v * d[j]).abs();
if scaled > row_max {
row_max = scaled;
}
}
assert!(
(row_max - 1.0).abs() < 1e-6,
"row {} max = {}, expected 1",
i,
row_max
);
}
}
}