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