Coder Social home page Coder Social logo

rust-lp-modeler's Introduction

lp-modeler

MIT license Build Status Gitter Github 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. This can then be processed by a mixed integer programming solver. Presently supported solvers that require a separate installation (see below the examples section) to be present at runtime of your lp_modeler-based project are:

Presently supported solvers that you can import as Rust crates (as optional features) are:

This project is inspired by COIN-OR PuLP 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.

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

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:

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.

Rust code

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<LpExpression> = {
       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. Importantly, this does not require admin rights on the system you want to install it on. All you need to do is install conda. 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), 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. 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 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<LpExpression> instead of lp_sum() function

  • Add a sum() function used in the form:

    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

All contributions ❤️

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
    • ...

rust-lp-modeler's People

Contributors

amilajack avatar aphi avatar carrascomj avatar colmanhumphrey avatar dginev avatar dlaehnemann avatar edoriandark avatar jcavat avatar lesstat avatar lovasoa avatar remysucre avatar sbeyer avatar tony-cox avatar tvincent2 avatar zappolowski avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

rust-lp-modeler's Issues

Must problem variables be of type "&str"?

Hi Developer,

I am writing the software that solves mixture integer programming.
In this software, an objective function maximizes the sum of the scores of possible certain index pairs(, as you show as an example of an assignment problem).
I noticed that your assignment problem example shows the error "Cannot open file" when I replace the type of men and women (&str) with u8. (e.g., men: "A", "B", "C" -> 1, 2, 3, women: "D", "E", "F" -> 4, 5, 6.)
Am I forced to use type "&str"?

Thank you in advance.

The solver will panic if a goal or constraint reduces to a constant expression

UPDATE: it seems the actual problem is literal expressions. Empty sums work fine, but they return a literal zero.

If a problem has an LpExpression::literal and no variables in a constraint or objective, trying to solve it will cause a panic.

Here's a reproduction:

// lp_modeler 0.5.0
use lp_modeler::dsl::*;
use lp_modeler::solvers::{CbcSolver, SolverTrait};

fn main() {
    let mut problem = LpProblem::new("problem", LpObjective::Maximize);

    problem += LpExpression::literal(0.0);

    let solver = CbcSolver::new();
    let _ = solver.run(&problem); // Here's where the panic happens
}

The backtrace is

thread 'main' panicked at 'Requested index out of bound of LpExpression vector. This should not happen.', /home/user/cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/dsl/variables.rs:368:21
stack backtrace:
   0: std::panicking::begin_panic
             at /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/std/src/panicking.rs:519:12
   1: lp_modeler::dsl::variables::LpExpression::expr_clone_at
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/dsl/variables.rs:368:21
   2: lp_modeler::dsl::variables::LpExpression::simplify
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/dsl/variables.rs:567:23
   3: <lp_modeler::dsl::variables::LpExpression as lp_modeler::format::lp_format::LpFileFormat>::to_lp_file_format
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/format/lp_format.rs:158:25
   4: lp_modeler::format::lp_format::objective_lp_file_block
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/format/lp_format.rs:61:44
   5: <lp_modeler::dsl::problem::LpProblem as lp_modeler::format::lp_format::LpFileFormat>::to_lp_file_format
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/format/lp_format.rs:25:27
   6: lp_modeler::format::lp_format::LpFileFormat::write_lp
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/format/lp_format.rs:12:22
   7: <lp_modeler::solvers::cbc::CbcSolver as lp_modeler::solvers::SolverTrait>::run
             at /home/user/.cargo/registry/src/github.com-1ecc6299db9ec823/lp-modeler-0.5.0/src/solvers/cbc.rs:138:9
   8: tmp::main
             at ./src/main.rs:13:13
   9: core::ops::function::FnOnce::call_once
             at /home/user/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/ops/function.rs:227:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Even if the problem is otherwise perfectly valid, adding such a constraint causes a panic.

// lp_modeler 0.5.0
use lp_modeler::dsl::*;
use lp_modeler::solvers::{CbcSolver, SolverTrait};

fn main() {
    // adapted from the README example
    let a = &LpInteger::new("a");
    let b = &LpInteger::new("b");
    let c = &LpInteger::new("c");
    let mut problem = LpProblem::new("One Problem", LpObjective::Maximize);
    problem += 10.0 * a + 20.0 * b;
    problem += (500 * a + 1200 * b + 1500 * c).le(10000);
    problem += a.le(b);

    // The problematic constraint
    problem += LpExpression::literal(0.0).equal(0);

    let solver = CbcSolver::new();
    let _ = solver.run(&problem); // panic right here
}

lp_sum creates a linked list instead of a balanced tree

