use super::super::*;
use crate::problem::{SolveRoute, SolveStatus};
use crate::sparse::CscMatrix;
#[test]
fn is_zero_q_tiny_nonzero_returns_false() {
let q = CscMatrix::from_triplets(&[0], &[0], &[1e-13], 1, 1).unwrap();
let c = vec![0.0];
let a = CscMatrix::new(0, 1);
let b = vec![];
let bounds = vec![(f64::NEG_INFINITY, f64::INFINITY)];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
assert!(
!problem.is_zero_q(),
"is_zero_q must return false for a matrix with stored value 1e-13"
);
}
#[test]
fn is_zero_q_structural_empty_returns_true() {
let n = 3;
let q = CscMatrix::new(n, n);
let c = vec![0.0; n];
let a = CscMatrix::new(0, n);
let b = vec![];
let bounds = vec![(f64::NEG_INFINITY, f64::INFINITY); n];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
assert!(
problem.is_zero_q(),
"is_zero_q must return true for a structurally empty Q"
);
}
#[test]
fn tiny_nonzero_q_routes_to_qp_not_lp() {
let q = CscMatrix::from_triplets(&[0], &[0], &[2e-13], 1, 1).unwrap();
let c = vec![2e-11]; let a = CscMatrix::new(0, 1);
let b = vec![];
let bounds = vec![(f64::NEG_INFINITY, f64::INFINITY)];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
let result = solve_qp(&problem);
assert_eq!(
result.stats.route,
SolveRoute::QpIpm,
"tiny non-zero Q must dispatch to QpIpm, not LP; route={:?}, status={:?}",
result.stats.route,
result.status
);
assert_eq!(
result.status,
SolveStatus::Optimal,
"tiny non-zero Q (x*=-100) must be Optimal via QP solver; got {:?}",
result.status
);
}
#[test]
fn structural_zero_q_routes_to_lp() {
let n = 2;
let q = CscMatrix::new(n, n);
let c = vec![1.0, 2.0];
let a = CscMatrix::from_triplets(&[0, 0], &[0, 1], &[1.0, 1.0], 1, n).unwrap();
let b = vec![4.0];
let bounds = vec![(0.0, f64::INFINITY); n];
let problem = QpProblem::new_all_le(q, c, a, b, bounds).unwrap();
let result = solve_qp(&problem);
assert_eq!(result.status, SolveStatus::Optimal, "LP (Q=0) must be Optimal");
assert_eq!(
result.stats.route,
SolveRoute::LpForwardedFromQp,
"structural-zero Q must use LpForwardedFromQp route, got {:?}",
result.stats.route
);
}
#[test]
fn lp_conversion_error_returns_numerical_error_not_infeasible() {
use crate::options::SolverOptions;
use crate::qp::solve_as_lp;
let mut problem = QpProblem::new(
CscMatrix::new(1, 1),
vec![1.0],
CscMatrix::new(0, 1),
vec![],
vec![(0.0, 1.0)],
vec![],
)
.unwrap();
problem.bounds[0] = (f64::NAN, 1.0);
let result = solve_as_lp(&problem, &SolverOptions::default());
assert_eq!(
result.status,
SolveStatus::NumericalError,
"LpProblem conversion failure must return NumericalError, not {:?}",
result.status
);
assert_eq!(
result.stats.route,
SolveRoute::LpForwardedFromQp,
"route must be LpForwardedFromQp on conversion error, got {:?}",
result.stats.route
);
}