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 SubOptimal,
56 Infeasible,
58 Unbounded,
60 NotSolved,
62}
63
64#[derive(Debug, Clone)]
66pub struct Solution {
67 pub status: Status,
69 pub results: HashMap<String, f32>,
71}
72
73impl Solution {
74 pub fn new(status: Status, results: HashMap<String, f32>) -> Solution {
76 Solution { status, results }
77 }
78}
79
80pub trait SolverTrait {
82 fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String>;
84}
85
86pub trait SolverProgram {
88 fn command_name(&self) -> &str;
90 fn arguments(&self, lp_file: &Path, solution_file: &Path) -> Vec<OsString>;
92 fn preferred_temp_solution_file(&self) -> Option<&Path> {
94 None
95 }
96 fn parse_stdout_status(&self, _stdout: &[u8]) -> Option<Status> {
98 None
99 }
100 fn solution_suffix(&self) -> Option<&str> {
102 None
103 }
104}
105
106pub trait SolverWithSolutionParsing {
108 #[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 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 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
193pub trait WithMaxSeconds<T> {
195 fn max_seconds(&self) -> Option<u32>;
197 fn with_max_seconds(&self, seconds: u32) -> T;
199}
200
201pub trait WithNbThreads<T> {
203 fn nb_threads(&self) -> Option<u32>;
205 fn with_nb_threads(&self, threads: u32) -> T;
207}
208
209pub trait WithMipGap<T> {
211 fn mip_gap(&self) -> Option<f32>;
213 fn with_mip_gap(&self, mipgap: f32) -> Result<T, String>;
215}
216
217#[derive(Default, Copy, Clone)]
224pub struct StaticSolver<T>(PhantomData<T>);
225
226impl<T> StaticSolver<T> {
227 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}