lp_solvers/solvers/
mod.rs1use 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#[derive(Debug, PartialEq, Clone)]
51pub enum Status {
52 Optimal,
54 TimeLimit,
56 MipGap,
58 SubOptimal,
60 Infeasible,
62 Unbounded,
64 NotSolved,
66}
67
68#[derive(Debug, Clone)]
70pub struct Solution {
71 pub status: Status,
73 pub results: HashMap<String, f32>,
75}
76
77impl Solution {
78 pub fn new(status: Status, results: HashMap<String, f32>) -> Solution {
80 Solution { status, results }
81 }
82}
83
84pub trait SolverTrait {
86 fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String>;
88}
89
90pub trait SolverProgram {
92 fn command_name(&self) -> &str;
94 fn arguments(&self, lp_file: &Path, solution_file: &Path) -> Vec<OsString>;
96 fn preferred_temp_solution_file(&self) -> Option<&Path> {
98 None
99 }
100 fn parse_stdout_status(&self, _stdout: &[u8]) -> Option<Status> {
102 None
103 }
104 fn solution_suffix(&self) -> Option<&str> {
106 None
107 }
108}
109
110pub trait SolverWithSolutionParsing {
112 #[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 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 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
197pub trait WithMaxSeconds<T> {
199 fn max_seconds(&self) -> Option<u32>;
201 fn with_max_seconds(&self, seconds: u32) -> T;
203}
204
205pub trait WithNbThreads<T> {
207 fn nb_threads(&self) -> Option<u32>;
209 fn with_nb_threads(&self, threads: u32) -> T;
211}
212
213pub trait WithMipGap<T> {
215 fn mip_gap(&self) -> Option<f32>;
217 fn with_mip_gap(&self, mipgap: f32) -> Result<T, String>;
219}
220
221pub trait WithMipStart<T> {
223 fn with_mip_start(&self, assignments: &HashMap<String, f32>) -> Result<T, String>;
225}
226
227#[derive(Default, Copy, Clone)]
234pub struct StaticSolver<T>(PhantomData<T>);
235
236impl<T> StaticSolver<T> {
237 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}