diff --git a/Cargo.toml b/Cargo.toml index 2d7f34e..be23a8e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ minilp = ["microlp"] # minilp is not maintained anymore, we use the microlp fork [dependencies] coin_cbc = { version = "0.1", optional = true, default-features = false } -microlp = { version = "0.2.4", optional = true } +microlp = { version = "0.2.5", optional = true } lpsolve = { version = "0.1", optional = true } highs = { version = "1.5.0", optional = true } russcip = { version = "0.4.1", optional = true } diff --git a/README.md b/README.md index 5977a3a..9479f45 100644 --- a/README.md +++ b/README.md @@ -68,15 +68,15 @@ This library offers an abstraction over multiple solvers. By default, it uses [c you can also activate other solvers using cargo features. | solver feature name | integer variables | no C compiler\* | no additional libs\*\* | fast | WASM | -| ---------------------- | ----------------- | --------------- | ---------------------- | ---- |---------| -| [`coin_cbc`][cbc] | ✅ | ✅ | ❌ | ✅ | ❌ | -| [`highs`][highs] | ✅ | ❌ | ✅\+ | ✅ | ❌ | -| [`lpsolve`][lpsolve] | ✅ | ❌ | ✅ | ❌ | ❌ | -| [`microlp`][microlp] | ❌ | ✅ | ✅ | ❌ | ✅ | -| [`lp-solvers`][lps] | ✅ | ✅ | ✅ | ❌ | ❌ | -| [`scip`][scip] | ✅ | ✅ | ❌ | ✅ | ❌ | -| [`cplex-rs`][cplex] | ✅ | ❌ | ✅\+\+ | ✅ | ❌ | -| [`clarabel`][clarabel] | ❌ | ✅ | ✅ | ✅ | ✅\+\+\+ | +| ---------------------- |-------------------| --------------- | ---------------------- | ---- |---------| +| [`coin_cbc`][cbc] | ✅ | ✅ | ❌ | ✅ | ❌ | +| [`highs`][highs] | ✅ | ❌ | ✅\+ | ✅ | ❌ | +| [`lpsolve`][lpsolve] | ✅ | ❌ | ✅ | ❌ | ❌ | +| [`microlp`][microlp] | ✅ | ✅ | ✅ | ❌ | ✅ | +| [`lp-solvers`][lps] | ✅ | ✅ | ✅ | ❌ | ❌ | +| [`scip`][scip] | ✅ | ✅ | ❌ | ✅ | ❌ | +| [`cplex-rs`][cplex] | ✅ | ❌ | ✅\+\+ | ✅ | ❌ | +| [`clarabel`][clarabel] | ❌ | ✅ | ✅ | ✅ | ✅\+\+\+ | - \* no C compiler: builds with only cargo, without requiring you to install a C compiler - \*\* no additional libs: works without additional libraries at runtime, all the dependencies are statically linked diff --git a/src/solvers/microlp.rs b/src/solvers/microlp.rs index 9fada9d..8cdcd7f 100644 --- a/src/solvers/microlp.rs +++ b/src/solvers/microlp.rs @@ -1,7 +1,5 @@ //! A solver that uses [microlp](https://docs.rs/microlp), a pure rust solver. -use std::panic::catch_unwind; - use microlp::Error; use crate::variable::{UnsolvedProblem, VariableDefinition}; @@ -23,7 +21,6 @@ pub fn microlp(to_solve: UnsolvedProblem) -> MicroLpProblem { ObjectiveDirection::Maximisation => microlp::OptimizationDirection::Maximize, ObjectiveDirection::Minimisation => microlp::OptimizationDirection::Minimize, }); - let mut integers: Vec = vec![]; let variables: Vec = variables .iter_variables_with_def() .map( @@ -37,18 +34,17 @@ pub fn microlp(to_solve: UnsolvedProblem) -> MicroLpProblem { }, )| { let coeff = *objective.linear.coefficients.get(&var).unwrap_or(&0.); - let var = problem.add_var(coeff, (min, max)); if is_integer { - integers.push(var); + problem.add_integer_var(coeff, (min as i32, max as i32)) + } else { + problem.add_var(coeff, (min, max)) } - var }, ) .collect(); MicroLpProblem { problem, variables, - integers, n_constraints: 0, } } @@ -57,7 +53,6 @@ pub fn microlp(to_solve: UnsolvedProblem) -> MicroLpProblem { pub struct MicroLpProblem { problem: microlp::Problem, variables: Vec, - integers: Vec, n_constraints: usize, } @@ -73,12 +68,7 @@ impl SolverModel for MicroLpProblem { type Error = ResolutionError; fn solve(self) -> Result { - let mut solution = self.problem.solve()?; - for int_var in self.integers { - solution = catch_unwind(|| solution.add_gomory_cut(int_var)).map_err(|_| { - ResolutionError::Other("microlp does not support integer variables") - })??; - } + let solution = self.problem.solve()?; Ok(MicroLpSolution { solution, variables: self.variables, @@ -154,4 +144,25 @@ mod tests { .unwrap(); assert_eq!((solution.value(x), solution.value(y)), (0.5, 3.)) } + + #[test] + fn can_solve_milp() { + let mut vars = variables!(); + + let x = vars.add(variable().clamp(2, f64::INFINITY)); + let y = vars.add(variable().clamp(0, 7)); + let z = vars.add(variable().integer().clamp(0, f64::INFINITY)); + + let solution = vars + .maximise(50 * x + 40 * y + 45 * z) + .using(microlp) + .with((3 * x + 2 * y + z) << 20) + .with((2 * x + y + 3 * z) << 15) + .solve() + .unwrap(); + assert_eq!( + (solution.value(x), solution.value(y), solution.value(z)), + (2.0, 6.5, 1.0) + ) + } } diff --git a/src/variable.rs b/src/variable.rs index c5fad0b..2c9b4f0 100644 --- a/src/variable.rs +++ b/src/variable.rs @@ -143,7 +143,7 @@ impl VariableDefinition { /// # use good_lp::{ProblemVariables, variable, default_solver, SolverModel, Solution}; /// let mut problem = ProblemVariables::new(); /// let x = problem.add(variable().integer().min(0).max(2.5)); - /// # if cfg!(not(any(feature = "microlp", feature="clarabel"))) { + /// # if cfg!(not(any(feature="clarabel"))) { /// let solution = problem.maximise(x).using(default_solver).solve().unwrap(); /// // x is bound to [0; 2.5], but the solution is x=2 because x needs to be an integer /// assert_eq!(solution.value(x), 2.); @@ -164,7 +164,7 @@ impl VariableDefinition { /// let mut problem = ProblemVariables::new(); /// let x = problem.add(variable().binary()); /// let y = problem.add(variable().binary()); - /// if cfg!(not(any(feature = "microlp", feature="clarabel"))) { + /// if cfg!(not(any(feature="clarabel"))) { /// let solution = problem.maximise(x + y).using(default_solver).solve().unwrap(); /// assert_eq!(solution.value(x), 1.); /// assert_eq!(solution.value(y), 1.);