use crate::basis::ColumnSource;
use crate::error::LuError;
use crate::factor::{Elim, LuOptions, idx32};
use crate::real::{Real, is_exact_zero, is_zero, lt};
use crate::solve::NO_ETA;
use crate::store::NIL;
use crate::update::BasisUpdate;
use crate::{Lu, LuFactor};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct SymbolicLu {
pub pivot_rows: Vec<u32>,
pub pivot_cols: Vec<u32>,
pub singletons: usize,
pub nnz_l: usize,
pub nnz_u: usize,
}
impl SymbolicLu {
#[inline]
pub fn m(&self) -> usize {
self.pivot_rows.len()
}
}
impl<T: Real, U: BasisUpdate<T>> Lu<T, U> {
pub(crate) fn refactorize_impl<C: ColumnSource<T>>(&mut self, cols: &C) -> Result<(), LuError> {
let m = cols.m();
if self.symbolic.as_ref().is_some_and(|s| s.m() == m) {
self.factors.valid = false;
self.factors.spike_valid = false;
cols.validate()?;
let sym = self.symbolic.take().expect("checked above");
let prev = self.factors.stats;
let rel_tol = self.refactor_pivot_tol.unwrap_or(self.opts.rel_pivot_tol);
let replay = eliminate_fixed(cols, &sym, &self.opts, rel_tol);
self.symbolic = Some(sym);
if let Some(mut e) = replay {
e.col_singletons = prev.col_singletons;
e.row_singletons = prev.row_singletons;
e.markowitz_pivots = prev.markowitz_pivots;
self.factors.m = m;
let done = self.finish(e);
debug_assert!(done.is_ok(), "Algorithm R never reports dead columns");
self.factors.stats.symbolic_reuses += 1;
return done;
}
}
self.factors.stats.symbolic_fallbacks += 1;
self.factorize(cols)
}
}
#[allow(clippy::too_many_lines)] fn eliminate_fixed<T: Real, C: ColumnSource<T>>(
cols: &C,
sym: &SymbolicLu,
opts: &LuOptions,
rel_tol: f64,
) -> Option<Elim<T>> {
let m = cols.m();
let abs_tol = T::from_f64(opts.abs_pivot_tol);
let rel_tol = T::from_f64(rel_tol);
let drop_tol = T::from_f64(opts.drop_tol);
let mut e = Elim {
nnz_basis: 0,
max_basis: T::ZERO,
col_singletons: 0,
row_singletons: 0,
markowitz_pivots: 0,
step_row: sym.pivot_rows.clone(),
step_col: sym.pivot_cols.clone(),
diag: vec![T::ZERO; m],
l_start: vec![0],
l_pivot: Vec::new(),
l_index: Vec::new(),
l_value: Vec::new(),
u_trip: Vec::with_capacity(sym.nnz_u.saturating_sub(m)),
dead: Vec::new(),
};
e.l_index.reserve(sym.nnz_l);
e.l_value.reserve(sym.nnz_l);
let mut row_step = vec![NIL; m];
let mut eta_of_row = vec![NO_ETA; m];
let mut x = vec![T::ZERO; m];
let mut in_pat = vec![NIL; m];
let mut done = vec![NIL; m];
let mut pat: Vec<u32> = Vec::new();
let mut post: Vec<u32> = Vec::new();
let mut stack: Vec<(u32, usize)> = Vec::new();
for s in 0..m {
let (p, q) = (sym.pivot_rows[s] as usize, sym.pivot_cols[s] as usize);
if p >= m || q >= m || row_step[p] != NIL {
return None;
}
pat.clear();
cols.for_each_entry(q, |i, v| {
let iu = i as usize;
if in_pat[iu] != s {
in_pat[iu] = s;
pat.push(i);
}
x[iu] += v;
e.nnz_basis += 1;
e.max_basis = e.max_basis.max(v.abs());
});
post.clear();
let eta_start = |r: usize, eta_of_row: &[u32], l_start: &[usize]| match eta_of_row[r] {
NO_ETA => 0,
t => l_start[t as usize],
};
for t in 0..pat.len() {
let r0 = pat[t] as usize;
if done[r0] == s {
continue;
}
done[r0] = s;
stack.push((idx32(r0), eta_start(r0, &eta_of_row, &e.l_start)));
while let Some(top) = stack.last_mut() {
let r = top.0 as usize;
let eta = eta_of_row[r];
if eta != NO_ETA && top.1 < e.l_start[eta as usize + 1] {
let k = e.l_index[top.1];
top.1 += 1;
let ku = k as usize;
if done[ku] != s {
done[ku] = s;
if in_pat[ku] != s {
in_pat[ku] = s;
pat.push(k);
}
stack.push((k, eta_start(ku, &eta_of_row, &e.l_start)));
}
} else {
stack.pop();
if eta != NO_ETA {
post.push(idx32(r));
}
}
}
}
for &r in post.iter().rev() {
let r = r as usize;
let xr = x[r];
if is_exact_zero(xr) {
continue;
}
let t = eta_of_row[r] as usize;
for k in e.l_start[t]..e.l_start[t + 1] {
let i = e.l_index[k] as usize;
x[i] -= e.l_value[k] * xr;
}
}
let xp = x[p];
let gamma = pat
.iter()
.filter(|&&i| row_step[i as usize] == NIL)
.fold(T::ZERO, |g, &i| g.max(x[i as usize].abs()));
let refused =
!lt(abs_tol, xp.abs()) || (s >= sym.singletons && lt(xp.abs(), rel_tol * gamma));
if refused {
return None;
}
let q32 = idx32(q);
e.diag[p] = xp;
let mut any = false;
for &i in &pat {
let iu = i as usize;
let v = x[iu];
x[iu] = T::ZERO;
if is_zero(v, drop_tol) || iu == p {
continue;
}
if row_step[iu] == NIL {
e.l_index.push(i);
e.l_value.push(v / xp);
any = true;
} else {
e.u_trip.push((i, q32, v));
}
}
if any {
eta_of_row[p] = idx32(e.l_pivot.len());
e.l_pivot.push(idx32(p));
e.l_start.push(e.l_index.len());
}
row_step[p] = s;
}
Some(e)
}