lp_modeler/solvers/
cbc.rs1extern crate uuid;
2use self::uuid::Uuid;
3
4use std::fs;
5use std::collections::HashMap;
6use std::fs::File;
7use std::io::{BufReader, BufRead};
8use std::process::Command;
9
10use dsl::LpProblem;
11use format::lp_format::*;
12use solvers::{Status, SolverTrait, WithMaxSeconds, WithNbThreads, SolverWithSolutionParsing, Solution};
13
14#[derive(Debug, Clone)]
15pub struct CbcSolver {
16 name: String,
17 command_name: String,
18 temp_solution_file: String,
19 threads: Option<u32>,
20 seconds: Option<u32>,
21}
22
23impl CbcSolver {
24 pub fn new() -> CbcSolver {
25 CbcSolver {
26 name: "Cbc".to_string(),
27 command_name: "cbc".to_string(),
28 temp_solution_file: format!("{}.sol", Uuid::new_v4().to_string()),
29 threads: None,
30 seconds: None,
31 }
32 }
33
34 pub fn command_name(&self, command_name: String) -> CbcSolver {
35 CbcSolver {
36 name: self.name.clone(),
37 command_name,
38 temp_solution_file: self.temp_solution_file.clone(),
39 threads: None,
40 seconds: None,
41 }
42 }
43
44 pub fn with_temp_solution_file(&self, temp_solution_file: String) -> CbcSolver {
45 CbcSolver {
46 name: self.name.clone(),
47 command_name: self.command_name.clone(),
48 temp_solution_file,
49 threads: None,
50 seconds: None,
51 }
52 }
53}
54
55impl SolverWithSolutionParsing for CbcSolver {
56 fn read_specific_solution<'a>(&self, f: &File, problem: Option<&'a LpProblem>) -> Result<Solution<'a>, String> {
57 let mut vars_value: HashMap<_, _> = HashMap::new();
58
59 if let Some(p) = problem {
62 let variables = p.variables();
63 for (name, _) in variables {
64 vars_value.insert(name.clone(), 0.0);
65 }
66 }
67
68 let mut file = BufReader::new(f);
69 let mut buffer = String::new();
70 let _ = file.read_line(&mut buffer);
71
72 let status = if let Some(status) = buffer.split_whitespace().next() {
73 match status {
74 "Optimal" => Status::Optimal,
75 "Infeasible" | "Integer" => Status::Infeasible,
77 "Unbounded" => Status::Unbounded,
78 "Stopped" => Status::SubOptimal,
80 _ => Status::NotSolved,
81 }
82 } else {
83 return Err("Incorrect solution format".to_string());
84 };
85 for line in file.lines() {
86 let l = line.unwrap();
87 let mut result_line: Vec<_> = l.split_whitespace().collect();
88 if result_line[0] == "**" {
89 result_line.remove(0);
90 };
91 if result_line.len() == 4 {
92 match result_line[2].parse::<f32>() {
93 Ok(n) => {
94 vars_value.insert(result_line[1].to_string(), n);
95 }
96 Err(e) => return Err(e.to_string()),
97 }
98 } else {
99 return Err("Incorrect solution format".to_string());
100 }
101 }
102 if let Some(p) = problem {
103 Ok( Solution::with_problem(status, vars_value, p) )
104 } else {
105 Ok( Solution::new(status, vars_value) )
106 }
107 }
108}
109
110impl WithMaxSeconds<CbcSolver> for CbcSolver {
111 fn max_seconds(&self) -> Option<u32> {
112 self.seconds
113 }
114 fn with_max_seconds(&self, seconds: u32) -> CbcSolver {
115 CbcSolver {
116 seconds: Some(seconds),
117 ..(*self).clone()
118 }
119 }
120}
121impl WithNbThreads<CbcSolver> for CbcSolver {
122 fn nb_threads(&self) -> Option<u32> {
123 self.threads
124 }
125 fn with_nb_threads(&self, threads: u32) -> CbcSolver {
126 CbcSolver {
127 threads: Some(threads),
128 ..(*self).clone()
129 }
130 }
131}
132
133impl SolverTrait for CbcSolver {
134 type P = LpProblem;
135
136 fn run<'a>(&self, problem: &'a Self::P) -> Result<Solution<'a>, String> {
137 let file_model = format!("{}.lp", problem.unique_name);
138 problem.write_lp(&file_model).map_err(|e| e.to_string())?;
139
140 let mut params: HashMap<String, String> = Default::default();
141 let optional_params: Vec<Option<(String, u32)>> = vec![
142 self.max_seconds().map(|s| ("seconds".to_owned(), s )),
143 self.nb_threads().map(|t| ("threads".to_owned(), t)) ];
144
145 for (arg, value) in optional_params.iter().flatten() {
146 params.insert(arg.to_string(), value.to_string());
147 }
148 params.iter().for_each( |(a,b)| println!("{},{}",a,b));
149
150 let result = Command::new(&self.command_name)
151 .arg(&file_model)
152 .args(params.iter().flat_map(|(k, v)| vec![k, v]))
153 .arg("solve")
154 .arg("solution")
155 .arg(&self.temp_solution_file)
156 .output()
157 .map_err(|_| format!("Error running the {} solver", self.name))
158 .and_then(|r| {
159 if r.status.success() {
160 self.read_solution(&self.temp_solution_file, Some(problem))
161 } else {
162 Err(r.status.to_string())
163 }
164 });
165
166 let _ = fs::remove_file(file_model);
167 result
168 }
169}