lp_sum returns a deeply imbalanced expression tree of the form AddExpr(a, AddExpr(b, AddExpr(c, AddExpr(d)))), which then causes stack overflows when being processed by simplify (see #37).

lp_sum could return a balanced tree of depth log2(n) (instead of n), which would be less likely to cause stack overflows during processing.

`run` function from solver return a Solution `struct`

This would change the dsl API

from:

let (solver_status, values) = solver::run(&prob).unwrap();

to:

let solution = solver::run(&prob).unwrap();
  • status and hashmap values are parts of the Solution struct
  • Solution should refer to a problem (with constraints and objective)
  • a eval function can be call to eval the overall objective function of the problem

Glpk Solution format parser doesn't cover all cases

In the glpsol sometimes leaves blanks for the upper and lower bound of a variable which makes the read_solution method output "Incorrect solution format".

Example:

Problem:    
Rows:       3
Columns:    2
Non-zeros:  4
Status:     OPTIMAL
Objective:  obj = 1 (MAXimum)

   No.   Row name   St   Activity     Lower bound   Upper bound    Marginal
------ ------------ -- ------------- ------------- ------------- -------------
     1 c1           B              1             0               
     2 c2           NL             0             0                          -1 
     3 c3           NS             1             1             =             1 

   No. Column name  St   Activity     Lower bound   Upper bound    Marginal
------ ------------ -- ------------- ------------- ------------- -------------
     1 a            B              1                             
     2 b            B              0                             

Karush-Kuhn-Tucker optimality conditions:

KKT.PE: max.abs.err = 0.00e+00 on row 0
        max.rel.err = 0.00e+00 on row 0
        High quality

KKT.PB: max.abs.err = 0.00e+00 on row 0
        max.rel.err = 0.00e+00 on row 0
        High quality

KKT.DE: max.abs.err = 0.00e+00 on column 0
        max.rel.err = 0.00e+00 on column 0
        High quality

KKT.DB: max.abs.err = 0.00e+00 on row 0
        max.rel.err = 0.00e+00 on row 0
        High quality

End of output

Produced by the following code:

use lp_modeler::operations::LpOperations;
use lp_modeler::problem::{LpObjective, LpProblem};
use lp_modeler::solvers::{GlpkSolver, SolverTrait};
use lp_modeler::variables::LpContinuous;

fn main() {
    let a = &LpContinuous::new("a");
    let b = &LpContinuous::new("b");

    let mut problem = LpProblem::new("One Problem", LpObjective::Maximize);

    problem += a;
    problem += a.ge(0);
    problem += b.ge(0);

    problem += (1 * a + 1 * b).equal(1.0);

    let solver = GlpkSolver::new();

    match solver.run(&problem) {
        Ok((status, var_values)) => {
            println!("Status {:?}", status);
            for (name, value) in var_values.iter() {
                println!("value of {} = {}", name, value);
            }
        }
        Err(msg) => println!("{}", msg),
    }
}

I would be willing to write the necessary changes and tests in a pull request if you are interested.

Native CBC interface?

Hi @jcavat , and cross-posting to @oddg and @xiaowei-routific .

I recently encountered significant performance overhead when using the CBC solver via "rust-lp-modeler" and realized a chunk of that is likely due to using system calls, rather than the native ABI interface.

I next spotted a repository, coin-or-cbc-sys which has used rust-bindgen to generate Rust bindings for CBC. There is an obvious synergy here, in my opinion, where the lp-modeler crate could at the least have a feature attribute that allows using CBC via the native interface, avoiding the syscall overhead (and potentially finding other avenues for speedup).

I have very little experience with CBC itself, and only recently found a need for an LP component in a larger application. As it happens, I have hundreds to thousands of small (~10 to 20 constraints) problems, so any overhead in the call to CBC itself becomes very noticeable.

Also, while I don't have much CBC experience, I have developed a couple of Rust wrappers over C toolkits before, notably one for libxml, so could share some experience from that if relevant. E.g. I do find the cbc-sys repo a little to customized for my taste, since I am a linux user I would have been satisfied to simply require the development headers (e.g. coinor-libcbc-dev on Ubuntu/travis CI) and linked the bindgen code against them.

Edit: to give some concrete numbers, a basic test runs with ~5 simple/easy constraints per LP problem, and sees a second of extra runtime spent on every 110 problems, or 9ms per problem. That doesn't sound like much, but given these problems are near-trivial it feels like it can be a tenth of that runtime if I was to write a (super-naive and crude) solver equivalent in Rust myself. I feel it's quite an unusual performance complaint to make, so maybe I should just write that and see if my claim is actually accurate...

no 64 bit support

The problem I'm trying to solve unfortunately doesn't fit in 32 bit numbers, and I'm getting:

the trait `std::ops::Mul<&lp_modeler::variables::LpBinary>` is not implemented for `i64`

Would be great to have 64 bit number support =)

