use std::collections::HashSet;
use pounce_common::types::Number;
use crate::btf::BlockTriangularBlock;
use crate::incidence::InequalityIncidence;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AuxiliaryCouplingClass {
PureEquality,
ObjectiveCoupled,
InequalityCoupled,
ObjectiveAndInequalityCoupled,
}
pub fn objective_gradient_support(grad_f: &[Number], zero_tol: Number) -> HashSet<usize> {
grad_f
.iter()
.enumerate()
.filter_map(|(i, &g)| if g.abs() > zero_tol { Some(i) } else { None })
.collect()
}
pub fn classify_block(
block: &BlockTriangularBlock,
inequalities: &InequalityIncidence,
obj_grad_support: &HashSet<usize>,
) -> AuxiliaryCouplingClass {
let in_obj = block.cols.iter().any(|c| obj_grad_support.contains(c));
let in_ineq = block
.cols
.iter()
.any(|&c| inequalities.var_in_inequality(c));
match (in_obj, in_ineq) {
(false, false) => AuxiliaryCouplingClass::PureEquality,
(true, false) => AuxiliaryCouplingClass::ObjectiveCoupled,
(false, true) => AuxiliaryCouplingClass::InequalityCoupled,
(true, true) => AuxiliaryCouplingClass::ObjectiveAndInequalityCoupled,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::incidence::ProbeView;
use pounce_common::types::{Index, Number};
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,
}
}
fn block(cols: Vec<usize>) -> BlockTriangularBlock {
BlockTriangularBlock {
eq_rows: cols.clone(),
cols,
}
}
#[test]
fn coupling_pure_equality() {
let p = probe(2, 2, &[0, 1], &[0, 1], &[0.0, 0.0], &[0.0, 0.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj = HashSet::new();
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::PureEquality
);
}
#[test]
fn coupling_objective_only() {
let p = probe(2, 2, &[0, 1], &[0, 1], &[0.0, 0.0], &[0.0, 0.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj: HashSet<usize> = [0].into_iter().collect();
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::ObjectiveCoupled
);
}
#[test]
fn coupling_inequality_only() {
let p = probe(2, 2, &[0, 0, 1], &[0, 1, 0], &[0.0, 0.0], &[0.0, 5.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj = HashSet::new();
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::InequalityCoupled
);
}
#[test]
fn coupling_both() {
let p = probe(2, 2, &[0, 0, 1], &[0, 1, 0], &[0.0, 0.0], &[0.0, 5.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj: HashSet<usize> = [1].into_iter().collect();
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::ObjectiveAndInequalityCoupled
);
}
#[test]
fn coupling_partial_block_inequality() {
let p = probe(2, 2, &[0, 0, 1], &[0, 1, 1], &[0.0, 0.0], &[0.0, 5.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj = HashSet::new();
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::InequalityCoupled
);
}
#[test]
fn coupling_zero_grad_tolerance() {
let support = objective_gradient_support(&[1e-16, 0.0], 1e-12);
assert!(support.is_empty());
let p = probe(2, 1, &[0], &[0], &[0.0], &[0.0]);
let ineq = InequalityIncidence::from_probe(&p);
let b = block(vec![0, 1]);
assert_eq!(
classify_block(&b, &ineq, &support),
AuxiliaryCouplingClass::PureEquality
);
}
#[test]
fn coupling_empty_block() {
let p = probe(2, 1, &[0], &[0], &[0.0], &[0.0]);
let ineq = InequalityIncidence::from_probe(&p);
let obj: HashSet<usize> = [0, 1].into_iter().collect();
let b = BlockTriangularBlock::default();
assert_eq!(
classify_block(&b, &ineq, &obj),
AuxiliaryCouplingClass::PureEquality
);
}
#[test]
fn coupling_grad_support_helper() {
let support = objective_gradient_support(&[2.5, 0.0, -1e-3, 1e-15], 1e-9);
let expected: HashSet<usize> = [0, 2].into_iter().collect();
assert_eq!(support, expected);
}
}