use std::collections::HashMap;
use std::ffi::OsString;
use std::fs::File;
use std::marker::PhantomData;
use std::path::{Path, PathBuf};
use std::process::Command;
use crate::lp_format::LpProblem;
pub use self::auto::*;
pub use self::cbc::*;
#[cfg(feature = "cplex")]
pub use self::cplex::*;
pub use self::glpk::*;
pub use self::gurobi::*;
pub mod auto;
pub mod cbc;
#[cfg(feature = "cplex")]
pub mod cplex;
pub mod glpk;
pub mod gurobi;
#[derive(Debug, PartialEq, Clone)]
pub enum Status {
Optimal,
TimeLimit,
MipGap,
SubOptimal,
Infeasible,
Unbounded,
NotSolved,
}
#[derive(Debug, Clone)]
pub struct Solution {
pub status: Status,
pub results: HashMap<String, f32>,
}
impl Solution {
pub fn new(status: Status, results: HashMap<String, f32>) -> Solution {
Solution { status, results }
}
}
pub trait SolverTrait {
fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String>;
}
pub trait SolverProgram {
fn command_name(&self) -> &str;
fn arguments(&self, lp_file: &Path, solution_file: &Path) -> Vec<OsString>;
fn preferred_temp_solution_file(&self) -> Option<&Path> {
None
}
fn parse_stdout_status(&self, _stdout: &[u8]) -> Option<Status> {
None
}
fn solution_suffix(&self) -> Option<&str> {
None
}
}
pub trait SolverWithSolutionParsing {
#[deprecated]
fn read_solution<'a, P: LpProblem<'a>>(
&self,
temp_solution_file: &str,
problem: Option<&'a P>,
) -> Result<Solution, String> {
Self::read_solution_from_path(self, &PathBuf::from(temp_solution_file), problem)
}
fn read_solution_from_path<'a, P: LpProblem<'a>>(
&self,
temp_solution_file: &Path,
problem: Option<&'a P>,
) -> Result<Solution, String> {
match File::open(temp_solution_file) {
Ok(f) => {
let res = self.read_specific_solution(&f, problem)?;
Ok(res)
}
Err(e) => Err(format!(
"Cannot open solution file {:?}: {}",
temp_solution_file, e
)),
}
}
fn read_specific_solution<'a, P: LpProblem<'a>>(
&self,
f: &File,
problem: Option<&'a P>,
) -> Result<Solution, String>;
}
impl<T: SolverWithSolutionParsing + SolverProgram> SolverTrait for T {
fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String> {
let command_name = self.command_name();
let file_model = problem
.to_tmp_file()
.map_err(|e| format!("Unable to create {} problem file: {}", command_name, e))?;
let temp_solution_file = if let Some(p) = self.preferred_temp_solution_file() {
PathBuf::from(p)
} else {
let mut builder = tempfile::Builder::new();
if let Some(suffix) = self.solution_suffix() {
builder.suffix(suffix);
}
PathBuf::from(builder.tempfile().map_err(|e| e.to_string())?.path())
};
let arguments = self.arguments(file_model.path(), &temp_solution_file);
let output = Command::new(command_name)
.args(arguments)
.output()
.map_err(|e| format!("Error while running {}: {}", command_name, e))?;
if !output.status.success() {
return Err(format!(
"{} exited with status {}",
command_name, output.status
));
}
match self.parse_stdout_status(&output.stdout) {
Some(Status::Infeasible) => Ok(Solution::new(Status::Infeasible, Default::default())),
Some(Status::Unbounded) => Ok(Solution::new(Status::Unbounded, Default::default())),
status_hint => {
let mut solution = self
.read_solution_from_path(&temp_solution_file, Some(problem))
.map_err(|e| {
format!(
"{}. Solver output: {}",
e,
std::str::from_utf8(&output.stdout).unwrap_or("Invalid UTF8")
)
})?;
if let Some(status) = status_hint {
solution.status = status;
}
Ok(solution)
}
}
}
}
pub trait WithMaxSeconds<T> {
fn max_seconds(&self) -> Option<u32>;
fn with_max_seconds(&self, seconds: u32) -> T;
}
pub trait WithNbThreads<T> {
fn nb_threads(&self) -> Option<u32>;
fn with_nb_threads(&self, threads: u32) -> T;
}
pub trait WithMipGap<T> {
fn mip_gap(&self) -> Option<f32>;
fn with_mip_gap(&self, mipgap: f32) -> Result<T, String>;
}
pub trait WithMipStart<T> {
fn with_mip_start(&self, assignments: &HashMap<String, f32>) -> Result<T, String>;
}
#[derive(Default, Copy, Clone)]
pub struct StaticSolver<T>(PhantomData<T>);
impl<T> StaticSolver<T> {
pub const fn new() -> Self {
StaticSolver(PhantomData)
}
}
impl<T: SolverTrait + Default> SolverTrait for StaticSolver<T> {
fn run<'a, P: LpProblem<'a>>(&self, problem: &'a P) -> Result<Solution, String> {
let solver = T::default();
SolverTrait::run(&solver, problem)
}
}