use crate::error::{OptimizeError, OptimizeResult};
use scirs2_core::ndarray::{Array1, Array2};
pub mod branch_and_bound;
pub mod cutting_plane;
pub mod knapsack;
pub mod lp_relaxation;
pub mod milp_branch_and_bound;
pub use branch_and_bound::{BranchAndBoundOptions, BranchAndBoundSolver};
pub use cutting_plane::{CuttingPlaneOptions, CuttingPlaneSolver};
pub use lp_relaxation::LpRelaxationSolver;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IntegerKind {
Continuous,
Binary,
Integer,
}
#[derive(Debug, Clone)]
pub struct IntegerVariableSet {
pub kinds: Vec<IntegerKind>,
}
impl IntegerVariableSet {
pub fn new(n: usize) -> Self {
IntegerVariableSet {
kinds: vec![IntegerKind::Continuous; n],
}
}
pub fn all_integer(n: usize) -> Self {
IntegerVariableSet {
kinds: vec![IntegerKind::Integer; n],
}
}
pub fn all_binary(n: usize) -> Self {
IntegerVariableSet {
kinds: vec![IntegerKind::Binary; n],
}
}
pub fn set_kind(&mut self, i: usize, kind: IntegerKind) {
if i < self.kinds.len() {
self.kinds[i] = kind;
}
}
pub fn is_integer(&self, i: usize) -> bool {
match self.kinds.get(i) {
Some(IntegerKind::Integer) | Some(IntegerKind::Binary) => true,
_ => false,
}
}
pub fn len(&self) -> usize {
self.kinds.len()
}
pub fn is_empty(&self) -> bool {
self.kinds.iter().all(|k| *k == IntegerKind::Continuous)
}
}
#[derive(Debug, Clone)]
pub struct MipResult {
pub x: Array1<f64>,
pub fun: f64,
pub success: bool,
pub message: String,
pub nodes_explored: usize,
pub lp_solves: usize,
pub lower_bound: f64,
}
#[derive(Debug, Clone)]
pub struct LinearProgram {
pub c: Array1<f64>,
pub a_ub: Option<Array2<f64>>,
pub b_ub: Option<Array1<f64>>,
pub a_eq: Option<Array2<f64>>,
pub b_eq: Option<Array1<f64>>,
pub lower: Option<Array1<f64>>,
pub upper: Option<Array1<f64>>,
}
impl LinearProgram {
pub fn new(c: Array1<f64>) -> Self {
LinearProgram {
c,
a_ub: None,
b_ub: None,
a_eq: None,
b_eq: None,
lower: None,
upper: None,
}
}
pub fn n_vars(&self) -> usize {
self.c.len()
}
}
#[inline]
pub fn is_integer_valued(v: f64, tol: f64) -> bool {
(v - v.round()).abs() <= tol
}
pub fn most_fractional_variable(x: &[f64], var_set: &IntegerVariableSet) -> Option<usize> {
let mut best_idx = None;
let mut best_frac = -1.0_f64;
for (i, &xi) in x.iter().enumerate() {
if var_set.is_integer(i) {
let frac = (xi - xi.floor()).abs();
let frac = frac.min(1.0 - frac); if frac > 1e-8 && frac > best_frac {
best_frac = frac;
best_idx = Some(i);
}
}
}
best_idx
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_integer_variable_set_creation() {
let ivs = IntegerVariableSet::new(5);
assert_eq!(ivs.len(), 5);
assert!(ivs.is_empty());
for i in 0..5 {
assert!(!ivs.is_integer(i));
}
}
#[test]
fn test_integer_variable_set_all_integer() {
let ivs = IntegerVariableSet::all_integer(3);
assert!(!ivs.is_empty());
for i in 0..3 {
assert!(ivs.is_integer(i));
}
}
#[test]
fn test_integer_variable_set_mixed() {
let mut ivs = IntegerVariableSet::new(4);
ivs.set_kind(1, IntegerKind::Integer);
ivs.set_kind(3, IntegerKind::Binary);
assert!(!ivs.is_integer(0));
assert!(ivs.is_integer(1));
assert!(!ivs.is_integer(2));
assert!(ivs.is_integer(3));
}
#[test]
fn test_is_integer_valued() {
assert!(is_integer_valued(3.0, 1e-8));
assert!(is_integer_valued(3.0000000001, 1e-7));
assert!(!is_integer_valued(3.5, 1e-8));
assert!(!is_integer_valued(3.1, 1e-8));
}
#[test]
fn test_most_fractional_variable() {
let x = vec![1.0, 2.6, 3.1, 0.5];
let ivs = IntegerVariableSet::all_integer(4);
let idx = most_fractional_variable(&x, &ivs);
assert_eq!(idx, Some(3));
}
#[test]
fn test_linear_program_construction() {
use scirs2_core::ndarray::array;
let c = array![1.0, -2.0, 3.0];
let lp = LinearProgram::new(c);
assert_eq!(lp.n_vars(), 3);
}
}