use crate::sparse::csc::CscMatrix;
const GROWTH_FACTOR: f64 = 2.0;
const GROWTH_COUNT: f64 = 1.5;
const EPS_DIAG: f64 = 1e-12;
#[derive(Debug, Clone, Copy)]
struct DominanceStats {
max_ratio: f64,
n_off_dominant: usize,
min_diag: f64,
mean_diag: f64,
}
#[derive(Debug, Clone)]
pub(crate) struct Mc64CacheValidity {
qualifying: Vec<bool>,
r0: f64,
n_off_dominant_0: usize,
mean_diag_0: f64,
}
fn scaled_dominance_stats(
matrix: &CscMatrix,
scaling: &[f64],
qualifying: &[bool],
) -> DominanceStats {
let n = matrix.n;
let mut diag = vec![0.0_f64; n];
let mut off_max = vec![0.0_f64; n];
for j in 0..n {
for k in matrix.col_ptr[j]..matrix.col_ptr[j + 1] {
let i = matrix.row_idx[k];
let v = (matrix.values[k] * scaling[i] * scaling[j]).abs();
if i == j {
diag[j] = v;
} else {
if v > off_max[i] {
off_max[i] = v;
}
if v > off_max[j] {
off_max[j] = v;
}
}
}
}
let mut max_ratio = 0.0_f64;
let mut n_off_dominant = 0usize;
let mut min_diag = f64::INFINITY;
let mut sum_diag = 0.0_f64;
let mut count = 0usize;
for j in 0..n {
if !qualifying[j] {
continue;
}
let d = diag[j];
let ratio = if d > 0.0 {
off_max[j] / d
} else if off_max[j] > 0.0 {
f64::INFINITY
} else {
0.0
};
if ratio > max_ratio {
max_ratio = ratio;
}
if ratio > 1.0 {
n_off_dominant += 1;
}
if d < min_diag {
min_diag = d;
}
sum_diag += d;
count += 1;
}
let (min_diag, mean_diag) = if count > 0 {
(min_diag, sum_diag / count as f64)
} else {
(0.0, 0.0)
};
DominanceStats {
max_ratio,
n_off_dominant,
min_diag,
mean_diag,
}
}
fn qualifying_rows(matrix: &CscMatrix) -> Vec<bool> {
let n = matrix.n;
let mut q = vec![false; n];
for (j, qj) in q.iter_mut().enumerate() {
for k in matrix.col_ptr[j]..matrix.col_ptr[j + 1] {
if matrix.row_idx[k] == j {
*qj = matrix.values[k] != 0.0;
break;
}
}
}
q
}
pub(crate) fn precompute_mc64_validity(matrix: &CscMatrix, scaling: &[f64]) -> Mc64CacheValidity {
let qualifying = qualifying_rows(matrix);
let stats = if scaling.len() == matrix.n {
scaled_dominance_stats(matrix, scaling, &qualifying)
} else {
DominanceStats {
max_ratio: 0.0,
n_off_dominant: 0,
min_diag: 0.0,
mean_diag: 0.0,
}
};
Mc64CacheValidity {
qualifying,
r0: stats.max_ratio.max(1.0),
n_off_dominant_0: stats.n_off_dominant,
mean_diag_0: stats.mean_diag,
}
}
pub(crate) fn mc64_value_bound_passes(
matrix: &CscMatrix,
scaling: &[f64],
validity: &Mc64CacheValidity,
) -> bool {
let n = matrix.n;
if scaling.len() != n || validity.qualifying.len() != n {
return false;
}
let stats = scaled_dominance_stats(matrix, scaling, &validity.qualifying);
let cond_ratio = stats.max_ratio <= GROWTH_FACTOR * validity.r0;
let cond_count =
(stats.n_off_dominant as f64) <= GROWTH_COUNT * (validity.n_off_dominant_0 as f64);
let cond_diag = stats.min_diag >= EPS_DIAG * validity.mean_diag_0;
cond_ratio && cond_count && cond_diag
}
#[cfg(test)]
mod tests {
use super::*;
fn matrix_3x3() -> CscMatrix {
CscMatrix::from_triplets(
3,
&[0, 1, 1, 2, 2],
&[0, 0, 1, 1, 2],
&[4.0, 2.0, 9.0, 1.0, 16.0],
)
.expect("valid CSC")
}
#[test]
fn dominance_stats_identity_scaling_hand_oracle() {
let m = matrix_3x3();
let s = [1.0, 1.0, 1.0];
let q = [true, true, true];
let st = scaled_dominance_stats(&m, &s, &q);
assert!(
(st.max_ratio - 0.5).abs() < 1e-15,
"max_ratio {}",
st.max_ratio
);
assert_eq!(st.n_off_dominant, 0);
assert!(
(st.min_diag - 4.0).abs() < 1e-15,
"min_diag {}",
st.min_diag
);
assert!(
(st.mean_diag - 29.0 / 3.0).abs() < 1e-13,
"mean_diag {}",
st.mean_diag
);
}
#[test]
fn dominance_stats_scaled_hand_oracle() {
let m = matrix_3x3();
let s = [2.0, 1.0, 0.5];
let q = [true, true, true];
let st = scaled_dominance_stats(&m, &s, &q);
assert!(
(st.max_ratio - 4.0 / 9.0).abs() < 1e-15,
"max_ratio {}",
st.max_ratio
);
assert_eq!(st.n_off_dominant, 0);
assert!(
(st.min_diag - 4.0).abs() < 1e-15,
"min_diag {}",
st.min_diag
);
assert!(
(st.mean_diag - 29.0 / 3.0).abs() < 1e-13,
"mean_diag {}",
st.mean_diag
);
}
#[test]
fn zero_diagonal_row_excluded_from_stats() {
let m = CscMatrix::from_triplets(3, &[0, 1, 1, 2], &[0, 0, 1, 1], &[4.0, 2.0, 9.0, 3.0])
.expect("valid CSC");
let q = qualifying_rows(&m);
assert_eq!(q, vec![true, true, false], "row 2 has no stored diagonal");
let s = [1.0, 1.0, 1.0];
let st = scaled_dominance_stats(&m, &s, &q);
assert!(st.max_ratio.is_finite(), "max_ratio must be finite");
assert!(
(st.max_ratio - 0.5).abs() < 1e-15,
"max_ratio {}",
st.max_ratio
);
assert_eq!(st.n_off_dominant, 0, "row 2 (ratio +inf) is excluded");
}
#[test]
fn explicit_zero_diagonal_disqualifies_row() {
let m = CscMatrix::from_triplets(2, &[0, 1, 1], &[0, 0, 1], &[5.0, 2.0, 0.0])
.expect("valid CSC");
let q = qualifying_rows(&m);
assert_eq!(q, vec![true, false], "row 1 diagonal is explicit zero");
}
#[test]
fn value_bound_passes_on_identical_matrix() {
let m = matrix_3x3();
let s = [1.0, 1.0, 1.0];
let v = precompute_mc64_validity(&m, &s);
assert!(
mc64_value_bound_passes(&m, &s, &v),
"identical matrix must pass the value bound"
);
}
#[test]
fn value_bound_rejects_on_ratio_growth() {
let m = CscMatrix::from_triplets(2, &[0, 1, 1], &[0, 0, 1], &[1.0, 5.0, 1.0])
.expect("valid CSC");
let s = [1.0, 1.0];
let v = Mc64CacheValidity {
qualifying: vec![true, true],
r0: 2.0,
n_off_dominant_0: 10,
mean_diag_0: 1.0,
};
assert!(
!mc64_value_bound_passes(&m, &s, &v),
"ratio 5 > GROWTH_FACTOR·r0 = 4 must reject"
);
}
#[test]
fn value_bound_rejects_on_count_growth() {
let m = CscMatrix::from_triplets(
3,
&[0, 1, 1, 2, 2],
&[0, 0, 1, 1, 2],
&[1.0, 1.2, 1.0, 1.2, 1.0],
)
.expect("valid CSC");
let s = [1.0, 1.0, 1.0];
let v = Mc64CacheValidity {
qualifying: vec![true, true, true],
r0: 2.0,
n_off_dominant_0: 1,
mean_diag_0: 1.0,
};
assert!(
!mc64_value_bound_passes(&m, &s, &v),
"3 off-dominant rows > GROWTH_COUNT·1 = 1.5 must reject"
);
}
#[test]
fn value_bound_rejects_on_diagonal_collapse() {
let m = CscMatrix::from_triplets(2, &[0, 1], &[0, 1], &[1e-20, 1.0]).expect("valid CSC");
let s = [1.0, 1.0];
let v = Mc64CacheValidity {
qualifying: vec![true, true],
r0: 1.0,
n_off_dominant_0: 0,
mean_diag_0: 1.0,
};
assert!(
!mc64_value_bound_passes(&m, &s, &v),
"collapsed scaled diagonal 1e-20 < 1e-12 must reject"
);
}
#[test]
fn value_bound_passes_within_budget() {
let m = CscMatrix::from_triplets(2, &[0, 1, 1], &[0, 0, 1], &[1.0, 5.0, 1.0])
.expect("valid CSC");
let s = [1.0, 1.0];
let v = Mc64CacheValidity {
qualifying: vec![true, true],
r0: 3.0,
n_off_dominant_0: 10,
mean_diag_0: 1.0,
};
assert!(
mc64_value_bound_passes(&m, &s, &v),
"ratio 5 <= GROWTH_FACTOR·r0 = 6 must pass"
);
}
#[test]
fn value_bound_rejects_on_length_mismatch() {
let m = matrix_3x3();
let v = precompute_mc64_validity(&m, &[1.0, 1.0, 1.0]);
assert!(
!mc64_value_bound_passes(&m, &[1.0, 1.0], &v),
"scaling length 2 != n 3 must reject"
);
}
}