use pounce_common::types::{Index, Number};
use pounce_nlp::tnlp::Linearity;
#[derive(Debug, Clone, Copy)]
pub struct ProbeView<'a> {
pub n_vars: usize,
pub m_rows: usize,
pub jac_irow: &'a [Index],
pub jac_jcol: &'a [Index],
pub jac_values: Option<&'a [Number]>,
pub g_l: &'a [Number],
pub g_u: &'a [Number],
pub linearity: Option<&'a [Linearity]>,
pub one_based: bool,
pub eq_tol: Number,
pub excluded_vars: Option<&'a [bool]>,
pub excluded_rows: Option<&'a [bool]>,
}
#[derive(Debug, Clone, Default)]
pub struct EqualityIncidence {
pub n_vars: usize,
pub eq_row_inner_idx: Vec<usize>,
pub adj_ptr: Vec<usize>,
pub vars: Vec<usize>,
}
impl EqualityIncidence {
pub fn from_probe(p: &ProbeView<'_>) -> Self {
let mut eq_row_inner_idx: Vec<usize> = Vec::new();
let mut inner_to_eq: Vec<Option<usize>> = vec![None; p.m_rows];
for (i, slot) in inner_to_eq.iter_mut().enumerate() {
if let Some(mask) = p.excluded_rows {
if mask[i] {
continue;
}
}
if (p.g_u[i] - p.g_l[i]).abs() <= p.eq_tol {
*slot = Some(eq_row_inner_idx.len());
eq_row_inner_idx.push(i);
}
}
let n_eq = eq_row_inner_idx.len();
let mut per_row: Vec<Vec<usize>> = vec![Vec::new(); n_eq];
let nnz = p.jac_irow.len();
for k in 0..nnz {
if let Some(vals) = p.jac_values {
if vals[k] == 0.0 {
continue;
}
}
let i = if p.one_based {
(p.jac_irow[k] as isize - 1) as usize
} else {
p.jac_irow[k] as usize
};
if i >= p.m_rows {
continue;
}
let Some(eq_k) = inner_to_eq[i] else { continue };
let j = if p.one_based {
(p.jac_jcol[k] as isize - 1) as usize
} else {
p.jac_jcol[k] as usize
};
if j >= p.n_vars {
continue;
}
if let Some(mask) = p.excluded_vars {
if mask[j] {
continue;
}
}
per_row[eq_k].push(j);
}
let mut adj_ptr: Vec<usize> = Vec::with_capacity(n_eq + 1);
let mut vars: Vec<usize> = Vec::new();
adj_ptr.push(0);
for row in per_row.iter_mut() {
row.sort_unstable();
row.dedup();
vars.extend_from_slice(row);
adj_ptr.push(vars.len());
}
Self {
n_vars: p.n_vars,
eq_row_inner_idx,
adj_ptr,
vars,
}
}
pub fn n_eq_rows(&self) -> usize {
self.eq_row_inner_idx.len()
}
pub fn neighbors(&self, k: usize) -> &[usize] {
let lo = self.adj_ptr[k];
let hi = self.adj_ptr[k + 1];
&self.vars[lo..hi]
}
}
#[derive(Debug, Clone, Default)]
pub struct InequalityIncidence {
pub n_vars: usize,
pub ineq_row_inner_idx: Vec<usize>,
pub adj_ptr: Vec<usize>,
pub vars: Vec<usize>,
pub col_to_rows_ptr: Vec<usize>,
pub col_to_rows: Vec<usize>,
}
impl InequalityIncidence {
pub fn from_probe(p: &ProbeView<'_>) -> Self {
let mut ineq_row_inner_idx: Vec<usize> = Vec::new();
let mut inner_to_ineq: Vec<Option<usize>> = vec![None; p.m_rows];
for (i, slot) in inner_to_ineq.iter_mut().enumerate() {
if let Some(mask) = p.excluded_rows {
if mask[i] {
continue;
}
}
if (p.g_u[i] - p.g_l[i]).abs() > p.eq_tol {
*slot = Some(ineq_row_inner_idx.len());
ineq_row_inner_idx.push(i);
}
}
let n_ineq = ineq_row_inner_idx.len();
let mut per_row: Vec<Vec<usize>> = vec![Vec::new(); n_ineq];
let nnz = p.jac_irow.len();
for k in 0..nnz {
if let Some(vals) = p.jac_values {
if vals[k] == 0.0 {
continue;
}
}
let i = if p.one_based {
(p.jac_irow[k] as isize - 1) as usize
} else {
p.jac_irow[k] as usize
};
if i >= p.m_rows {
continue;
}
let Some(ineq_k) = inner_to_ineq[i] else {
continue;
};
let j = if p.one_based {
(p.jac_jcol[k] as isize - 1) as usize
} else {
p.jac_jcol[k] as usize
};
if j >= p.n_vars {
continue;
}
if let Some(mask) = p.excluded_vars {
if mask[j] {
continue;
}
}
per_row[ineq_k].push(j);
}
let mut adj_ptr: Vec<usize> = Vec::with_capacity(n_ineq + 1);
let mut vars: Vec<usize> = Vec::new();
adj_ptr.push(0);
for row in per_row.iter_mut() {
row.sort_unstable();
row.dedup();
vars.extend_from_slice(row);
adj_ptr.push(vars.len());
}
let mut col_to_rows_ptr = vec![0usize; p.n_vars + 1];
for &v in &vars {
col_to_rows_ptr[v + 1] += 1;
}
for i in 1..=p.n_vars {
col_to_rows_ptr[i] += col_to_rows_ptr[i - 1];
}
let mut col_to_rows = vec![0usize; col_to_rows_ptr[p.n_vars]];
let mut cursor = col_to_rows_ptr[..p.n_vars].to_vec();
for (ineq_k, row) in per_row.iter().enumerate() {
for &v in row {
col_to_rows[cursor[v]] = ineq_k;
cursor[v] += 1;
}
}
Self {
n_vars: p.n_vars,
ineq_row_inner_idx,
adj_ptr,
vars,
col_to_rows_ptr,
col_to_rows,
}
}
pub fn n_ineq_rows(&self) -> usize {
self.ineq_row_inner_idx.len()
}
pub fn neighbors(&self, k: usize) -> &[usize] {
let lo = self.adj_ptr[k];
let hi = self.adj_ptr[k + 1];
&self.vars[lo..hi]
}
pub fn rows_for_var(&self, j: usize) -> &[usize] {
let lo = self.col_to_rows_ptr[j];
let hi = self.col_to_rows_ptr[j + 1];
&self.col_to_rows[lo..hi]
}
pub fn var_in_inequality(&self, j: usize) -> bool {
!self.rows_for_var(j).is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn probe<'a>(
n_vars: usize,
m_rows: usize,
irow: &'a [Index],
jcol: &'a [Index],
g_l: &'a [Number],
g_u: &'a [Number],
) -> ProbeView<'a> {
ProbeView {
n_vars,
m_rows,
jac_irow: irow,
jac_jcol: jcol,
jac_values: None,
g_l,
g_u,
linearity: None,
one_based: false,
eq_tol: 1e-12,
excluded_vars: None,
excluded_rows: None,
}
}
#[test]
fn incidence_empty_problem_is_empty() {
let p = probe(0, 0, &[], &[], &[], &[]);
let inc = EqualityIncidence::from_probe(&p);
assert_eq!(inc.n_eq_rows(), 0);
assert_eq!(inc.vars.len(), 0);
assert_eq!(inc.adj_ptr, vec![0]);
}
#[test]
fn incidence_filters_inequalities() {
let p = probe(2, 2, &[0, 0, 1], &[0, 1, 0], &[1.0, 0.0], &[1.0, 5.0]);
let inc = EqualityIncidence::from_probe(&p);
assert_eq!(inc.n_eq_rows(), 1);
assert_eq!(inc.eq_row_inner_idx, vec![0]);
assert_eq!(inc.neighbors(0), &[0, 1]);
}
#[test]
fn incidence_dedupes_and_sorts_columns() {
let p = probe(2, 1, &[0, 0, 0, 0], &[1, 1, 0, 1], &[2.5], &[2.5]);
let inc = EqualityIncidence::from_probe(&p);
assert_eq!(inc.neighbors(0), &[0, 1]);
}
#[test]
fn incidence_respects_fortran_indexing() {
let mut p = probe(
2,
1,
&[1, 1], &[1, 2], &[0.0],
&[0.0],
);
p.one_based = true;
let inc = EqualityIncidence::from_probe(&p);
assert_eq!(inc.n_eq_rows(), 1);
assert_eq!(inc.neighbors(0), &[0, 1]);
}
#[test]
fn incidence_drops_structural_zeros_when_values_provided() {
let vals = [3.5, 0.0];
let p = ProbeView {
n_vars: 2,
m_rows: 1,
jac_irow: &[0, 0],
jac_jcol: &[0, 1],
jac_values: Some(&vals),
g_l: &[1.0],
g_u: &[1.0],
linearity: None,
one_based: false,
eq_tol: 1e-12,
excluded_vars: None,
excluded_rows: None,
};
let inc = EqualityIncidence::from_probe(&p);
assert_eq!(inc.neighbors(0), &[0]);
}
#[test]
fn inequality_incidence_filters_equalities() {
let p = probe(2, 2, &[0, 0, 1], &[0, 1, 0], &[1.0, 0.0], &[1.0, 5.0]);
let ineq = InequalityIncidence::from_probe(&p);
assert_eq!(ineq.n_ineq_rows(), 1);
assert_eq!(ineq.ineq_row_inner_idx, vec![1]);
assert_eq!(ineq.neighbors(0), &[0]);
assert!(ineq.var_in_inequality(0));
assert!(!ineq.var_in_inequality(1));
}
#[test]
fn inequality_incidence_range_row() {
let p = probe(2, 1, &[0, 0], &[0, 1], &[-2.0], &[5.0]);
let ineq = InequalityIncidence::from_probe(&p);
assert_eq!(ineq.n_ineq_rows(), 1);
assert_eq!(ineq.neighbors(0), &[0, 1]);
assert_eq!(ineq.rows_for_var(0), &[0]);
assert_eq!(ineq.rows_for_var(1), &[0]);
}
#[test]
fn inequality_incidence_one_sided() {
let p = probe(1, 1, &[0], &[0], &[-1e19], &[5.0]);
let ineq = InequalityIncidence::from_probe(&p);
assert_eq!(ineq.n_ineq_rows(), 1);
assert_eq!(ineq.neighbors(0), &[0]);
}
}