pub(crate) fn activity_range(
entries: &[(usize, f64)],
bounds: &[(f64, f64)],
exclude_col: Option<usize>,
) -> (f64, f64, bool, bool) {
let mut row_lb = 0.0f64;
let mut row_ub = 0.0f64;
let mut lb_finite = true;
let mut ub_finite = true;
for &(j, a_ij) in entries {
if Some(j) == exclude_col {
continue;
}
let (lb_j, ub_j) = bounds[j];
if a_ij > 0.0 {
if lb_j == f64::NEG_INFINITY {
lb_finite = false;
} else if lb_finite {
row_lb += a_ij * lb_j;
}
if ub_j == f64::INFINITY {
ub_finite = false;
} else if ub_finite {
row_ub += a_ij * ub_j;
}
} else if a_ij < 0.0 {
if ub_j == f64::INFINITY {
lb_finite = false;
} else if lb_finite {
row_lb += a_ij * ub_j;
}
if lb_j == f64::NEG_INFINITY {
ub_finite = false;
} else if ub_finite {
row_ub += a_ij * lb_j;
}
}
}
(row_lb, row_ub, lb_finite, ub_finite)
}
#[cfg(test)]
mod tests {
use super::*;
const NEG_INF: f64 = f64::NEG_INFINITY;
const POS_INF: f64 = f64::INFINITY;
#[test]
fn finite_bounds_positive_and_negative_coeffs() {
let entries = vec![(0, 2.0), (1, -3.0)];
let bounds = vec![(1.0, 4.0), (-2.0, 5.0)];
let (lb, ub, lf, uf) = activity_range(&entries, &bounds, None);
assert!(lf && uf);
assert!((lb - (-13.0)).abs() < 1e-12);
assert!((ub - 14.0).abs() < 1e-12);
}
#[test]
fn infinite_lower_bound_marks_lb_infinite_for_positive_coeff() {
let entries = vec![(0, 1.5)];
let bounds = vec![(NEG_INF, 10.0)];
let (_, ub, lf, uf) = activity_range(&entries, &bounds, None);
assert!(!lf);
assert!(uf);
assert!((ub - 15.0).abs() < 1e-12);
}
#[test]
fn infinite_upper_bound_with_negative_coeff_flips_lb_flag() {
let entries = vec![(0, -2.0)];
let bounds = vec![(1.0, POS_INF)];
let (_, ub, lf, uf) = activity_range(&entries, &bounds, None);
assert!(!lf);
assert!(uf);
assert!((ub - (-2.0)).abs() < 1e-12);
}
#[test]
fn zero_coefficient_is_skipped() {
let entries = vec![(0, 0.0), (1, 1.0)];
let bounds = vec![(NEG_INF, POS_INF), (2.0, 3.0)];
let (lb, ub, lf, uf) = activity_range(&entries, &bounds, None);
assert!(lf && uf);
assert!((lb - 2.0).abs() < 1e-12);
assert!((ub - 3.0).abs() < 1e-12);
}
#[test]
fn exclude_col_skips_target_entry() {
let entries = vec![(0, 2.0), (1, -3.0)];
let bounds = vec![(1.0, 4.0), (-2.0, 5.0)];
let (lb, ub, lf, uf) = activity_range(&entries, &bounds, Some(1));
assert!(lf && uf);
assert!((lb - 2.0).abs() < 1e-12);
assert!((ub - 8.0).abs() < 1e-12);
}
#[test]
fn empty_row_yields_zero_finite() {
let (lb, ub, lf, uf) = activity_range(&[], &[(0.0, 1.0)], None);
assert!(lf && uf);
assert_eq!(lb, 0.0);
assert_eq!(ub, 0.0);
}
#[test]
fn both_directions_infinite() {
let entries = vec![(0, 1.0)];
let bounds = vec![(NEG_INF, POS_INF)];
let (_, _, lf, uf) = activity_range(&entries, &bounds, None);
assert!(!lf && !uf);
}
}