use crate::basis::ColumnSource;
use crate::error::LuError;
use crate::real::{Real, approx_eq, is_exact_zero, is_zero, le_exact, lt};
use crate::solve::{Kind, NO_ETA, SolveStats, Workspace};
use crate::store::{CountLists, LineStore, NIL, check_links, remove_linked};
use crate::symbolic::SymbolicLu;
use crate::update::{BasisUpdate, ForrestTomlin, UpdateOutcome, UpdateStats};
use crate::{Colamd, Ordering, OrderingTarget, Pattern, Row, SparseError, SparseVec};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct LuOptions {
pub abs_pivot_tol: f64,
pub rel_pivot_tol: f64,
pub search_limit: usize,
pub growth_limit: f64,
pub fill_factor: f64,
pub refactor_interval: usize,
pub update_stability_tol: f64,
pub hyper_rhs_density: f64,
pub hyper_result_density: f64,
pub drop_tol: f64,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum PreOrder {
Colamd,
}
impl Default for LuOptions {
fn default() -> Self {
Self {
abs_pivot_tol: 1e-10,
rel_pivot_tol: 1e-2,
search_limit: 4,
growth_limit: 1e10,
fill_factor: 3.0,
refactor_interval: 100,
update_stability_tol: 1e-8,
hyper_rhs_density: 0.10,
hyper_result_density: 0.10,
drop_tol: 1e-12,
}
}
}
impl LuOptions {
fn is_valid(&self) -> bool {
let finite_nonneg = |x: f64| x.is_finite() && !lt(x, 0.0);
self.search_limit >= 1
&& lt(0.0, self.rel_pivot_tol)
&& !lt(1.0, self.rel_pivot_tol)
&& finite_nonneg(self.abs_pivot_tol)
&& finite_nonneg(self.drop_tol)
&& finite_nonneg(self.update_stability_tol)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct LuStats {
pub m: usize,
pub nnz_basis: usize,
pub nnz_l: usize,
pub nnz_u: usize,
pub fill_in: usize,
pub col_singletons: usize,
pub row_singletons: usize,
pub markowitz_pivots: usize,
pub growth: f64,
pub factorizations: usize,
pub ftran: SolveStats,
pub btran: SolveStats,
pub ftran_update: SolveStats,
pub max_basis: f64,
pub max_u: f64,
pub max_u_fresh: f64,
pub growth_exceeded: bool,
pub symbolic_reuses: usize,
pub symbolic_fallbacks: usize,
}
#[derive(Clone, Debug)]
pub struct Factors<T: Real> {
pub(crate) m: usize,
pub(crate) valid: bool,
pub(crate) step_row: Vec<u32>,
pub(crate) step_col: Vec<u32>,
pub(crate) row_step: Vec<usize>,
pub(crate) col_step: Vec<usize>,
pub(crate) l_row_step: Vec<usize>,
pub(crate) l_start: Vec<usize>,
pub(crate) l_pivot: Vec<u32>,
pub(crate) l_index: Vec<u32>,
pub(crate) l_value: Vec<T>,
pub(crate) l_eta_of_row: Vec<u32>,
pub(crate) lt_start: Vec<usize>,
pub(crate) lt_pivot: Vec<u32>,
pub(crate) lt_value: Vec<T>,
pub(crate) diag: Vec<T>,
pub(crate) u_rows: LineStore<T>,
pub(crate) u_cols: LineStore<T>,
pub(crate) spike: Vec<T>,
pub(crate) spike_idx: Vec<u32>,
pub(crate) spike_valid: bool,
pub(crate) ws: Workspace<T>,
pub(crate) nnz_factor0: usize,
pub(crate) stats: LuStats,
}
impl<T: Real> Default for Factors<T> {
fn default() -> Self {
Self {
m: 0,
valid: false,
step_row: Vec::new(),
step_col: Vec::new(),
row_step: Vec::new(),
col_step: Vec::new(),
l_row_step: Vec::new(),
l_start: vec![0],
l_pivot: Vec::new(),
l_index: Vec::new(),
l_value: Vec::new(),
l_eta_of_row: Vec::new(),
lt_start: vec![0],
lt_pivot: Vec::new(),
lt_value: Vec::new(),
diag: Vec::new(),
u_rows: LineStore::default(),
u_cols: LineStore::default(),
spike: Vec::new(),
spike_idx: Vec::new(),
spike_valid: false,
ws: Workspace::default(),
nnz_factor0: 0,
stats: LuStats::default(),
}
}
}
#[derive(Clone, Debug)]
pub struct Lu<T: Real = f64, U: BasisUpdate<T> = ForrestTomlin<T>> {
pub(crate) opts: LuOptions,
pub(crate) factors: Factors<T>,
pub(crate) update: U,
pub(crate) expected_density: [f64; 3],
pub(crate) symbolic: Option<SymbolicLu>,
pub(crate) pre_order: Option<PreOrder>,
pub(crate) refactor_pivot_tol: Option<f64>,
}
impl<T: Real, U: BasisUpdate<T>> Default for Lu<T, U> {
fn default() -> Self {
Self::new(LuOptions::default())
}
}
pub trait LuFactor<T: Real> {
fn factorize<C: ColumnSource<T>>(&mut self, cols: &C) -> Result<(), LuError>;
fn refactorize_same_pattern<C: ColumnSource<T>>(&mut self, cols: &C) -> Result<(), LuError>;
fn symbolic(&self) -> Option<&SymbolicLu>;
fn ftran(&mut self, v: &mut SparseVec<T>);
fn btran(&mut self, v: &mut SparseVec<T>);
fn ftran_for_update(&mut self, v: &mut SparseVec<T>);
fn update(&mut self, r: usize, alpha_r: T) -> Result<UpdateOutcome, LuError>;
fn m(&self) -> usize;
fn is_valid(&self) -> bool;
fn stats(&self) -> &LuStats;
fn update_stats(&self) -> &UpdateStats;
fn options(&self) -> &LuOptions;
fn set_options(&mut self, opts: LuOptions);
fn check_invariants(&self) -> Result<(), SparseError>;
}
#[inline]
#[allow(clippy::cast_precision_loss)] pub(crate) fn count_f64(n: usize) -> f64 {
n as f64
}
pub(crate) const VACANT: u32 = u32::MAX;
#[inline]
pub(crate) fn idx32(i: usize) -> u32 {
u32::try_from(i).expect("index exceeds u32::MAX")
}
impl<T: Real, U: BasisUpdate<T>> Lu<T, U> {
pub fn new(opts: LuOptions) -> Self {
debug_assert!(opts.is_valid(), "{opts:?}");
Self {
opts,
factors: Factors::default(),
update: U::default(),
expected_density: [0.0; 3],
symbolic: None,
pre_order: None,
refactor_pivot_tol: None,
}
}
pub fn set_pre_order(&mut self, pre_order: Option<PreOrder>) {
self.pre_order = pre_order;
}
#[inline]
pub fn pre_order(&self) -> Option<PreOrder> {
self.pre_order
}
pub fn set_refactor_pivot_tol(&mut self, tol: Option<f64>) {
if let Some(u) = tol {
assert!(
u.is_finite() && lt(0.0, u) && le_exact(u, 1.0),
"refactor pivot tolerance {u} outside (0, 1]"
);
}
self.refactor_pivot_tol = tol;
}
#[inline]
pub fn refactor_pivot_tol(&self) -> Option<f64> {
self.refactor_pivot_tol
}
#[inline]
pub fn has_spike(&self) -> bool {
self.factors.valid && self.factors.spike_valid
}
#[inline]
pub fn update_policy(&self) -> &U {
&self.update
}
pub(crate) fn finish(&mut self, k: Elim<T>) -> Result<(), LuError> {
let f = &mut self.factors;
let m = f.m;
let mut stats = LuStats {
m,
nnz_basis: k.nnz_basis,
max_basis: k.max_basis.to_f64(),
col_singletons: k.col_singletons,
row_singletons: k.row_singletons,
markowitz_pivots: k.markowitz_pivots,
factorizations: f.stats.factorizations,
ftran: f.stats.ftran,
btran: f.stats.btran,
ftran_update: f.stats.ftran_update,
symbolic_reuses: f.stats.symbolic_reuses,
symbolic_fallbacks: f.stats.symbolic_fallbacks,
..LuStats::default()
};
if !k.dead.is_empty() {
self.symbolic = None;
let mut cols = k.dead;
cols.sort_unstable();
let mut pivoted = vec![false; m];
for &p in &k.step_row {
pivoted[p as usize] = true;
}
let rows: Vec<Row> = (0..m)
.filter(|&i| !pivoted[i])
.map(Row::from_usize)
.collect();
debug_assert_eq!(rows.len(), cols.len());
f.stats = stats;
self.update.reset(m, 0.0);
return Err(LuError::Singular {
rank_deficiency: cols.len(),
cols,
rows,
});
}
f.row_step = vec![NIL; m];
f.col_step = vec![NIL; m];
for (s, (&p, &q)) in k.step_row.iter().zip(&k.step_col).enumerate() {
f.row_step[p as usize] = s;
f.col_step[q as usize] = s;
}
f.l_row_step.clone_from(&f.row_step);
f.step_row = k.step_row;
f.step_col = k.step_col;
f.l_start = k.l_start;
f.l_pivot = k.l_pivot;
f.l_index = k.l_index;
f.l_value = k.l_value;
f.diag = k.diag;
let mut row_len = vec![0_usize; m];
let mut col_len = vec![0_usize; m];
for &(i, j, _) in &k.u_trip {
row_len[i as usize] += 1;
col_len[j as usize] += 1;
}
let slack = |n: &usize| n + 2;
let row_caps: Vec<usize> = row_len.iter().map(slack).collect();
let col_caps: Vec<usize> = col_len.iter().map(slack).collect();
f.u_rows = LineStore::new(&row_caps);
f.u_cols = LineStore::new(&col_caps);
let mut max_u = T::ZERO;
for &(i, j, v) in &k.u_trip {
f.u_rows.push(i as usize, j, v);
f.u_cols.push(j as usize, i, v);
max_u = max_u.max(v.abs());
}
for &d in &f.diag {
max_u = max_u.max(d.abs());
}
f.prepare_hypersparse();
stats.nnz_l = f.l_value.len();
stats.nnz_u = f.u_rows.live() + m;
stats.max_u = max_u.to_f64();
stats.max_u_fresh = stats.max_u;
stats.factorizations += 1;
f.stats = stats;
f.nnz_factor0 = stats.nnz_l + stats.nnz_u;
let update_growth = f.refresh_stats(self.opts.growth_limit, 0);
self.update.reset(m, update_growth);
f.valid = true;
debug_assert!(f.check_invariants().is_ok(), "{:?}", f.check_invariants());
let sym = self.symbolic.get_or_insert_with(SymbolicLu::default);
sym.pivot_rows.clone_from(&f.step_row);
sym.pivot_cols.clone_from(&f.step_col);
sym.singletons = stats.col_singletons + stats.row_singletons;
sym.nnz_l = stats.nnz_l;
sym.nnz_u = stats.nnz_u;
Ok(())
}
}
impl<T: Real, U: BasisUpdate<T>> LuFactor<T> for Lu<T, U> {
fn factorize<C: ColumnSource<T>>(&mut self, cols: &C) -> Result<(), LuError> {
self.factors.valid = false;
self.factors.spike_valid = false;
cols.validate()?;
self.factors.m = cols.m();
let mut k = Kernel::load(cols, self.opts);
k.triangular_phase();
match self.pre_order {
None => k.markowitz_phase(),
Some(PreOrder::Colamd) => k.preordered_phase(),
}
self.finish(k.into_elim())
}
fn refactorize_same_pattern<C: ColumnSource<T>>(&mut self, cols: &C) -> Result<(), LuError> {
self.refactorize_impl(cols)
}
#[inline]
fn symbolic(&self) -> Option<&SymbolicLu> {
self.symbolic.as_ref()
}
#[inline]
fn ftran(&mut self, v: &mut SparseVec<T>) {
self.ftran_kind(v, Kind::Ftran);
}
#[inline]
fn btran(&mut self, v: &mut SparseVec<T>) {
self.btran_impl(v);
}
#[inline]
fn ftran_for_update(&mut self, v: &mut SparseVec<T>) {
self.ftran_kind(v, Kind::FtranUpdate);
}
#[inline]
fn update(&mut self, r: usize, alpha_r: T) -> Result<UpdateOutcome, LuError> {
self.update
.update(&mut self.factors, &self.opts, r, alpha_r)
}
#[inline]
fn m(&self) -> usize {
self.factors.m
}
#[inline]
fn is_valid(&self) -> bool {
self.factors.valid
}
#[inline]
fn stats(&self) -> &LuStats {
&self.factors.stats
}
#[inline]
fn update_stats(&self) -> &UpdateStats {
self.update.stats()
}
#[inline]
fn options(&self) -> &LuOptions {
&self.opts
}
fn set_options(&mut self, opts: LuOptions) {
debug_assert!(opts.is_valid(), "{opts:?}");
self.opts = opts;
}
fn check_invariants(&self) -> Result<(), SparseError> {
self.factors.check_invariants()
}
}
impl<T: Real> Factors<T> {
fn prepare_hypersparse(&mut self) {
let m = self.m;
self.l_eta_of_row.clear();
self.l_eta_of_row.resize(m, NO_ETA);
let mut row_len = vec![0_usize; m + 1];
for (e, &p) in self.l_pivot.iter().enumerate() {
self.l_eta_of_row[p as usize] = idx32(e);
for &i in &self.l_index[self.l_start[e]..self.l_start[e + 1]] {
row_len[i as usize + 1] += 1;
}
}
for i in 0..m {
row_len[i + 1] += row_len[i];
}
let nnz = self.l_index.len();
self.lt_pivot.clear();
self.lt_pivot.resize(nnz, 0);
self.lt_value.clear();
self.lt_value.resize(nnz, T::ZERO);
let mut next = row_len.clone();
for (e, &p) in self.l_pivot.iter().enumerate() {
let r = self.l_start[e]..self.l_start[e + 1];
for (&i, &l) in self.l_index[r.clone()].iter().zip(&self.l_value[r]) {
let at = next[i as usize];
self.lt_pivot[at] = p;
self.lt_value[at] = l;
next[i as usize] += 1;
}
}
self.lt_start = row_len;
self.ws.resize(m);
self.ws.queued.clear();
if self.spike.len() == m {
for &i in &self.spike_idx {
self.spike[i as usize] = T::ZERO;
}
} else {
self.spike = vec![T::ZERO; m];
}
self.spike_idx.clear();
}
pub(crate) fn refresh_stats(&mut self, growth_limit: f64, nnz_eta: usize) -> f64 {
let s = &mut self.stats;
s.nnz_u = self.u_rows.live() + self.m;
s.fill_in = (s.nnz_l + s.nnz_u + nnz_eta).saturating_sub(s.nnz_basis);
s.growth = if lt(0.0, s.max_basis) {
s.max_u / s.max_basis
} else {
1.0
};
s.growth_exceeded = lt(growth_limit, s.growth);
let reference = s.max_basis.max(s.max_u_fresh);
if lt(0.0, reference) {
s.max_u / reference
} else {
1.0
}
}
pub(crate) fn check_invariants(&self) -> Result<(), SparseError> {
let bad = |msg: String| Err(SparseError::Malformed(msg));
if !self.valid {
return bad("factor is not valid".into());
}
let m = self.m;
if self.step_row.len() != self.step_col.len() {
return bad("step arrays differ in length".into());
}
let mut steps = 0;
for s in 0..self.step_row.len() {
let (p, q) = (self.step_row[s], self.step_col[s]);
if p == VACANT && q == VACANT {
continue;
}
steps += 1;
let (p, q) = (p as usize, q as usize);
if p >= m || q >= m || self.row_step[p] != s || self.col_step[q] != s {
return bad(format!("step {s} inconsistent with the inverse maps"));
}
}
if steps != m {
return bad(format!("{steps} steps for m = {m}"));
}
for e in 0..self.l_pivot.len() {
let sp = self.l_row_step[self.l_pivot[e] as usize];
for &i in &self.l_index[self.l_start[e]..self.l_start[e + 1]] {
if self.l_row_step[i as usize] <= sp {
return bad(format!("L eta {e} touches row {i} pivoted earlier"));
}
}
}
let mut n_row = 0;
for i in 0..m {
if is_exact_zero(self.diag[i]) {
return bad(format!("zero diagonal at row {i}"));
}
let (idx, val) = self.u_rows.line(i);
n_row += idx.len();
for (&j, &v) in idx.iter().zip(val) {
if self.col_step[j as usize] <= self.row_step[i] {
return bad(format!("U entry ({i}, {j}) below the diagonal"));
}
match self.u_cols.get(j as usize, idx32(i)) {
Some(w) if approx_eq(v, w, T::ZERO) => {}
_ => return bad(format!("U copies disagree at ({i}, {j})")),
}
}
}
if n_row != self.u_cols.live() {
return bad("U row and column copies differ in size".into());
}
Ok(())
}
}
struct Kernel<T: Real> {
m: usize,
abs_tol: T,
rel_tol: T,
drop_tol: T,
search_limit: usize,
cols: LineStore<T>,
rows: LineStore<T>,
col_lists: CountLists,
row_lists: CountLists,
wmap: Vec<usize>,
lmap: Vec<usize>,
tiny: Vec<bool>,
tw: Vec<usize>,
cancelled: Vec<(usize, u32)>,
nnz_basis: usize,
max_basis: T,
step_row: Vec<u32>,
step_col: Vec<u32>,
diag: Vec<T>,
l_start: Vec<usize>,
l_pivot: Vec<u32>,
l_index: Vec<u32>,
l_value: Vec<T>,
u_trip: Vec<(u32, u32, T)>,
dead: Vec<usize>,
col_singletons: usize,
row_singletons: usize,
markowitz_pivots: usize,
}
pub(crate) struct Elim<T: Real> {
pub(crate) nnz_basis: usize,
pub(crate) max_basis: T,
pub(crate) col_singletons: usize,
pub(crate) row_singletons: usize,
pub(crate) markowitz_pivots: usize,
pub(crate) step_row: Vec<u32>,
pub(crate) step_col: Vec<u32>,
pub(crate) diag: Vec<T>,
pub(crate) l_start: Vec<usize>,
pub(crate) l_pivot: Vec<u32>,
pub(crate) l_index: Vec<u32>,
pub(crate) l_value: Vec<T>,
pub(crate) u_trip: Vec<(u32, u32, T)>,
pub(crate) dead: Vec<usize>,
}
#[derive(Clone, Copy)]
struct Candidate<T: Real> {
i: usize,
j: usize,
merit: usize,
ratio: T,
}
impl<T: Real> Candidate<T> {
fn beats(&self, other: Option<&Candidate<T>>) -> bool {
other.is_none_or(|o| {
self.merit < o.merit || (self.merit == o.merit && lt(o.ratio, self.ratio))
})
}
}
impl<T: Real> Kernel<T> {
fn load<C: ColumnSource<T>>(basis: &C, opts: LuOptions) -> Self {
let m = basis.m();
let drop_tol = T::from_f64(opts.drop_tol);
let mut col_len = vec![0_usize; m];
let mut row_len = vec![0_usize; m];
for (k, len) in col_len.iter_mut().enumerate() {
basis.for_each_entry(k, |i, _| {
*len += 1;
row_len[i as usize] += 1;
});
}
let caps = |v: &[usize]| v.iter().map(|&n| n + 4).collect::<Vec<_>>();
let mut cols = LineStore::new_linked(&caps(&col_len));
let mut rows = LineStore::new_pattern_linked(&caps(&row_len));
let mut max_basis = T::ZERO;
let mut tiny = vec![false; m];
for (k, tiny_k) in tiny.iter_mut().enumerate() {
basis.for_each_entry(k, |i, v| {
*tiny_k |= is_zero(v, drop_tol);
let (q, r) = (cols.len(k), rows.len(i as usize));
cols.push_linked(k, i, v, r);
rows.push_linked(i as usize, idx32(k), T::ZERO, q);
max_basis = max_basis.max(v.abs());
});
}
debug_assert_eq!(check_links(&cols, &rows, true), Ok(()));
let mut col_lists = CountLists::new(m, m);
let mut row_lists = CountLists::new(m, m);
for j in 0..m {
col_lists.insert(j, cols.len(j));
}
for i in 0..m {
row_lists.insert(i, rows.len(i));
}
Self {
m,
abs_tol: T::from_f64(opts.abs_pivot_tol),
rel_tol: T::from_f64(opts.rel_pivot_tol),
drop_tol,
search_limit: opts.search_limit,
nnz_basis: cols.live(),
cols,
rows,
col_lists,
row_lists,
wmap: vec![NIL; m],
lmap: vec![NIL; m],
tiny,
tw: Vec::new(),
cancelled: Vec::new(),
max_basis,
step_row: Vec::with_capacity(m),
step_col: Vec::with_capacity(m),
diag: vec![T::ZERO; m],
l_start: vec![0],
l_pivot: Vec::new(),
l_index: Vec::new(),
l_value: Vec::new(),
u_trip: Vec::new(),
dead: Vec::new(),
col_singletons: 0,
row_singletons: 0,
markowitz_pivots: 0,
}
}
fn into_elim(self) -> Elim<T> {
Elim {
nnz_basis: self.nnz_basis,
max_basis: self.max_basis,
col_singletons: self.col_singletons,
row_singletons: self.row_singletons,
markowitz_pivots: self.markowitz_pivots,
step_row: self.step_row,
step_col: self.step_col,
diag: self.diag,
l_start: self.l_start,
l_pivot: self.l_pivot,
l_index: self.l_index,
l_value: self.l_value,
u_trip: self.u_trip,
dead: self.dead,
}
}
fn preordered_phase(&mut self) {
self.kill_empty_columns();
for c in self.bump_order() {
self.kill_empty_columns();
if !self.col_lists.contains(c) {
continue;
}
let gamma = self
.cols
.line(c)
.1
.iter()
.fold(T::ZERO, |g, &v| g.max(v.abs()));
if !self.above_abs(gamma) {
self.kill(c);
continue;
}
let u = self.rel_tol;
let mut best: Option<(usize, usize, T)> = None;
let (idx, val) = self.cols.line(c);
for (&i, &v) in idx.iter().zip(val) {
let (i, a) = (i as usize, v.abs());
if !self.above_abs(v) || lt(a, u * gamma) {
continue;
}
let r = self.rows.len(i);
if best.is_none_or(|(_, rb, ab)| r < rb || (r == rb && lt(ab, a))) {
best = Some((i, r, a));
}
}
let (i, _, _) = best.expect("the largest entry is acceptable");
self.pivot(i, c);
self.markowitz_pivots += 1;
}
self.kill_empty_columns();
debug_assert_eq!(
self.col_lists.size(),
0,
"every bump column is pivoted or dead"
);
}
fn bump_order(&self) -> Vec<usize> {
let mut row_of = vec![NIL; self.m];
let mut n_rows = 0;
for (i, r) in row_of.iter_mut().enumerate() {
if self.row_lists.contains(i) {
*r = n_rows;
n_rows += 1;
}
}
let active: Vec<usize> = (0..self.m)
.filter(|&j| self.col_lists.contains(j))
.collect();
let mut col_ptr = Vec::with_capacity(active.len() + 1);
let mut row_idx = Vec::new();
col_ptr.push(0);
for &j in &active {
let start = row_idx.len();
row_idx.extend(
self.cols
.indices(j)
.iter()
.map(|&i| idx32(row_of[i as usize])),
);
row_idx[start..].sort_unstable();
col_ptr.push(row_idx.len());
}
let pattern = Pattern {
n_rows,
col_ptr,
row_idx,
};
debug_assert_eq!(pattern.check_invariants(), Ok(()));
let perm = Colamd::default()
.order(&pattern, OrderingTarget::Columns)
.expect("a valid pattern");
perm.fwd.iter().map(|&k| active[k as usize]).collect()
}
#[inline]
fn above_abs(&self, v: T) -> bool {
lt(self.abs_tol, v.abs())
}
#[inline]
fn value_in_row(&self, i: usize, r: usize) -> T {
let j = self.rows.index_at(i, r) as usize;
let q = self
.cols
.find(j, idx32(i), self.rows.link(i, r))
.expect("row and column files agree");
self.cols.value_at(j, q)
}
#[inline]
fn row_twin(&self, j: usize, q: usize) -> (usize, usize) {
let i = self.cols.index_at(j, q) as usize;
let r = self
.rows
.find(i, idx32(j), self.cols.link(j, q))
.expect("row and column files agree");
(i, r)
}
fn kill(&mut self, j: usize) {
for q in 0..self.cols.len(j) {
let (i, r) = self.row_twin(j, q);
remove_linked(&mut self.rows, &mut self.cols, i, r);
self.row_lists.set(i, self.rows.len(i));
}
self.cols.clear(j);
self.col_lists.remove(j);
self.dead.push(j);
}
fn kill_empty_columns(&mut self) {
while let Some(j) = self.col_lists.first(0) {
self.kill(j);
}
}
fn triangular_phase(&mut self) {
loop {
self.kill_empty_columns();
if let Some(j) = self.col_lists.first(1) {
let (idx, val) = self.cols.line(j);
let (i, v) = (idx[0] as usize, val[0]);
if self.above_abs(v) {
self.pivot(i, j);
self.col_singletons += 1;
} else {
self.kill(j);
}
continue;
}
let mut found = None;
let mut e = self.row_lists.first(1);
while let Some(i) = e {
let j = self.rows.index_at(i, 0) as usize;
let v = self.value_in_row(i, 0);
if self.above_abs(v) {
found = Some((i, j));
break;
}
e = self.row_lists.next(i);
}
let Some((i, j)) = found else {
debug_assert_eq!(check_links(&self.cols, &self.rows, false), Ok(()));
return;
};
self.pivot(i, j);
self.row_singletons += 1;
}
}
fn markowitz_phase(&mut self) {
loop {
self.kill_empty_columns();
if self.col_lists.size() == 0 {
return;
}
let (best, dead) = self.search();
let killed = !dead.is_empty();
for j in dead {
self.kill(j);
}
match best {
Some(c) => {
self.pivot(c.i, c.j);
self.markowitz_pivots += 1;
}
None if killed => {}
None => {
debug_assert!(
false,
"Markowitz search found neither pivot nor dead column"
);
return;
}
}
}
}
fn search(&self) -> (Option<Candidate<T>>, Vec<usize>) {
let u = self.rel_tol;
let limit = self.search_limit.max(1);
let mut best: Option<Candidate<T>> = None;
let mut dead = Vec::new();
let mut examined = 0_usize;
let col_max = |j: usize| {
self.cols
.line(j)
.1
.iter()
.fold(T::ZERO, |g, &v| g.max(v.abs()))
};
let acceptable = |v: T, gamma: T| self.above_abs(v) && !lt(v.abs(), u * gamma);
for k in 1..=self.m {
let mut e = self.col_lists.first(k);
while let Some(j) = e {
e = self.col_lists.next(j);
let gamma = col_max(j);
if !self.above_abs(gamma) {
dead.push(j);
continue;
}
let mut any = false;
let (idx, val) = self.cols.line(j);
for (&i, &v) in idx.iter().zip(val) {
if acceptable(v, gamma) {
any = true;
let i = i as usize;
let c = Candidate {
i,
j,
merit: (self.rows.len(i) - 1) * (k - 1),
ratio: v.abs() / gamma,
};
if c.beats(best.as_ref()) {
best = Some(c);
}
}
}
examined += usize::from(any);
if let Some(b) = &best
&& (b.merit <= (k - 1) * (k - 1) || examined >= limit)
{
return (best, dead);
}
}
let mut e = self.row_lists.first(k);
while let Some(i) = e {
e = self.row_lists.next(i);
let mut any = false;
for (r, &j) in self.rows.indices(i).iter().enumerate() {
let j = j as usize;
let gamma = col_max(j);
let v = self.value_in_row(i, r);
if acceptable(v, gamma) {
any = true;
let c = Candidate {
i,
j,
merit: (k - 1) * (self.cols.len(j) - 1),
ratio: v.abs() / gamma,
};
if c.beats(best.as_ref()) {
best = Some(c);
}
}
}
examined += usize::from(any);
if let Some(b) = &best
&& (b.merit <= k * (k - 1) || examined >= limit)
{
return (best, dead);
}
}
}
(best, dead)
}
fn update_column(
&mut self,
j: usize,
u_pj: T,
lcol: &[(u32, T)],
lq: &mut Vec<usize>,
cancelled: &mut Vec<(usize, u32)>,
) {
lq.clear();
lq.resize(lcol.len(), NIL);
for (q, &i) in self.cols.line(j).0.iter().enumerate() {
let k = self.lmap[i as usize];
if k != NIL {
lq[k] = q;
}
}
for (k, &(i, l_ic)) in lcol.iter().enumerate() {
let delta = -l_ic * u_pj;
let q = lq[k];
if q == NIL {
let (q, r) = (self.cols.len(j), self.rows.len(i as usize));
self.cols.push_linked(j, i, delta, r);
lq[k] = q;
self.rows.push_linked(i as usize, idx32(j), T::ZERO, q);
} else {
self.cols.values_mut(j)[q] += delta;
}
}
cancelled.clear();
if self.tiny[j] {
self.tiny[j] = false;
let (idx, val) = self.cols.line(j);
cancelled.extend(
idx.iter()
.zip(val)
.enumerate()
.filter(|&(_, (_, &v))| is_zero(v, self.drop_tol))
.map(|(q, (&i, _))| (q, i)),
);
} else {
for (k, &(i, _)) in lcol.iter().enumerate() {
if is_zero(self.cols.value_at(j, lq[k]), self.drop_tol) {
cancelled.push((lq[k], i));
}
}
cancelled.sort_unstable();
}
for &(q, i) in cancelled.iter() {
self.wmap[i as usize] = q;
}
for &(_, i) in cancelled.iter() {
let q = self.wmap[i as usize];
let (_, t) = remove_linked(&mut self.cols, &mut self.rows, j, q);
if q < self.cols.len(j) {
let moved = self.cols.index_at(j, q) as usize;
if self.wmap[moved] != NIL {
self.wmap[moved] = q;
}
}
let r = self
.rows
.find(i as usize, idx32(j), t)
.expect("row and column files agree");
remove_linked(&mut self.rows, &mut self.cols, i as usize, r);
self.wmap[i as usize] = NIL;
}
}
fn pivot(&mut self, p: usize, c: usize) {
let (p32, c32) = (idx32(p), idx32(c));
let piv = self.cols.get(c, p32).expect("pivot entry is stored");
let mut tw = std::mem::take(&mut self.tw);
tw.clear();
let mut r_p = NIL;
let lcol: Vec<(u32, T)> = {
let (idx, val) = self.cols.line(c);
let mut lcol = Vec::with_capacity(idx.len().saturating_sub(1));
for (q, (&i, &v)) in idx.iter().zip(val).enumerate() {
if i == p32 {
r_p = self.cols.link(c, q);
} else {
lcol.push((i, v / piv));
tw.push(self.cols.link(c, q));
}
}
lcol
};
debug_assert_ne!(r_p, NIL, "the pivot entry is in its column");
self.cols.clear(c);
self.col_lists.remove(c);
for (&(i, _), &t) in lcol.iter().zip(&tw) {
let i = i as usize;
let r = self
.rows
.find(i, c32, t)
.expect("row and column files agree");
remove_linked(&mut self.rows, &mut self.cols, i, r);
}
let r_p = self
.rows
.find(p, c32, r_p)
.expect("row and column files agree");
remove_linked(&mut self.rows, &mut self.cols, p, r_p);
let mut urow = Vec::with_capacity(self.rows.len(p));
for r in 0..self.rows.len(p) {
let j = self.rows.index_at(p, r) as usize;
let q = self
.cols
.find(j, p32, self.rows.link(p, r))
.expect("row and column files agree");
let v = self.cols.value_at(j, q);
remove_linked(&mut self.cols, &mut self.rows, j, q);
urow.push((idx32(j), v));
}
self.rows.clear(p);
self.row_lists.remove(p);
if !lcol.is_empty() && !urow.is_empty() {
for (k, &(i, _)) in lcol.iter().enumerate() {
self.lmap[i as usize] = k;
}
let lq = &mut tw;
let mut cancelled = std::mem::take(&mut self.cancelled);
for &(j, u_pj) in &urow {
self.update_column(j as usize, u_pj, &lcol, lq, &mut cancelled);
}
for &(i, _) in &lcol {
self.lmap[i as usize] = NIL;
}
self.cancelled = cancelled;
}
self.tw = tw;
for &(i, _) in &lcol {
let i = i as usize;
self.row_lists.set(i, self.rows.len(i));
}
for &(j, _) in &urow {
let j = j as usize;
self.col_lists.set(j, self.cols.len(j));
}
self.step_row.push(p32);
self.step_col.push(c32);
self.diag[p] = piv;
if !lcol.is_empty() {
self.l_pivot.push(p32);
for (i, l) in lcol {
self.l_index.push(i);
self.l_value.push(l);
}
self.l_start.push(self.l_index.len());
}
for (j, u) in urow {
self.u_trip.push((p32, j, u));
}
}
}