use num_bigint::BigInt;
use num_rational::BigRational;
use num_traits::{Signed, Zero};
use oxiz_math::lp_core::{ConstraintSense, LPResult, LPSolver, OptDir};
use proptest::prelude::*;
fn lp_coeff_strategy() -> impl Strategy<Value = i64> {
-10i64..10i64
}
fn positive_coeff_strategy() -> impl Strategy<Value = i64> {
1i64..10i64
}
fn rat(n: i64) -> BigRational {
BigRational::from_integer(BigInt::from(n))
}
#[cfg(test)]
mod simplex_basic_properties {
use super::*;
proptest! {
#[test]
fn simplex_single_var_feasible(
c in lp_coeff_strategy(),
bound in positive_coeff_strategy()
) {
let mut lp = LPSolver::new();
let x = lp.new_continuous();
lp.set_objective(x, rat(c));
lp.set_direction(OptDir::Maximize);
lp.new_constraint([(x, rat(1))], ConstraintSense::Le, rat(bound));
lp.new_constraint([(x, rat(1))], ConstraintSense::Ge, rat(0));
let result = lp.solve();
match result {
LPResult::Optimal { objective, .. } => {
if c >= 0 {
prop_assert!(objective >= BigRational::zero());
}
if c > 0 {
prop_assert!(objective <= rat(c * bound + 1));
}
},
LPResult::Unbounded => {
},
LPResult::Infeasible => {
},
LPResult::Unknown => {
}
}
}
#[test]
fn simplex_zero_objective(bound in positive_coeff_strategy()) {
let mut lp = LPSolver::new();
let x = lp.new_continuous();
lp.set_objective(x, rat(0));
lp.set_direction(OptDir::Maximize);
lp.new_constraint([(x, rat(1))], ConstraintSense::Le, rat(bound));
let result = lp.solve();
if let LPResult::Optimal { objective, .. } = result {
prop_assert_eq!(objective, BigRational::zero());
}
}
}
}
#[cfg(test)]
mod simplex_optimality_properties {
use super::*;
proptest! {
#[test]
fn simplex_solution_satisfies_constraints(
c1 in lp_coeff_strategy(),
c2 in lp_coeff_strategy(),
b in positive_coeff_strategy()
) {
let mut lp = LPSolver::new();
let x1 = lp.new_continuous();
let x2 = lp.new_continuous();
lp.set_objective(x1, rat(c1));
lp.set_objective(x2, rat(c2));
lp.set_direction(OptDir::Maximize);
lp.new_constraint([(x1, rat(1)), (x2, rat(1))], ConstraintSense::Le, rat(b));
let result = lp.solve();
if let LPResult::Optimal { values, .. } = result {
let val1 = values.get(&x1).cloned().unwrap_or(BigRational::zero());
let val2 = values.get(&x2).cloned().unwrap_or(BigRational::zero());
prop_assert!(val1 >= BigRational::zero());
prop_assert!(val2 >= BigRational::zero());
prop_assert!(&val1 + &val2 <= rat(b));
}
}
#[test]
fn simplex_maximize_positive_gives_positive(
c in positive_coeff_strategy(),
bound in positive_coeff_strategy()
) {
let mut lp = LPSolver::new();
let x = lp.new_continuous();
lp.set_objective(x, rat(c));
lp.set_direction(OptDir::Maximize);
lp.new_constraint([(x, rat(1))], ConstraintSense::Le, rat(bound));
let result = lp.solve();
if let LPResult::Optimal { objective, .. } = result {
prop_assert!(objective >= BigRational::zero());
}
}
#[test]
fn simplex_min_equals_max_negative(
c in lp_coeff_strategy(),
bound in positive_coeff_strategy()
) {
let mut lp_min = LPSolver::new();
let x1 = lp_min.new_continuous();
lp_min.set_objective(x1, rat(c));
lp_min.set_direction(OptDir::Minimize);
lp_min.new_constraint([(x1, rat(1))], ConstraintSense::Le, rat(bound));
let mut lp_max = LPSolver::new();
let x2 = lp_max.new_continuous();
lp_max.set_objective(x2, rat(-c));
lp_max.set_direction(OptDir::Maximize);
lp_max.new_constraint([(x2, rat(1))], ConstraintSense::Le, rat(bound));
let result_min = lp_min.solve();
let result_max = lp_max.solve();
if let (LPResult::Optimal { objective: val_min, .. }, LPResult::Optimal { objective: val_max, .. }) = (result_min, result_max) {
prop_assert!((&val_min + &val_max).abs() < rat(1));
}
}
}
}
#[cfg(test)]
mod simplex_duality_properties {
use super::*;
proptest! {
#[test]
fn simplex_strong_duality_simple(b in positive_coeff_strategy()) {
let mut primal = LPSolver::new();
let x = primal.new_continuous();
primal.set_objective(x, rat(1));
primal.set_direction(OptDir::Maximize);
primal.new_constraint([(x, rat(1))], ConstraintSense::Le, rat(b));
primal.new_constraint([(x, rat(1))], ConstraintSense::Ge, rat(0));
let primal_result = primal.solve();
if let LPResult::Optimal { objective: primal_val, .. } = primal_result {
prop_assert!(primal_val >= BigRational::zero());
prop_assert!(primal_val <= rat(b + 1));
}
}
}
}
#[cfg(test)]
mod simplex_sensitivity_properties {
use super::*;
proptest! {
#[test]
fn simplex_rhs_sensitivity(
c in positive_coeff_strategy(),
b1 in 1i64..5i64,
delta in 1i64..5i64
) {
let b2 = b1 + delta;
let mut lp1 = LPSolver::new();
let x1 = lp1.new_continuous();
lp1.set_objective(x1, rat(c));
lp1.set_direction(OptDir::Maximize);
lp1.new_constraint([(x1, rat(1))], ConstraintSense::Le, rat(b1));
let mut lp2 = LPSolver::new();
let x2 = lp2.new_continuous();
lp2.set_objective(x2, rat(c));
lp2.set_direction(OptDir::Maximize);
lp2.new_constraint([(x2, rat(1))], ConstraintSense::Le, rat(b2));
let result1 = lp1.solve();
let result2 = lp2.solve();
if let (LPResult::Optimal { objective: val1, .. }, LPResult::Optimal { objective: val2, .. }) = (result1, result2) {
prop_assert!(val2 >= val1);
}
}
#[test]
fn simplex_obj_coeff_sensitivity(
c1 in 1i64..5i64,
delta in 1i64..5i64,
b in positive_coeff_strategy()
) {
let c2 = c1 + delta;
let mut lp1 = LPSolver::new();
let x1 = lp1.new_continuous();
lp1.set_objective(x1, rat(c1));
lp1.set_direction(OptDir::Maximize);
lp1.new_constraint([(x1, rat(1))], ConstraintSense::Le, rat(b));
let mut lp2 = LPSolver::new();
let x2 = lp2.new_continuous();
lp2.set_objective(x2, rat(c2));
lp2.set_direction(OptDir::Maximize);
lp2.new_constraint([(x2, rat(1))], ConstraintSense::Le, rat(b));
let result1 = lp1.solve();
let result2 = lp2.solve();
if let (LPResult::Optimal { objective: val1, .. }, LPResult::Optimal { objective: val2, .. }) = (result1, result2) {
prop_assert!(val2 >= val1);
}
}
}
}