minilp solver?

Opening discussion: there is a native implementation of a LP solver in Rust: minilp. Supporting it would provide

  • a native experience, no external installation involved (glpk, cbc, etc.);
  • an useful fallback for higher level crates that rely on lp_modeler;
  • yet another solver to maintain.

Would this be interesting for the crate?

Simplify overflows the stack

I have a very long objective function (sum of 1000+ terms) which causes simplify(expr: &LpExpression) -> LpExpression to overflow the stack when adding the objective function to the problem. Similarly solver.run also triggers the overflow. I understand this might not be the standard use case, and there might not be an easy fix. But I'd like to know what the purpose of simplify is, and if there's a way I can manually get my function into its "normal form" so that I can remove the call to simplify.

Missing status values when solving with Cbc

Hello,

Currently, when a lp problem is solved using Cbc, the status of the result can only be Status::SubOptimal or Status::Optimal, meaning that Status::Unbounded, Status::Infeasible and Status::NotSolved are missing.

I should be able to propose a PR to add those missing statuses during the week.

Best regards,
Thomas

GLPK support

Hello,

I open this issue to let you know that I've started to implement GLPK support. Hopefully I should be able to send a PR next week.

Best regards,
Thomas

Weird issues modeling a problem

First of all: sorry for the shitty quality bug report.

I'm trying to model bitcoin coin selection with lp-modeler and have this which works completely as expected:

https://gist.github.com/RHavar/d2249af67e428befb966e4fcf7d95a17/92c7bb2bac02ec2147b2a1871be45a4391536cfb

