Skip to content

Commit

Permalink
Update microlp, adding integer variables (#66)
Browse files Browse the repository at this point in the history
* Update microlp, adding integer variables

* add integer variable tests
  • Loading branch information
Specy authored Nov 6, 2024
1 parent 4041609 commit cc7acdb
Show file tree
Hide file tree
Showing 4 changed files with 37 additions and 26 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
39 changes: 25 additions & 14 deletions src/solvers/microlp.rs
Original file line number Diff line number Diff line change
@@ -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};
Expand All @@ -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<microlp::Variable> = vec![];
let variables: Vec<microlp::Variable> = variables
.iter_variables_with_def()
.map(
Expand All @@ -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,
}
}
Expand All @@ -57,7 +53,6 @@ pub fn microlp(to_solve: UnsolvedProblem) -> MicroLpProblem {
pub struct MicroLpProblem {
problem: microlp::Problem,
variables: Vec<microlp::Variable>,
integers: Vec<microlp::Variable>,
n_constraints: usize,
}

Expand All @@ -73,12 +68,7 @@ impl SolverModel for MicroLpProblem {
type Error = ResolutionError;

fn solve(self) -> Result<Self::Solution, Self::Error> {
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,
Expand Down Expand Up @@ -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)
)
}
}
4 changes: 2 additions & 2 deletions src/variable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.);
Expand All @@ -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.);
Expand Down

0 comments on commit cc7acdb

Please sign in to comment.