lp_modeler/solvers/
mod.rs

1//! This module provides the interface to different solvers.
2//!
3//! Both [`coin_cbc`](https://docs.rs/coin_cbc/latest/coin_cbc/) and
4//! [`minilp`](https://docs.rs/minilp/0.2.2/minilp/) are available as cargo
5//! [features](https://doc.rust-lang.org/cargo/reference/features.html). To use
6//! them, specify your dependency to `lp_modeler` accordingly in your `Cargo.toml`
7//! (note the name difference of the `native_coin_cbc` feature for the `coin_cbc` crate):
8//! ```toml
9//! [dependencies.lp_modeler]
10//! version = "4.3"
11//! features = "native_coin_cbc"
12//! ```
13//! or:
14//! ```toml
15//! [dependencies.lp_modeler]
16//! version = "4.3"
17//! features = "minilp"
18//! ```
19//! For `coin_cbc` to compile, the `Cbc` library files need to be available on your system.
20//! See the [`coin_cbc` project README](https://github.com/KardinalAI/coin_cbc) for more infos.
21//!
22//! The other solvers need to be installed externally on your system.
23//! The respective information is provided in the project's README in the section on
24//! [installing external solvers](https://github.com/jcavat/rust-lp-modeler#installing-external-solvers).
25
26use std::collections::HashMap;
27
28use dsl::{Problem, LpContinuous, LpBinary, LpInteger, LpProblem, LpExprNode, LpExprOp, LpExprArenaIndex};
29
30pub mod cbc;
31pub use self::cbc::*;
32
33pub mod gurobi;
34pub use self::gurobi::*;
35
36pub mod glpk;
37pub use self::glpk::*;
38
39#[cfg(feature = "minilp")]
40pub mod minilp;
41#[cfg(feature = "minilp")]
42pub use self::minilp::*;
43
44#[cfg(feature = "native_coin_cbc")]
45pub mod native_cbc;
46#[cfg(feature = "native_coin_cbc")]
47pub use self::native_cbc::*;
48
49use std::fs::File;
50use std::fs;
51use util::is_zero;
52
53#[derive(Debug, PartialEq, Clone)]
54pub enum Status {
55    Optimal,
56    SubOptimal,
57    Infeasible,
58    Unbounded,
59    NotSolved,
60}
61
62#[derive(Debug, Clone)]
63pub struct Solution<'a> {
64    pub status: Status,
65    pub results: HashMap<String, f32>,
66    pub related_problem: Option<&'a LpProblem>
67}
68impl Solution<'_> {
69    pub fn new<'a>(status: Status, results: HashMap<String, f32>) -> Solution<'a> {
70        Solution {
71            status,
72            results,
73            related_problem: None
74        }
75    }
76    pub fn with_problem(status: Status, results: HashMap<String, f32>, problem: &LpProblem) -> Solution {
77        Solution {
78            status,
79            results,
80            related_problem: Some(problem)
81        }
82    }
83    fn check_possible_solution(&self) {
84        match &self.status {
85            Status::Unbounded | Status::NotSolved | Status::Infeasible => panic!("Solution must be optimal or suboptimal"),
86            _ => ()
87        }
88    }
89    pub fn get_raw_value(&self, name: &str) -> f32 {
90        self.check_possible_solution();
91        *self.results.get(name).expect("No value found for this variable. Check if the variable has been used in the related problem.")
92    }
93    pub fn get_bool(&self, var: &LpBinary) -> bool {
94        self.check_possible_solution();
95        self.results.get(&var.name).and_then(|&f| if is_zero(1.0-f) { Some(true) } else if is_zero(f) { Some(false) } else { None } ).expect("Result value cannot be interpreted as boolean")
96    }
97    pub fn get_float(&self, var: &LpContinuous) -> f32 {
98        self.check_possible_solution();
99        *self.results.get(&var.name).expect("No value found for this variable. Check if the variable has been used in the related problem.")
100    }
101    pub fn get_int(&self, var: &LpInteger) -> i32 {
102        self.check_possible_solution();
103        let &f = self.results.get(&var.name).expect("No value found for this variable. Check if the variable has been used in the related problem.");
104        let i = f as i32;
105        assert!( is_zero( f-(i as f32)), format!("Value {} cannot be interpreted as integer.", f) );
106        i
107    }
108    pub fn eval(&self) -> Option<f32> {
109        self.related_problem
110            .and_then( |problem| {
111                match &problem.obj_expr_arena {
112                    Some(obj_expr_arena) => Some( self.eval_with(&obj_expr_arena.get_root_index(), &self.results) ),
113                    None => None
114                }
115            })
116    }
117    fn eval_with(&self, index: &LpExprArenaIndex, values: &HashMap<String, f32>) -> f32 {
118        match self.related_problem.unwrap().obj_expr_arena.as_ref().unwrap().expr_ref_at(*index) {
119            LpExprNode::LpCompExpr(operation, left, right) => {
120                match operation {
121                    LpExprOp::Addition => self.eval_with(left, values) + self.eval_with(right, values),
122                    LpExprOp::Multiplication => self.eval_with(left, values) * self.eval_with(right, values),
123                    LpExprOp::Subtraction => self.eval_with(left, values) - self.eval_with(right, values),
124                }
125            },
126            LpExprNode::ConsBin(LpBinary { name })
127            | LpExprNode::ConsCont(LpContinuous { name, .. })
128            | LpExprNode::ConsInt(LpInteger { name, .. }) => *values.get(name).unwrap_or(&0f32),
129            LpExprNode::LitVal(n) => *n,
130            LpExprNode::EmptyExpr => 0.0
131        }
132    }
133}
134
135pub trait SolverTrait {
136    type P: Problem;
137    fn run<'a>(&self, problem: &'a Self::P) -> Result<Solution<'a>, String>;
138}
139
140pub trait SolverWithSolutionParsing {
141    fn read_solution<'a>(&self, temp_solution_file: &String, problem: Option<&'a LpProblem>) -> Result<Solution<'a>, String> {
142        match File::open( temp_solution_file ) {
143            Ok(f) => {
144                let res = self.read_specific_solution(&f, problem)?;
145                let _ = fs::remove_file(temp_solution_file);
146                Ok(res)
147            }
148            Err(_) => return Err("Cannot open file".to_string()),
149        }
150    }
151    fn read_specific_solution<'a>(&self, f: &File, problem: Option<&'a LpProblem>) -> Result<Solution<'a>, String>;
152}
153
154pub trait WithMaxSeconds<T> {
155    fn max_seconds(&self) -> Option<u32>;
156    fn with_max_seconds(&self, seconds: u32) -> T;
157}
158
159pub trait WithNbThreads<T> {
160    fn nb_threads(&self) -> Option<u32>;
161    fn with_nb_threads(&self, threads: u32) -> T;
162}