(You can run it, and it also dump the .lp file.

Anyway, with a minor change, it fails with:
signal 6:

And with another minor change, it leads to bizarre results (giving a decimal output for a binary problem) with weird names.

It's probably easiest to see by going to the gist:
https://gist.github.com/RHavar/d2249af67e428befb966e4fcf7d95a17/a95eb5a35aede5871f1835b37bbf938700ee81e5

And the original version is the one that works, and the next two changes are the weird results

add support for highs

Hello !
I just finished a new rust binding to a cool solver: HiGHS. HiGHS is a performant C++ solver that leverages OpenMP to solve problems in parallel on multi-core machines.
It would be great if rust-lp-modeler could add support for this solver using the highs crate: https://crates.io/crates/highs

0.5 release

What is missing for the 0.5 release?

  • Adding new features to change log
    . [ ] Is there enough documentation for native backed?

Evaluate objective function without parsing

Provide a function that evaluate the objective function who may have been returned by the run function.

Maybe something like:

let (solver_status, values, objective) = solver::run(&prob).unwrap();

Derive Eq, Hash etc. for variables

It's common to map objects to variables, like in the Assignment Problem example. Sometimes one would also like to map a variable back to the object, like the BiMap here. For example, the user might want to look for the object associated with a variable once a solution is found. This inverse mapping would require variables like LpBinary to implement Eq and Hash.

Wrong parameters in Cbc command

Hello,

Commit b0a9ee1 changed the command used to run Cbc from cbc test.lp solve solution sol.sol(see b0a9ee1#diff-9088e7468441431402cc8f4e47955a14L113) to cbc ResultFile=sol.sol test.lp (see b0a9ee1#diff-9088e7468441431402cc8f4e47955a14R192).

While cbc executes the former properly, it fails to run the latter (maybe ResultFile is specific to Gurobi?).

I believe this specific command should then be reverted to its previous version.

Let me know if you want me to send a PR.

Thomas

crates.io old

The example from crates.io looks similar to the one in Readme.
The main difference is, that it doesn't compile...

Cbc cannot process generated LP file

Hello,

Using the LP file generated in main.rs, Cbc (2.8.14 from Debian Stretch) produces a solution where all variables and the objective value are equal to 0 (see sol.txt).

During the solution, Cbc complains about invalid row names:

Coin3007W ### CoinLpIO::is_invalid_name(): Name is empty
Coin3007W ### CoinLpIO::are_invalid_names(): Invalid name: vnames[3]: (null)
Coin3007W ### CoinLpIO::setLpDataRowAndColNames(): Invalid row names

In fact, in the LP file, the objective function is defined by:

Maximize
  10 a + 20 b

but Cbc apparently expects the objective function to have a name (which should normally be optional).

Adding a name:

Maximize
  obj: 10 a + 20 b

fixes the issue and allows Cbc to process the file and solve the problem correctly.

I believe a default name should be added to the objective function as it would fix this issue with Cbc and should be harmless for other solvers. Moreover, it is apparently what is done in Pulp. If it's ok for you, expect a PR soon. :)

Best regards,
Thomas

NativeCbcSolver failing

Just to document some comments in other issues/PRs that were closed, the NativeCbcSolver builds the wrong problem for some formulations.

The problem should be in the var_lit function which recursively extracts the coefficients and variables from the expressions.

Reproduce

Default branch uses CbcSolver, test passes.

git clone https://github.com/carrascomj/kair.git
cd kair
cargo test

Default branch uses NativeCbcSolver for tests, test fails (a different solution is returned).

git clone -b run-native https://github.com/carrascomj/kair.git
cd kair
cargo test

Simplification of expression including constants is incorrect

I encountered a problem when trying to use the constant part from split_constant_and_expr on expression containing constant values.

A simplified example:

expr1: a - 2
expr2: 1 - a

expr3: simplify(expr1 + expr2)

The correct simplification should yield for the constant part -1, but instead 1 is returned.

Contributors

@tvincent2 hi, I will add your name on the contributors list for Cargo and in the Readme.md. Do you prefer I put your name or your pseudo ?

Simplify the expression tree

What would you think about simplifying the expression tree by

  • Removing SubExpr (and replacing it with Add(Mul(-1, expr))
  • Replacing AddExpr(left, right) by AddExpr(Vec<expr>), to avoid creating deep trees (that cause #37)

This is not perfect, but should be easy to implement as a workaround for now. Would you accept such a PR ?

A more ambitious refactor that we can keep for later is to implement the expression as a simple HashMap<Variable, Coefficient>.

Support for distributive property

No distributive property support. Example: 3 * (x1 + x2) or 3 * (2 + x1) doens't work yet. They should be written rather like this 3*x1 + 3*x2 and 6 * x1 respectively.

add support for other model file formats than `.lp` (e.g. the `MPS` format)

Issues #73 and #72 demonstrate that the .lp file format is not very clearly specified. It might thus be more stable to use other more clearly file formats for any solvers that allow this. For solvers from the COIN-OR Suite (including cbc, but also see: https://coin-or.github.io/user_introduction), the best format is probably OSIL and according to slide 8 in this COIN-OR presentation, .nl might be another option.

OSIL

"an open, XML-based format used by the Optimization Services framework of COIN-OR"

This seems to be a very rigorously specified and very powerful format and all the tools in COIN-OR can handle it. It would require using some Rust xml parsing library and implementing the specification with it: there are some good hints in this recent blog post, so probably strong-xml or yaserde would be good. So implementing the format will be more involved.

All relevant info is on this webpage, including a link to the original paper describing it and the current stable specification:

.nl

"AMPL’s intermediate format that also supports non-linear modeling."

I guess, the complexity of implementing this would be somewhere in between the .lp file format and the OSIL .xml format. It might be a good compromise, as it seems that both COIN-OR and gurobi have capabilities of working with the .nl format.

Resources:

What do others think? Further format suggestions? Or objections?

Return map of symbolic variables to values in solver.run

Right now SolverTrait::run returns HashMap<String, f32>. Usually one might hope to do something with the variable in addition to printing it. In that case, returning a map from the symbolic variable object instead of a String would be more helpful. For example, here I maintain a bidirectional map between symbolic variables and a type X, and printout the value associated to the variable once I find a solution.

[Rust beginner] Easy way to construct expressions?

I am looking for an "easy" way to construct more complex expressions.
From the examples I was expecting that something like

   let mut lhs = LpExpression::EmptyExpr;
   lhs += 5.0 * some_variable;
   for i in certain_i_indices {
      for j in certain_j_indices {
         lhs += some_coeff_collection[i][j] * some_variable_collection[i][j];
      }
   }
   problem += lhs.le(some_value);

would work, but it doesn't.

Can you provide some examples that are "easy" enough for that case?

Stack overflow on adding objective function

Hi,

Many thanks for the great work on the crate! I am trying to use lp-modeler for a discrete optimisation course, and I am looking at a warehouse location problem. For small problem instances I have been able to successfully use lp-modeler with Cbc.

However for larger instances, I am getting a stack overflow when trying to set the objective function. In this case I have 1275 variables (25 facilities and 50 customers, perhaps the model itself could be improved too I'm not sure...).

Is it possible to use lp-modeler for this number of variables, and if so how?

Many thanks for your help,
Pierre Henry

Parsing of infeasible CBC solution fails

When having a (toy) model like

minimize
  obj: a + b
subject to
  c1: a + b <= 1
  c2: a + b >= 2
binaries
  a b
end

the resulting solution file looks like

Infeasible - objective value 2.00000000
**       0 a                      2                       0
      1 b                      0                       0

As the first line is additionally marked with leading '**' parsing fails, as the total number of fields isn't 4 anymore. Thus, an error is returned ("Incorrect solution format") although it could be parsed by simply ignoring the first field (the status itself should be sufficient to mark the result as being potentially unusable).

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.