use super::analyze::Symbolic;
use super::factor::Numeric;
use super::kernel::refactor_block_in_place;
use super::types::flip;
pub struct RefactorScratch {
x: Vec<f64>,
columns: Vec<Vec<(i64, f64)>>,
}
impl RefactorScratch {
pub fn new(n: usize) -> Self {
Self { x: vec![0.0; n], columns: vec![Vec::new(); n] }
}
}
pub fn refactor(
n: usize,
col_ptr: &[i64],
row_idx: &[i64],
values: &[f64],
sym: &Symbolic,
num: &mut Numeric,
scratch: &mut RefactorScratch,
) -> bool {
let nblocks = sym.nblocks();
let mut off_p = vec![0i64; n + 1];
let mut off_i: Vec<i64> = Vec::new();
let mut off_x: Vec<f64> = Vec::new();
let mut poff: i64 = 0;
for block in 0..nblocks {
let k1 = sym.r[block];
let k2 = sym.r[block + 1];
let nk = k2 - k1;
for k in 0..nk {
let oldcol = sym.q[k + k1];
let entries = &mut scratch.columns[k + k1];
entries.clear();
for p in col_ptr[oldcol] as usize..col_ptr[oldcol + 1] as usize {
let oldrow = row_idx[p];
let newrow = num.pinv[oldrow as usize];
let value = values[p];
if (newrow as usize) < k1 {
entries.push((flip(oldrow), value));
} else {
entries.push((newrow - k1 as i64, value));
}
}
}
let mut off_diag_out: Vec<Vec<(i64, f64)>> = vec![Vec::new(); nk];
let ok = refactor_block_in_place(
nk,
&mut num.blocks[block],
&mut scratch.x[..nk],
&scratch.columns[k1..k2],
&mut off_diag_out,
);
if !ok {
return false;
}
for (k, entries) in off_diag_out.into_iter().enumerate() {
let global_col = k + k1;
for (oldrow, value) in entries {
off_i.push(oldrow);
off_x.push(value);
poff += 1;
}
off_p[global_col + 1] = poff;
}
}
for i in off_i.iter_mut() {
*i = num.pinv[*i as usize];
}
num.off_p = off_p;
num.off_i = off_i;
num.off_x = off_x;
true
}
#[cfg(test)]
mod tests {
use super::super::analyze::analyze;
use super::super::factor::factor;
use super::super::solve::solve;
use super::*;
fn dense_solve(a: &[Vec<f64>], b: &[f64]) -> Vec<f64> {
let n = a.len();
let mut a: Vec<Vec<f64>> = a.to_vec();
let mut rhs = b.to_vec();
for col in 0..n {
let pivot_row = (col..n).max_by(|&r1, &r2| a[r1][col].abs().total_cmp(&a[r2][col].abs())).unwrap();
a.swap(col, pivot_row);
rhs.swap(col, pivot_row);
for row in (col + 1)..n {
let factor = a[row][col] / a[col][col];
#[allow(clippy::needless_range_loop)]
for k in col..n {
a[row][k] -= factor * a[col][k];
}
rhs[row] -= factor * rhs[col];
}
}
let mut x = vec![0.0; n];
for row in (0..n).rev() {
let mut sum = rhs[row];
for k in (row + 1)..n {
sum -= a[row][k] * x[k];
}
x[row] = sum / a[row][row];
}
x
}
fn to_csc(n: usize, entries: &[(usize, usize, f64)]) -> (Vec<i64>, Vec<i64>, Vec<f64>) {
let mut by_col: Vec<Vec<(i64, f64)>> = vec![Vec::new(); n];
for &(r, c, v) in entries {
by_col[c].push((r as i64, v));
}
for col in by_col.iter_mut() {
col.sort_by_key(|&(r, _)| r);
}
let mut col_ptr = vec![0i64; n + 1];
let mut row_idx = Vec::new();
let mut values = Vec::new();
for (c, col) in by_col.iter().enumerate() {
for &(r, v) in col {
row_idx.push(r);
values.push(v);
}
col_ptr[c + 1] = row_idx.len() as i64;
}
(col_ptr, row_idx, values)
}
fn dense_from_entries(n: usize, entries: &[(usize, usize, f64)]) -> Vec<Vec<f64>> {
let mut a = vec![vec![0.0; n]; n];
for &(r, c, v) in entries {
a[r][c] += v;
}
a
}
#[test]
fn multi_block_refactor_with_new_values_matches_dense() {
let n = 5;
let original = vec![
(0, 0, 4.0),
(0, 1, 1.0),
(1, 0, 1.0),
(1, 1, 5.0),
(2, 2, 3.0),
(2, 0, 0.5),
(3, 3, 6.0),
(3, 4, 1.0),
(4, 3, 1.0),
(4, 4, 7.0),
(3, 0, 0.2),
(4, 2, 0.3),
];
let (col_ptr, row_idx, values) = to_csc(n, &original);
let sym = analyze(n, &col_ptr, &row_idx);
assert!(sym.nblocks() >= 2, "fixture should produce multiple BTF blocks, got {}", sym.nblocks());
let mut num = factor(n, &col_ptr, &row_idx, &values, &sym, 1e-3).unwrap();
let updated = vec![
(0, 0, 5.5),
(0, 1, 0.6),
(1, 0, 0.4),
(1, 1, 4.2),
(2, 2, 2.1),
(2, 0, 0.9),
(3, 3, 4.4),
(3, 4, 0.5),
(4, 3, 0.7),
(4, 4, 5.6),
(3, 0, 0.35),
(4, 2, 0.15),
];
let (col_ptr2, row_idx2, values2) = to_csc(n, &updated);
assert_eq!(col_ptr, col_ptr2, "fixture must keep the same sparsity pattern");
assert_eq!(row_idx, row_idx2, "fixture must keep the same sparsity pattern");
let mut scratch = RefactorScratch::new(n);
assert!(refactor(n, &col_ptr2, &row_idx2, &values2, &sym, &mut num, &mut scratch));
let b = vec![1.0, -2.0, 0.5, 3.0, -1.0];
let x = solve(&sym, &num, None, &b);
let expected = dense_solve(&dense_from_entries(n, &updated), &b);
for i in 0..n {
assert!((x[i] - expected[i]).abs() < 1e-8, "index {i}: {} vs {}", x[i], expected[i]);
}
}
#[test]
fn refactor_reused_repeatedly_matches_dense_each_time() {
let n = 4;
let base = vec![(0, 0, 6.0), (1, 1, 7.0), (2, 2, 8.0), (3, 3, 5.0), (0, 1, -0.9), (1, 0, -0.5), (2, 3, 0.4), (3, 2, -0.2)];
let (col_ptr, row_idx, values) = to_csc(n, &base);
let sym = analyze(n, &col_ptr, &row_idx);
let mut num = factor(n, &col_ptr, &row_idx, &values, &sym, 1e-3).unwrap();
let mut scratch = RefactorScratch::new(n);
for trial in 0..5 {
let scale = 1.0 + trial as f64 * 0.3;
let updated: Vec<(usize, usize, f64)> = base.iter().map(|&(r, c, v)| (r, c, v * scale)).collect();
let (cp, ri, vals) = to_csc(n, &updated);
assert!(refactor(n, &cp, &ri, &vals, &sym, &mut num, &mut scratch), "trial {trial}");
let b = vec![1.0, 2.0, -1.0, 0.5];
let x = solve(&sym, &num, None, &b);
let expected = dense_solve(&dense_from_entries(n, &updated), &b);
for i in 0..n {
assert!((x[i] - expected[i]).abs() < 1e-8, "trial {trial}, index {i}: {} vs {}", x[i], expected[i]);
}
}
}
#[cfg(feature = "klu")]
#[test]
fn matches_real_klu_refactor_on_random_matrices() {
let mut seed: u64 = 0x7F4A7C15E3B0C9D2;
let mut next_f64 = move || {
seed ^= seed << 13;
seed ^= seed >> 7;
seed ^= seed << 17;
(seed >> 11) as f64 / (1u64 << 53) as f64
};
for trial in 0..40 {
let n = 4 + (trial % 14);
let pattern: Vec<(usize, usize)> = {
let mut pairs = std::collections::BTreeSet::new();
for i in 0..n {
pairs.insert((i, i));
let degree = 1 + (next_f64() * 3.0) as usize;
for _ in 0..degree {
let j = (next_f64() * n as f64) as usize;
if j != i {
pairs.insert((i, j));
}
}
}
pairs.into_iter().collect()
};
let make_values = |next_f64: &mut dyn FnMut() -> f64| -> Vec<(usize, usize, f64)> {
let mut row_sums = vec![0.0f64; n];
let mut off_entries: Vec<(usize, usize, f64)> = Vec::new();
for &(i, j) in &pattern {
if i != j {
let v = next_f64() * 2.0 - 1.0;
off_entries.push((i, j, v));
row_sums[i] += v.abs();
}
}
let mut entries: Vec<(usize, usize, f64)> =
row_sums.iter().enumerate().map(|(i, &rs)| (i, i, rs + 1.0 + next_f64())).collect();
entries.extend(off_entries);
entries
};
let entries1 = make_values(&mut next_f64);
let (col_ptr, row_idx, values) = to_csc(n, &entries1);
let sym = analyze(n, &col_ptr, &row_idx);
let mut num = factor(n, &col_ptr, &row_idx, &values, &sym, 1e-3)
.unwrap_or_else(|| panic!("trial {trial} (n={n}): unexpectedly singular (factor)"));
let mut scratch = RefactorScratch::new(n);
let entries2 = make_values(&mut next_f64);
let (col_ptr2, row_idx2, values2) = to_csc(n, &entries2);
assert!(
refactor(n, &col_ptr2, &row_idx2, &values2, &sym, &mut num, &mut scratch),
"trial {trial} (n={n}): unexpectedly singular (refactor)"
);
let b: Vec<f64> = (0..n).map(|i| 1.0 + i as f64 * 0.3).collect();
let rust_x = solve(&sym, &num, None, &b);
let mut klu_sys = crate::sparse_klu::KluRealSystem::new(n, &entries1).unwrap();
let _ = klu_sys.factor_and_solve(&entries1, &b).unwrap();
let klu_x = klu_sys.factor_and_solve(&entries2, &b).unwrap();
for i in 0..n {
assert!(
(rust_x[i] - klu_x[i]).abs() < 1e-8,
"trial {trial} (n={n}), index {i}: rust={} klu={}",
rust_x[i],
klu_x[i]
);
}
}
}
}