lp_solvers/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;
27use std::ffi::OsString;
28use std::fs::File;
29use std::marker::PhantomData;
30use std::path::{Path, PathBuf};
31use std::process::Command;
32
33use crate::lp_format::LpProblem;
34
35pub use self::auto::*;
36pub use self::cbc::*;
37#[cfg(feature = "cplex")]
38pub use self::cplex::*;
39pub use self::glpk::*;
40pub use self::gurobi::*;
41
42pub mod auto;
43pub mod cbc;
44#[cfg(feature = "cplex")]
45pub mod cplex;
46pub mod glpk;
47pub mod gurobi;
48
49/// Solution status
50#[derive(Debug, PartialEq, Clone)]
51pub enum Status {
52    /// the best possible solution was found
53    Optimal,
54    /// The time limit was reached
55    TimeLimit,
56    /// The MipGap was reached
57    MipGap,
58    /// A solution was found; it may not be the best one.
59    SubOptimal,
60    /// There is no solution for the problem
61    Infeasible,
62    /// There is no single finite optimum for the problem
63    Unbounded,
64    /// Unable to solve
65    NotSolved,
66}
67
68/// A solution to a problem
69#[derive(Debug, Clone)]
70pub struct Solution {
71    /// solution state
72    pub status: Status,
73    /// map from variable name to variable value
74    pub results: HashMap<String, f32>,
75}
76
77impl Solution {
78    /// Create a solution
79    pub fn new(status: Status, results: HashMap<String, f32>) -> Solution {
80        Solution { status, results }
81    }
82}
83
84/// A solver that can take a problem and return a solution
85pub trait SolverTrait {
86    /// Run the solver on the given problem
87    fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String>;
88}
89
90/// An external commandline solver
91pub trait SolverProgram {
92    /// Returns the commandline program name
93    fn command_name(&self) -> &str;
94    /// Returns the commandline arguments
95    fn arguments(&self, lp_file: &Path, solution_file: &Path) -> Vec<OsString>;
96    /// If there is a predefined solution filename
97    fn preferred_temp_solution_file(&self) -> Option<&Path> {
98        None
99    }
100    /// Parse the output of the program
101    fn parse_stdout_status(&self, _stdout: &[u8]) -> Option<Status> {
102        None
103    }
104    /// A suffix the solution file must have
105    fn solution_suffix(&self) -> Option<&str> {
106        None
107    }
108}
109
110/// A solver that can parse a solution file
111pub trait SolverWithSolutionParsing {
112    /// Use read_solution_from_path instead.
113    #[deprecated]
114    fn read_solution<'a, P: LpProblem<'a>>(
115        &self,
116        temp_solution_file: &str,
117        problem: Option<&'a P>,
118    ) -> Result<Solution, String> {
119        Self::read_solution_from_path(self, &PathBuf::from(temp_solution_file), problem)
120    }
121    /// Read a solution
122    fn read_solution_from_path<'a, P: LpProblem<'a>>(
123        &self,
124        temp_solution_file: &Path,
125        problem: Option<&'a P>,
126    ) -> Result<Solution, String> {
127        match File::open(temp_solution_file) {
128            Ok(f) => {
129                let res = self.read_specific_solution(&f, problem)?;
130                Ok(res)
131            }
132            Err(e) => Err(format!(
133                "Cannot open solution file {:?}: {}",
134                temp_solution_file, e
135            )),
136        }
137    }
138    /// Read a solution from a file
139    fn read_specific_solution<'a, P: LpProblem<'a>>(
140        &self,
141        f: &File,
142        problem: Option<&'a P>,
143    ) -> Result<Solution, String>;
144}
145
146impl<T: SolverWithSolutionParsing + SolverProgram> SolverTrait for T {
147    fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String> {
148        let command_name = self.command_name();
149        let file_model = problem
150            .to_tmp_file()
151            .map_err(|e| format!("Unable to create {} problem file: {}", command_name, e))?;
152
153        let temp_solution_file = if let Some(p) = self.preferred_temp_solution_file() {
154            PathBuf::from(p)
155        } else {
156            let mut builder = tempfile::Builder::new();
157            if let Some(suffix) = self.solution_suffix() {
158                builder.suffix(suffix);
159            }
160            PathBuf::from(builder.tempfile().map_err(|e| e.to_string())?.path())
161        };
162        let arguments = self.arguments(file_model.path(), &temp_solution_file);
163
164        let output = Command::new(command_name)
165            .args(arguments)
166            .output()
167            .map_err(|e| format!("Error while running {}: {}", command_name, e))?;
168
169        if !output.status.success() {
170            return Err(format!(
171                "{} exited with status {}",
172                command_name, output.status
173            ));
174        }
175        match self.parse_stdout_status(&output.stdout) {
176            Some(Status::Infeasible) => Ok(Solution::new(Status::Infeasible, Default::default())),
177            Some(Status::Unbounded) => Ok(Solution::new(Status::Unbounded, Default::default())),
178            status_hint => {
179                let mut solution = self
180                    .read_solution_from_path(&temp_solution_file, Some(problem))
181                    .map_err(|e| {
182                        format!(
183                            "{}. Solver output: {}",
184                            e,
185                            std::str::from_utf8(&output.stdout).unwrap_or("Invalid UTF8")
186                        )
187                    })?;
188                if let Some(status) = status_hint {
189                    solution.status = status;
190                }
191                Ok(solution)
192            }
193        }
194    }
195}
196
197/// Configure the max allowed runtime
198pub trait WithMaxSeconds<T> {
199    /// get max runtime
200    fn max_seconds(&self) -> Option<u32>;
201    /// set max runtime
202    fn with_max_seconds(&self, seconds: u32) -> T;
203}
204
205/// A solver where the parallelism can be configured
206pub trait WithNbThreads<T> {
207    /// get thread count
208    fn nb_threads(&self) -> Option<u32>;
209    /// set thread count
210    fn with_nb_threads(&self, threads: u32) -> T;
211}
212
213/// Configure the MIP (optimality) gap
214pub trait WithMipGap<T> {
215    /// get MIP gap
216    fn mip_gap(&self) -> Option<f32>;
217    /// set MIP gap
218    fn with_mip_gap(&self, mipgap: f32) -> Result<T, String>;
219}
220
221/// Provide a MIP start: (partial) initial solution
222pub trait WithMipStart<T> {
223    /// set MIP start
224    fn with_mip_start(&self, assignments: &HashMap<String, f32>) -> Result<T, String>;
225}
226
227/// A static version of a solver, where the solver itself doesn't hold any data
228///
229/// ```
230/// use lp_solvers::solvers::{StaticSolver, CbcSolver};
231/// const STATIC_SOLVER : StaticSolver<CbcSolver> = StaticSolver::new();
232/// ```
233#[derive(Default, Copy, Clone)]
234pub struct StaticSolver<T>(PhantomData<T>);
235
236impl<T> StaticSolver<T> {
237    /// Create a new static solver
238    pub const fn new() -> Self {
239        StaticSolver(PhantomData)
240    }
241}
242
243impl<T: SolverTrait + Default> SolverTrait for StaticSolver<T> {
244    fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String> {
245        let solver = T::default();
246        SolverTrait::run(&solver, problem)
247    }
248}