lp_modeler/solvers/
cbc.rs

1extern 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        // populate default values for all vars
60        // CBC keeps only non-zero values from a number of variables
61        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 status is either "Infeasible" or "Integer infeasible"
76                "Infeasible" | "Integer" => Status::Infeasible,
77                "Unbounded" => Status::Unbounded,
78                // "Stopped" can be "on time", "on iterations", "on difficulties" or "on ctrl-c"
79                "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}