https://github.com/rust-or/rust-lp-modeler
Lp modeler written in Rust
https://github.com/rust-or/rust-lp-modeler
formulation glpk linear-models linear-programming lp-modeler rust rust-library solver
Last synced: 5 months ago
JSON representation
Lp modeler written in Rust
- Host: GitHub
- URL: https://github.com/rust-or/rust-lp-modeler
- Owner: rust-or
- License: mit
- Created: 2016-09-09T08:51:29.000Z (over 9 years ago)
- Default Branch: master
- Last Pushed: 2021-03-01T08:03:03.000Z (almost 5 years ago)
- Last Synced: 2025-09-11T05:09:23.196Z (6 months ago)
- Topics: formulation, glpk, linear-models, linear-programming, lp-modeler, rust, rust-library, solver
- Language: Rust
- Size: 26.9 MB
- Stars: 103
- Watchers: 10
- Forks: 29
- Open Issues: 9
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# lp-modeler
[](http://opensource.org/licenses/MIT)
[](https://travis-ci.org/jcavat/rust-lp-modeler)
[](https://gitter.im/rust-lp-modeler/community?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge)
[](https://github.com/jcavat/rust-lp-modeler/actions)
This project provides a mathematical programming modeling library for Rust.
An optimization problem (e.g. an integer or linear programme) can be formulated using familiar Rust syntax (see examples), and written into a universal [LP model format](https://www.gurobi.com/documentation/8.0/refman/lp_format.html).
This can then be processed by a mixed integer programming solver.
Presently supported solvers that require a [separate installation (see below the examples section)](#installing-external-solvers) to be present at runtime of your `lp_modeler`-based project are:
* [COIN-OR cbc](https://github.com/coin-or/Cbc)
* [Gurobi](https://www.gurobi.com/documentation/)
* [GLPK](https://www.gnu.org/software/glpk/)
Presently supported solvers that you can import as Rust crates (as [optional features](https://doc.rust-lang.org/cargo/reference/features.html)) are:
* [minilp](https://docs.rs/minilp/latest/minilp/)
* [coin_cbc](https://docs.rs/coin_cbc/latest/coin_cbc/) (requires the `Cbc` library files to be present at compile time of your `lp_modeler`-based project, see the [`coin_cbc` project README](https://github.com/KardinalAI/coin_cbc) for how to do this)
This project is inspired by [COIN-OR PuLP](http://www.coin-or.org/PuLP/ "Coin-Or PuLP website") which provides
such a library for Python.
## Usage
These examples present a formulation (in LP model format), and demonstrate the Rust code required to generate this formulation. Code can be found in [tests/problems.rs](tests/problems.rs).
### Example 1 - Simple model
#### Formulation
```
\ One Problem
Maximize
10 a + 20 b
Subject To
c1: 500 a + 1200 b + 1500 c <= 10000
c2: a - b <= 0
Generals
a c b
End
```
#### Rust code
```rust
extern crate lp_modeler;
use lp_modeler::solvers::{CbcSolver, SolverTrait};
use lp_modeler::dsl::*;
use lp_modeler::constraint;
fn main() {
// Define problem variables
let ref a = LpInteger::new("a");
let ref b = LpInteger::new("b");
let ref c = LpInteger::new("c");
// Define problem and objective sense
let mut problem = LpProblem::new("One Problem", LpObjective::Maximize);
// Objective Function: Maximize 10*a + 20*b
problem += 10.0 * a + 20.0 * b;
// Constraint: 500*a + 1200*b + 1500*c <= 10000
problem += constraint!(500*a + 1200*b + 1500*c <= 10000);
// Constraint: a <= b
problem += constraint!(a <= b);
// Specify solver
let solver = CbcSolver::new();
// Run optimisation and process output hashmap
match solver.run(&problem) {
Ok(solution) => {
println!("Status {:?}", solution.status);
for (name, value) in solution.results.iter() {
println!("value of {} = {}", name, value);
}
},
Err(msg) => println!("{}", msg),
}
}
```
To generate the LP file which is shown above:
```rust
problem.write_lp("problem.lp")
```
### Example 2 - An Assignment model
#### Formulation
This more complex formulation programmatically generates the expressions for the objective and constraints.
We wish to maximise the quality of the pairing between a group of men and women, based on their mutual compatibility score. Each man must be assigned to exactly one woman, and vice versa.
###### Compatibility Score Matrix
| | Abe | Ben | Cam |
| --- | --- | --- | --- |
| **Deb** | 50 | 60 | 60 |
| **Eve** | 75 | 95 | 70 |
| **Fay** | 75 | 80 | 80 |
This problem is formulated as an [Assignment Problem](https://en.wikipedia.org/wiki/Assignment_problem).
#### Rust code
```rust
extern crate lp_modeler;
use std::collections::HashMap;
use lp_modeler::dsl::*;
use lp_modeler::solvers::{SolverTrait, CbcSolver};
fn main() {
// Problem Data
let men = vec!["A", "B", "C"];
let women = vec!["D", "E", "F"];
let compatibility_score: HashMap<(&str, &str),f32> = vec![
(("A", "D"), 50.0),
(("A", "E"), 75.0),
(("A", "F"), 75.0),
(("B", "D"), 60.0),
(("B", "E"), 95.0),
(("B", "F"), 80.0),
(("C", "D"), 60.0),
(("C", "E"), 70.0),
(("C", "F"), 80.0),
].into_iter().collect();
// Define Problem
let mut problem = LpProblem::new("Matchmaking", LpObjective::Maximize);
// Define Variables
let vars: HashMap<(&str,&str), LpBinary> =
men.iter()
.flat_map(|&m| women.iter()
.map(move |&w| {
let key = (m,w);
let value = LpBinary::new(&format!("{}_{}", m,w));
(key, value)
}))
.collect();
// Define Objective Function
let obj_vec: Vec = {
vars.iter().map( |(&(m,w), bin)| {
let &coef = compatibility_score.get(&(m, w)).unwrap();
coef * bin
} )
}.collect();
problem += obj_vec.sum();
// Define Constraints
// - constraint 1: Each man must be assigned to exactly one woman
for &m in &men{
problem += sum(&women, |&w| vars.get(&(m,w)).unwrap() ).equal(1);
}
// - constraint 2: Each woman must be assigned to exactly one man
for &w in &women{
problem += sum(&men, |&m| vars.get(&(m,w)).unwrap() ).equal(1);
}
// Run Solver
let solver = CbcSolver::new();
let result = solver.run(&problem);
// Compute final objective function value
// (terminate if error, or assign status & variable values)
assert!(result.is_ok(), result.unwrap_err());
let (status, results) = result.unwrap();
let mut obj_value = 0f32;
for (&(m, w), var) in &vars{
let obj_coef = compatibility_score.get(&(m, w)).unwrap();
let var_value = results.get(&var.name).unwrap();
obj_value += obj_coef * var_value;
}
// Print output
println!("Status: {:?}", status);
println!("Objective Value: {}", obj_value);
for (var_name, var_value) in &results{
let int_var_value = *var_value as u32;
if int_var_value == 1{
println!("{} = {}", var_name, int_var_value);
}
}
}
```
This code computes the objective function value and processes the output to print the chosen pairing of men and women:
```
Status: Optimal
Objective Value: 230
B_E = 1
C_D = 1
A_F = 1
```
## installing external solvers
### installing conda (package manager)
If you want the latest release version of Cbc, Gurobi or GLPK, the easiest cross-platform installation pathway should be via [conda](https://docs.conda.io/en/latest/).
Importantly, this does not require admin rights on the system you want to install it on.
All you need to do is [install conda](https://docs.conda.io/projects/conda/en/latest/user-guide/install/index.html).
Once this is done, use the respective conda command for the solver you want to use (see below).
### COIN-OR Cbc
#### latest release (via conda)
To get the latest Cbc release for your system with conda (installation [see above](#installing-conda-package-manager)), use this command:
```
conda create -n coin-or-cbc -c conda-forge coin-or-cbc
```
Then activating the newly created environment will make the `cbc` executable available:
```
conda activate coin-or-cbc
```
#### latest release (via coinbrew)
To get the latest Cbc release, including the .
We recommend using COIN-OR's `coinbrew`, as described here:
https://coin-or.github.io/user_introduction#building-from-source
#### latest commit (via coinbrew)
To get the very latest Cbc version, including unreleased bug fixes, you will need to [build it from source](https://coin-or.github.io/user_introduction#building-from-source).
We recommend using COIN-OR's `coinbrew`, as described here:
https://coin-or.github.io/user_introduction#building-from-source
### GLPK
#### recent release (via conda)
To get a recent release of GLPK for your system with conda, use this command:
```
conda create -n glpk -c conda-forge glpk
```
Then activating the newly created environment will make the `glpsol` executable available:
```
conda activate glpk
```
### Gurobi
#### latest release (via conda)
To use Gurobi, **you need to have a valid [license key](https://www.gurobi.com/downloads/)** and have it in a location that Gurobi can find it.
Once you have a valid license, you can get the latest Gurobi release for your system with conda, use this command:
```
conda create -n gurobi -c gurobi gurobi
```
Then activating the newly created environment will make the `gurobi_cl` executable available:
```
conda activate gurobi
```
## Changelog
### 0.5.0
* Add a native `minilp` impl to call the Rust native solver `minilp`
* Changed `coin_cbc`-based `NativeCbcSolver` to an optional feature
* Fix adding upper bounds to `NativeCbc`
* Add a `coinstraint!()` macro
* Add `AddAssign`, `SubAssign` and `MulAssign` traits
* Reworked various internal functions to remove recursions (fixes related stack overflows)
* Add install infos for the solvers to the docs
### 0.4.3
* Add a native coin-or impl (NativeCBCSolver) to call CoinOR CBC trough the C API.
### 0.4.2
* Fix incorrect simplification of (expr-c1)+c2
### 0.4.1
* Fix failed cbc parsing on infeasible solution
### 0.4.0
* Improve modules
* Remove maplit dependency
* All the features to write expressions and constraints are put into `dsl` module
* `use lp_modeler::dsl::*` is enough to write a system
* `use lp_modeler::solvers::*` is always used to choose a solver
* Add a `sum()` method for vector of `LpExpression`/`Into` instead of `lp_sum()` function
* Add a `sum()` function used in the form:
```rust
problem += sum(&vars, |&v| v * 10.0) ).le(10.0);
```
### 0.3.3
* Fix and improve error message for GLPK solution parsing
* Format code with rust fmt
### 0.3.3
* Add new examples in documentation
* Improve 0.0 comparison
### 0.3.1
* Add distributive property (ex: `3 * (a + b + 2) = 3*a + 3*b + 6`)
* Add trivial rules (ex: `3 * a * 0 = 0` or `3 + 0 = 3`)
* Add commutative property to simplify some computations
* Support for GLPK
### 0.3.0
* Functional lib with simple algebra properties
## Contributors
### Main contributor
* Joel Cavat [(jcavat)](https://github.com/jcavat)
### All contributions :heart:
* Thomas Vincent [(tvincent2)](https://github.com/tvincent2)
* Antony Phillips [(aphi)](https://github.com/aphi)
* Florian B. [(Lesstat)](https://github.com/Lesstat)
* Amila Welihinda [(amilajack)](https://github.com/amilajack)
* [(zappolowski)](https://github.com/zappolowski)
* Yisu Rem Wang [(remysucre)](https://github.com/remysucre)
* Tony Cox [(tony-cox)](https://github.com/tony-cox)
* [EdorianDark](https://github.com/EdorianDark)
* Colman Humphrey [(ColmanHumphrey)](https://github.com/ColmanHumphrey)
* Stephan Beyer [sbeyer](https://github.com/sbeyer)
* Ophir Lojkine [lovasoa](https://github.com/lovasoa)
* David Lähnemann [dlaehnemann](https://github.com/dlaehnemann)
## Further work
* Parse and provide the objective value
* Config for lp_solve and CPLEX
* It would be great to use some constraint for binary variables such as
* a && b which is the constraint a + b = 2
* a || b which is the constraint a + b >= 1
* a <=> b which is the constraint a = b
* a => b which is the constraint a <= b
* All these cases are easy with two constraints but more complex with expressions
* ...