use pounce_common::types::{NLP_LOWER_BOUND_INF, NLP_UPPER_BOUND_INF};
use pounce_common::Number;
use pounce_qp::{BoundStatus, ConsStatus, WorkingSet};
#[allow(clippy::too_many_arguments)]
pub fn classify_working_set(
lambda_x: &[Number],
lambda_g: &[Number],
m_eq: usize,
x: &[Number],
x_l: &[Number],
x_u: &[Number],
g: &[Number],
g_l: &[Number],
g_u: &[Number],
mult_tol: Number,
primal_tol: Number,
) -> WorkingSet {
let n = lambda_x.len();
let m = lambda_g.len();
debug_assert_eq!(x.len(), n);
debug_assert_eq!(x_l.len(), n);
debug_assert_eq!(x_u.len(), n);
debug_assert_eq!(g.len(), m);
debug_assert_eq!(g_l.len(), m);
debug_assert_eq!(g_u.len(), m);
debug_assert!(m_eq <= m);
let mut bounds = Vec::with_capacity(n);
for i in 0..n {
let lo_fin = x_l[i] > NLP_LOWER_BOUND_INF;
let up_fin = x_u[i] < NLP_UPPER_BOUND_INF;
if lo_fin && up_fin && (x_u[i] - x_l[i]).abs() < primal_tol {
bounds.push(BoundStatus::Fixed);
continue;
}
let mu = lambda_x[i];
let at_lo = lo_fin && (x[i] - x_l[i]).abs() < primal_tol;
let at_up = up_fin && (x_u[i] - x[i]).abs() < primal_tol;
let status = if mu > mult_tol && at_lo {
BoundStatus::AtLower
} else if mu < -mult_tol && at_up {
BoundStatus::AtUpper
} else if at_lo && mu >= 0.0 {
BoundStatus::AtLower
} else if at_up && mu <= 0.0 {
BoundStatus::AtUpper
} else {
BoundStatus::Inactive
};
bounds.push(status);
}
let mut constraints = Vec::with_capacity(m);
for i in 0..m {
if i < m_eq {
constraints.push(ConsStatus::Equality);
continue;
}
let lo_fin = g_l[i] > NLP_LOWER_BOUND_INF;
let up_fin = g_u[i] < NLP_UPPER_BOUND_INF;
if lo_fin && up_fin && (g_u[i] - g_l[i]).abs() < primal_tol {
constraints.push(ConsStatus::Equality);
continue;
}
let mu = lambda_g[i];
let at_lo = lo_fin && (g[i] - g_l[i]).abs() < primal_tol;
let at_up = up_fin && (g_u[i] - g[i]).abs() < primal_tol;
let status = if mu > mult_tol && at_lo {
ConsStatus::AtLower
} else if mu < -mult_tol && at_up {
ConsStatus::AtUpper
} else if at_lo && mu >= 0.0 {
ConsStatus::AtLower
} else if at_up && mu <= 0.0 {
ConsStatus::AtUpper
} else {
ConsStatus::Inactive
};
constraints.push(status);
}
WorkingSet {
bounds,
constraints,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn classify_treats_nlp_bound_inf_sentinel_as_unbounded() {
let ws = classify_working_set(
&[0.0],
&[],
0,
&[-1.0e19],
&[NLP_LOWER_BOUND_INF],
&[NLP_UPPER_BOUND_INF],
&[],
&[],
&[],
1e-8,
1e-6,
);
assert_eq!(ws.bounds[0], BoundStatus::Inactive);
}
#[test]
fn classify_all_inactive_when_strictly_interior() {
let ws = classify_working_set(
&[0.0],
&[],
0,
&[0.5],
&[-1.0],
&[1.0],
&[],
&[],
&[],
1e-8,
1e-8,
);
assert_eq!(ws.bounds[0], BoundStatus::Inactive);
assert!(ws.constraints.is_empty());
}
#[test]
fn classify_lower_bound_active_when_primal_at_bound_and_mult_positive() {
let ws = classify_working_set(
&[2.0],
&[],
0,
&[0.0],
&[0.0],
&[1.0],
&[],
&[],
&[],
1e-8,
1e-8,
);
assert_eq!(ws.bounds[0], BoundStatus::AtLower);
}
#[test]
fn classify_upper_bound_active_when_primal_at_bound_and_mult_negative() {
let ws = classify_working_set(
&[-2.0],
&[],
0,
&[1.0],
&[0.0],
&[1.0],
&[],
&[],
&[],
1e-8,
1e-8,
);
assert_eq!(ws.bounds[0], BoundStatus::AtUpper);
}
#[test]
fn classify_fixed_when_bounds_equal() {
let ws = classify_working_set(
&[0.0],
&[],
0,
&[2.0],
&[2.0],
&[2.0],
&[],
&[],
&[],
1e-8,
1e-8,
);
assert_eq!(ws.bounds[0], BoundStatus::Fixed);
}
#[test]
fn classify_equality_constraint_always_active() {
let ws = classify_working_set(
&[],
&[1.0],
1,
&[],
&[],
&[],
&[5.0],
&[5.0],
&[5.0],
1e-8,
1e-8,
);
assert_eq!(ws.constraints[0], ConsStatus::Equality);
}
#[test]
fn classify_inequality_at_lower_bound() {
let ws = classify_working_set(
&[],
&[3.0],
0,
&[],
&[],
&[],
&[1.0],
&[1.0],
&[10.0],
1e-8,
1e-8,
);
assert_eq!(ws.constraints[0], ConsStatus::AtLower);
}
#[test]
fn classify_inequality_at_upper_bound() {
let ws = classify_working_set(
&[],
&[-3.0],
0,
&[],
&[],
&[],
&[10.0],
&[0.0],
&[10.0],
1e-8,
1e-8,
);
assert_eq!(ws.constraints[0], ConsStatus::AtUpper);
}
#[test]
fn classify_inactive_when_primal_off_bound_despite_large_multiplier() {
let ws = classify_working_set(
&[2.0],
&[],
0,
&[0.5],
&[0.0],
&[1.0],
&[],
&[],
&[],
1e-8,
1e-8,
);
assert_eq!(ws.bounds[0], BoundStatus::Inactive);
}
}