use super::super::*;
use crate::problem::{ConstraintType, SolveStatus};
use crate::sparse::CscMatrix;
#[test]
fn test_emptycol_skip_strict_linear_only_x0_and_nonconvex_x1() {
let q = CscMatrix::from_triplets(&[1], &[1], &[-2.0_f64], 2, 2).unwrap();
let c = vec![1.0_f64, 3.0_f64];
let a = CscMatrix::new(0, 2);
let b: Vec<f64> = vec![];
let bounds = vec![(-2.0_f64, 2.0_f64), (-2.0_f64, 2.0_f64)];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
let opts = SolverOptions {
timeout_secs: Some(5.0),
..Default::default()
};
let result = solve_qp_with(&problem, &opts);
assert!(
matches!(
result.status,
SolveStatus::LocallyOptimal | SolveStatus::Optimal
),
"expected LocallyOptimal/Optimal, got {:?}",
result.status
);
assert!((result.solution[0] - (-2.0)).abs() < 1e-5, "x[0]: {}", result.solution[0]);
assert!((result.solution[1] - (-2.0)).abs() < 1e-5, "x[1]: {}", result.solution[1]);
let qx = problem.q.mat_vec_mul(&result.solution).unwrap();
let bd = &result.bound_duals;
assert_eq!(bd.len(), 4, "expected bd len=4, got {}", bd.len());
let bc0 = -bd[0] + bd[2];
let bc1 = -bd[1] + bd[3];
let r0 = (qx[0] + problem.c[0] + bc0).abs();
let r1 = (qx[1] + problem.c[1] + bc1).abs();
assert!(r0 < 1e-4, "x[0] stationarity must be ~0, got {:.3e}", r0);
assert!(r1 < 1e-4, "x[1] stationarity must be ~0, got {:.3e}", r1);
}
#[test]
fn test_emptycol_skip_strict_lp_like_full_elimination() {
let q = CscMatrix::new(2, 2);
let c = vec![1.0_f64, 0.0_f64];
let a = CscMatrix::new(0, 2);
let b: Vec<f64> = vec![];
let bounds = vec![(0.0_f64, 1.0_f64), (0.0_f64, 1.0_f64)];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
let opts = SolverOptions {
timeout_secs: Some(5.0),
..Default::default()
};
let result = solve_qp_with(&problem, &opts);
assert_eq!(result.status, SolveStatus::Optimal, "got {:?}", result.status);
assert!(result.solution[0].abs() < 1e-6, "x[0]: {}", result.solution[0]);
}
#[test]
fn test_emptycol_skip_strict_truly_empty_col_preserved() {
let q = CscMatrix::new(2, 2);
let c = vec![0.0_f64, 0.0_f64];
let a = CscMatrix::from_triplets(&[0], &[1], &[1.0_f64], 1, 2).unwrap();
let b = vec![0.5_f64];
let bounds = vec![(0.0_f64, 1.0_f64), (0.0_f64, 1.0_f64)];
let problem = QpProblem::new(
q,
c,
a,
b,
bounds,
vec![ConstraintType::Eq],
)
.unwrap();
let opts = SolverOptions {
timeout_secs: Some(5.0),
..Default::default()
};
let result = solve_qp_with(&problem, &opts);
assert_eq!(result.status, SolveStatus::Optimal, "got {:?}", result.status);
assert!((result.solution[1] - 0.5).abs() < 1e-6, "x[1]: {}", result.solution[1]);
}
#[test]
fn test_emptycol_skip_strict_with_active_constraint_unchanged() {
let q = CscMatrix::from_triplets(&[1], &[1], &[-2.0_f64], 2, 2).unwrap();
let c = vec![1.0_f64, 3.0_f64];
let a = CscMatrix::from_triplets(&[0], &[0], &[1.0_f64], 1, 2).unwrap();
let b = vec![5.0_f64]; let bounds = vec![(-2.0_f64, 2.0_f64), (-2.0_f64, 2.0_f64)];
let problem = QpProblem::new(
q,
c,
a,
b,
bounds,
vec![ConstraintType::Le],
)
.unwrap();
let opts = SolverOptions {
timeout_secs: Some(5.0),
..Default::default()
};
let result = solve_qp_with(&problem, &opts);
assert!(
matches!(
result.status,
SolveStatus::LocallyOptimal | SolveStatus::Optimal
),
"got {:?}",
result.status
);
assert!((result.solution[0] - (-2.0)).abs() < 1e-5);
assert!((result.solution[1] - (-2.0)).abs() < 1e-5);
let bd = &result.bound_duals;
assert_eq!(bd.len(), 4);
assert!(bd[0] > 0.5, "bd_lb[0] should be ≈1, got {}", bd[0]);
}