#![allow(dead_code)]
use super::WorkspaceOptions;
use crate::{CscPattern, OrderingError};
pub const NONE: i32 = -1;
#[inline(always)]
pub fn flip(x: i32) -> i32 {
-2 - x
}
#[inline]
pub fn clear_flag(wflg: i32, wbig: i32, w: &mut [i32]) -> i32 {
if wflg < 2 || wflg >= wbig {
for x in w.iter_mut() {
if *x != 0 {
*x = 1;
}
}
return 2;
}
wflg
}
#[derive(Debug)]
pub struct Workspace {
pub n: usize,
pub iwlen: usize,
pub pfree: usize,
pub iw: Vec<i32>,
pub pe: Vec<i32>,
pub len: Vec<i32>,
pub nv: Vec<i32>,
pub elen: Vec<i32>,
pub degree: Vec<i32>,
pub w: Vec<i32>,
pub head: Vec<i32>,
pub next: Vec<i32>,
pub last: Vec<i32>,
pub wf: Vec<i64>,
pub wflg: i32,
pub wbig: i32,
pub lemax: i32,
pub mindeg: usize,
pub ncmpa: u32,
pub nel: usize,
pub ndense: i32,
pub n_mass_elim: u32,
pub n_supervar_merge: u32,
}
impl Workspace {
pub fn new(
pattern: &CscPattern<'_>,
opts: &WorkspaceOptions,
) -> Result<Workspace, OrderingError> {
Self::new_with_n_buckets(pattern, opts, pattern.n)
}
pub fn new_with_n_buckets(
pattern: &CscPattern<'_>,
opts: &WorkspaceOptions,
n_buckets: usize,
) -> Result<Workspace, OrderingError> {
let n = pattern.n;
debug_assert!(n_buckets >= n, "n_buckets must cover deg ∈ [0, n)");
if n >= i32::MAX as usize {
return Err(OrderingError::IndexOverflow);
}
let mut len: Vec<i32> = vec![0; n];
let mut nzaat: usize = 0;
#[allow(clippy::needless_range_loop)]
for j in 0..n {
let j_i32 = j as i32;
let start = pattern.col_ptr[j] as usize;
let end = pattern.col_ptr[j + 1] as usize;
let mut cnt: usize = 0;
for &r in &pattern.row_idx[start..end] {
if r != j_i32 {
cnt += 1;
}
}
len[j] = cnt as i32;
nzaat += cnt;
}
let iwlen = nzaat
.checked_add(nzaat / 5)
.and_then(|s| s.checked_add(n))
.ok_or(OrderingError::IndexOverflow)?;
if iwlen > i32::MAX as usize {
return Err(OrderingError::IndexOverflow);
}
let mut iw: Vec<i32> = vec![0; iwlen];
let mut pe: Vec<i32> = vec![0; n];
let mut pfree: usize = 0;
#[allow(clippy::needless_range_loop)]
for j in 0..n {
pe[j] = pfree as i32;
let j_i32 = j as i32;
let start = pattern.col_ptr[j] as usize;
let end = pattern.col_ptr[j + 1] as usize;
for &r in &pattern.row_idx[start..end] {
if r != j_i32 {
iw[pfree] = r;
pfree += 1;
}
}
}
debug_assert_eq!(pfree, nzaat);
let dense = if opts.dense_alpha < 0.0 {
n.saturating_sub(2)
} else {
(opts.dense_alpha * (n as f64).sqrt()) as usize
};
let dense = dense.max(16).min(n);
let mut nv: Vec<i32> = vec![1; n];
let mut elen: Vec<i32> = vec![0; n];
let mut w: Vec<i32> = vec![1; n];
let degree: Vec<i32> = len.clone();
let mut head: Vec<i32> = vec![NONE; n_buckets];
let mut next: Vec<i32> = vec![NONE; n];
let mut last: Vec<i32> = vec![NONE; n];
let mut wf: Vec<i64> = vec![0; n];
let wbig = i32::MAX - n as i32;
let wflg = 0;
let mut nel: usize = 0;
let mut ndense: i32 = 0;
for i in 0..n {
let deg = degree[i] as usize;
if deg == 0 {
elen[i] = flip(1);
nel += 1;
pe[i] = NONE;
w[i] = 0;
} else if deg > dense {
ndense += 1;
nv[i] = 0;
elen[i] = NONE;
pe[i] = NONE;
nel += 1;
} else {
let inext = head[deg];
if inext != NONE {
last[inext as usize] = i as i32;
}
next[i] = inext;
head[deg] = i as i32;
wf[i] = deg as i64;
}
}
Ok(Workspace {
n,
iwlen,
pfree,
iw,
pe,
len,
nv,
elen,
degree,
w,
head,
next,
last,
wf,
wflg,
wbig,
lemax: 0,
mindeg: 0,
ncmpa: 0,
nel,
ndense,
n_mass_elim: 0,
n_supervar_merge: 0,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
fn pat<'a>(n: usize, cp: &'a [i32], ri: &'a [i32]) -> CscPattern<'a> {
CscPattern::new(n, cp, ri).expect("valid test pattern")
}
#[test]
fn flip_involution() {
for x in [-100i32, -1, 0, 1, 17, 1000] {
assert_eq!(flip(flip(x)), x);
}
}
#[test]
fn clear_flag_resets_on_overflow() {
let mut w = [0, 3, 5, 7, 0];
let wflg = clear_flag(100, 100, &mut w);
assert_eq!(wflg, 2);
assert_eq!(w, [0, 1, 1, 1, 0]);
}
#[test]
fn clear_flag_passthrough() {
let mut w = [1, 2, 3];
let wflg = clear_flag(5, 100, &mut w);
assert_eq!(wflg, 5);
assert_eq!(w, [1, 2, 3]);
}
#[test]
fn diag_4_zero_degree_fast_path() {
let cp = [0, 1, 2, 3, 4];
let ri = [0, 1, 2, 3];
let p = pat(4, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.n, 4);
assert_eq!(ws.nel, 4, "all four pre-eliminated");
assert_eq!(ws.ndense, 0, "no dense deferral");
for i in 0..4 {
assert_eq!(ws.elen[i], flip(1));
assert_eq!(ws.pe[i], NONE);
assert_eq!(ws.w[i], 0);
assert_eq!(ws.degree[i], 0);
}
for d in 0..4 {
assert_eq!(ws.head[d], NONE, "degree {d} bucket empty");
}
}
#[test]
fn tridiag_5_populates_degree_lists() {
let cp = [0, 2, 5, 8, 11, 13];
let ri = [0, 1, 0, 1, 2, 1, 2, 3, 2, 3, 4, 3, 4];
let p = pat(5, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.n, 5);
assert_eq!(ws.nel, 0, "no fast-path eliminations");
assert_eq!(ws.ndense, 0);
assert_eq!(ws.degree, vec![1, 2, 2, 2, 1]);
assert_eq!(ws.len, vec![1, 2, 2, 2, 1]);
assert_eq!(ws.head[1], 4);
assert_eq!(ws.next[4], 0);
assert_eq!(ws.next[0], NONE);
assert_eq!(ws.last[0], 4);
assert_eq!(ws.head[2], 3);
assert_eq!(ws.next[3], 2);
assert_eq!(ws.next[2], 1);
assert_eq!(ws.next[1], NONE);
assert_eq!(ws.last[1], 2);
assert_eq!(ws.last[2], 3);
assert_eq!(ws.last[3], NONE);
assert_eq!(ws.iwlen, 14);
assert_eq!(ws.pfree, 8);
}
#[test]
fn arrow_5_all_live() {
let cp = [0, 5, 7, 9, 11, 13];
let ri = [0, 1, 2, 3, 4, 0, 1, 0, 2, 0, 3, 0, 4];
let p = pat(5, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.degree, vec![4, 1, 1, 1, 1]);
assert_eq!(ws.nel, 0);
assert_eq!(ws.ndense, 0);
assert_eq!(ws.head[4], 0, "hub at deg-4 bucket");
assert_eq!(ws.head[1], 4, "last spoke inserted");
}
#[test]
fn arrow_200_hub_deferred() {
let n = 200usize;
let mut cp: Vec<i32> = Vec::with_capacity(n + 1);
let mut ri: Vec<i32> = Vec::new();
cp.push(0);
ri.push(0);
for r in 1..n {
ri.push(r as i32);
}
cp.push(ri.len() as i32);
for j in 1..n {
ri.push(0);
ri.push(j as i32);
cp.push(ri.len() as i32);
}
let p = pat(n, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.degree[0], (n - 1) as i32);
assert_eq!(ws.nel, 1, "hub only");
assert_eq!(ws.ndense, 1);
assert_eq!(ws.nv[0], 0, "hub marked deferred");
assert_eq!(ws.pe[0], NONE);
assert_eq!(ws.elen[0], NONE);
assert_eq!(ws.head[1], (n - 1) as i32);
}
#[test]
fn dense_alpha_negative_uses_n_minus_2() {
let n = 20usize;
let b = 5usize;
let mut cp: Vec<i32> = vec![0];
let mut ri: Vec<i32> = Vec::new();
for j in 0..n {
let lo = j.saturating_sub(b);
let hi = (j + b + 1).min(n);
for r in lo..hi {
ri.push(r as i32);
}
cp.push(ri.len() as i32);
}
let p = pat(n, &cp, &ri);
let opts = WorkspaceOptions { dense_alpha: -1.0 };
let ws = Workspace::new(&p, &opts).unwrap();
assert_eq!(ws.ndense, 0, "nothing deferred below n - 2");
assert_eq!(ws.nel, 0);
}
#[test]
fn empty_pattern_ok() {
let cp = [0i32];
let ri: [i32; 0] = [];
let p = pat(0, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.n, 0);
assert_eq!(ws.nel, 0);
assert_eq!(ws.iwlen, 0);
}
#[test]
fn diagonal_entries_skipped() {
let cp = [0, 1, 2, 3];
let ri = [0, 1, 2];
let p = pat(3, &cp, &ri);
let ws = Workspace::new(&p, &WorkspaceOptions::default()).unwrap();
assert_eq!(ws.len, vec![0, 0, 0]);
assert_eq!(ws.pfree, 0);
assert_eq!(ws.nel, 3);
}
}