use xingo_sparse::{
Col, ColumnRef, CscMatrix, Lu, LuFactor, Row, Sign, SparseVec, Triplets, UpdateOutcome,
};
fn basis_times(heading: &[ColumnRef], a: &CscMatrix, v: &[f64]) -> Vec<f64> {
let mut y = vec![0.0; a.n_rows()];
for (k, c) in heading.iter().enumerate() {
c.for_each_entry(a, |i, b_ik| y[i as usize] += b_ik * v[k]);
}
y
}
fn basis_transpose_times(heading: &[ColumnRef], a: &CscMatrix, w: &[f64]) -> Vec<f64> {
heading
.iter()
.map(|c| {
let mut s = 0.0;
c.for_each_entry(a, |i, b_ik| s += b_ik * w[i as usize]);
s
})
.collect()
}
fn distance(x: &[f64], y: &[f64]) -> f64 {
x.iter()
.zip(y)
.map(|(p, q)| (p - q).abs())
.fold(0.0, f64::max)
}
fn main() {
let (m, n) = (4, 6);
let mut t = Triplets::new(m, n);
for (i, j, v) in [
(0, 0, 2.0),
(0, 1, 1.0),
(0, 3, 1.0),
(1, 1, 1.0),
(1, 2, 3.0),
(1, 4, -1.0),
(2, 0, 1.0),
(2, 2, 1.0),
(2, 5, 2.0),
(3, 3, 4.0),
(3, 4, 1.0),
(3, 5, 1.0),
] {
t.push(i, j, v);
}
let a: CscMatrix = t.to_csc().expect("valid triplets");
println!("A: {} × {}, {} nonzeros", a.n_rows(), a.n_cols(), a.nnz());
let mut heading = vec![
ColumnRef::Structural(Col(0)),
ColumnRef::Structural(Col(2)),
ColumnRef::Structural(Col(5)),
ColumnRef::Structural(Col(3)),
];
let mut lu = Lu::<f64>::default();
lu.factorize(&(heading.as_slice(), &a))
.expect("the basis is nonsingular");
let s = lu.stats();
println!(
"factorized B ({} nonzeros): nnz(L) = {}, nnz(U) = {}, singletons {} + {}, bump of {} pivot(s), growth {:.2}",
s.nnz_basis,
s.nnz_l,
s.nnz_u,
s.col_singletons,
s.row_singletons,
s.markowitz_pivots,
s.growth
);
let b = [1.0, 2.0, 3.0, 4.0];
let mut x = SparseVec::from_dense(&b);
lu.ftran(&mut x);
let res_ftran = distance(&basis_times(&heading, &a, x.values()), &b);
println!("FTRAN: x = {:?}, ‖B x − b‖∞ = {res_ftran:.1e}", x.values());
let c_b = [1.0, -1.0, 0.5, 2.0];
let mut y = SparseVec::from_dense(&c_b);
lu.btran(&mut y);
let res_btran = distance(&basis_transpose_times(&heading, &a, y.values()), &c_b);
println!(
"BTRAN: y = {:?}, ‖Bᵀ y − c_B‖∞ = {res_btran:.1e}",
y.values()
);
let r = 1;
let entering = ColumnRef::Logical(Row(1), Sign::Plus);
let mut alpha = SparseVec::new(m);
entering.for_each_entry(&a, |i, v| alpha.set(i, v));
lu.ftran_for_update(&mut alpha);
let alpha_r = alpha.get(u32::try_from(r).expect("position fits u32"));
let outcome = lu
.update(r, alpha_r)
.expect("a stable, nonsingular exchange");
heading[r] = entering;
let u = lu.update_stats();
println!(
"update at position {r} (α_r = {alpha_r:.4}): {outcome:?}; {} update(s), {} row eta(s) with {} multiplier(s)",
u.updates, u.n_etas, u.nnz_eta
);
assert_eq!(outcome, UpdateOutcome::Updated);
let mut x_updated = SparseVec::from_dense(&b);
lu.ftran(&mut x_updated);
let res_updated = distance(&basis_times(&heading, &a, x_updated.values()), &b);
println!(
"FTRAN after the update: x = {:?}, ‖B′ x − b‖∞ = {res_updated:.1e}",
x_updated.values()
);
lu.factorize(&(heading.as_slice(), &a))
.expect("the new basis is nonsingular");
let mut x_fresh = SparseVec::from_dense(&b);
lu.ftran(&mut x_fresh);
let res_fresh = distance(&basis_times(&heading, &a, x_fresh.values()), &b);
let gap = distance(x_updated.values(), x_fresh.values());
println!(
"refactorized: {} factorization(s) so far, ‖B′ x − b‖∞ = {res_fresh:.1e}, updated vs fresh ‖Δx‖∞ = {gap:.1e}",
lu.stats().factorizations
);
let tol = 1e-12;
assert!(
res_ftran <= tol && res_btran <= tol,
"solves with the factorized basis"
);
assert!(res_updated <= tol, "solve with the updated factor");
assert!(
res_fresh <= tol && gap <= tol,
"updated and fresh factors agree"
);
println!("ok");
}