1use std::cmp::Ordering;
4use std::fmt;
5use std::fmt::Formatter;
6use std::io::prelude::*;
7use std::io::BufWriter;
8use std::io::Result;
9
10use tempfile::NamedTempFile;
11
12#[derive(Clone, Copy, Eq, PartialEq, Debug)]
14pub enum LpObjective {
15 Minimize,
17 Maximize,
19}
20
21pub trait WriteToLpFileFormat {
25 fn to_lp_file_format(&self, f: &mut fmt::Formatter) -> fmt::Result;
27}
28
29impl<T: WriteToLpFileFormat> WriteToLpFileFormat for &T {
30 fn to_lp_file_format(&self, f: &mut Formatter) -> fmt::Result {
31 (*self).to_lp_file_format(f)
32 }
33}
34
35pub trait AsVariable {
37 fn name(&self) -> &str;
39 fn is_integer(&self) -> bool;
41 fn lower_bound(&self) -> f64;
43 fn upper_bound(&self) -> f64;
45}
46
47impl<T: AsVariable> AsVariable for &T {
48 fn name(&self) -> &str {
49 (*self).name()
50 }
51
52 fn is_integer(&self) -> bool {
53 (*self).is_integer()
54 }
55
56 fn lower_bound(&self) -> f64 {
57 (*self).lower_bound()
58 }
59
60 fn upper_bound(&self) -> f64 {
61 (*self).upper_bound()
62 }
63}
64
65pub struct Constraint<E> {
67 pub lhs: E,
69 pub operator: Ordering,
71 pub rhs: f64,
73}
74
75impl<E: WriteToLpFileFormat> WriteToLpFileFormat for Constraint<E> {
76 fn to_lp_file_format(&self, f: &mut Formatter) -> fmt::Result {
77 self.lhs.to_lp_file_format(f)?;
78 write!(
79 f,
80 " {} {}",
81 match self.operator {
82 Ordering::Equal => "=",
83 Ordering::Less => "<=",
84 Ordering::Greater => ">=",
85 },
86 self.rhs
87 )
88 }
89}
90
91pub trait LpProblem<'a>: Sized {
93 type Variable: AsVariable;
95 type Expression: WriteToLpFileFormat;
97 type ConstraintIterator: Iterator<Item = Constraint<Self::Expression>>;
99 type VariableIterator: Iterator<Item = Self::Variable>;
101
102 fn name(&self) -> &str {
104 "lp_solvers_problem"
105 }
106 fn variables(&'a self) -> Self::VariableIterator;
108 fn objective(&'a self) -> Self::Expression;
110 fn sense(&'a self) -> LpObjective;
112 fn constraints(&'a self) -> Self::ConstraintIterator;
114 fn to_lp_file_format(&'a self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
116 write!(f, "\\ {}\n\n", self.name())?;
117 objective_lp_file_block(self, f)?;
118 write_constraints_lp_file_block(self, f)?;
119 write_bounds_lp_file_block(self, f)?;
120 write!(f, "\nEnd\n")?;
121 Ok(())
122 }
123 fn display_lp(&'a self) -> DisplayedLp<'a, Self>
125 where
126 Self: Sized,
127 {
128 DisplayedLp(self)
129 }
130
131 fn to_tmp_file(&'a self) -> Result<NamedTempFile>
133 where
134 Self: Sized,
135 {
136 let mut f = tempfile::Builder::new()
137 .prefix(self.name())
138 .suffix(".lp")
139 .tempfile()?;
140
141 let mut buf_f = BufWriter::new(&mut f);
143 write!(buf_f, "{}", self.display_lp())?;
144 buf_f.flush()?;
145
146 drop(buf_f);
149
150 Ok(f)
151 }
152}
153
154pub struct DisplayedLp<'a, P>(&'a P);
156
157impl<'a, P: LpProblem<'a>> std::fmt::Display for DisplayedLp<'a, P> {
158 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
159 self.0.to_lp_file_format(f)
160 }
161}
162
163fn objective_lp_file_block<'a>(
164 prob: &'a impl LpProblem<'a>,
165 f: &mut std::fmt::Formatter,
166) -> std::fmt::Result {
167 let obj_type = match prob.sense() {
169 LpObjective::Maximize => "Maximize\n ",
170 LpObjective::Minimize => "Minimize\n ",
171 };
172 write!(f, "{}obj: ", obj_type)?;
173 prob.objective().to_lp_file_format(f)?;
174 Ok(())
175}
176
177fn write_constraints_lp_file_block<'a>(
178 prob: &'a impl LpProblem<'a>,
179 f: &mut std::fmt::Formatter,
180) -> std::fmt::Result {
181 write!(f, "\n\nSubject To\n")?;
182 for (idx, constraint) in prob.constraints().enumerate() {
183 write!(f, " c{}: ", idx)?;
184 constraint.to_lp_file_format(f)?;
185 writeln!(f)?;
186 }
187 Ok(())
188}
189
190fn write_bounds_lp_file_block<'a>(prob: &'a impl LpProblem<'a>, f: &mut Formatter) -> fmt::Result {
191 let mut integers = vec![];
192 write!(f, "\nBounds\n")?;
193 for variable in prob.variables() {
194 let low: f64 = variable.lower_bound();
195 let up: f64 = variable.upper_bound();
196 write!(f, " ")?;
197 if low > f64::NEG_INFINITY {
198 write!(f, "{} <= ", low)?;
199 }
200 let name = variable.name().to_string();
201 write!(f, "{}", name)?;
202 if up < f64::INFINITY {
203 write!(f, " <= {}", up)?;
204 }
205 if low.is_infinite() && up.is_infinite() {
206 write!(f, " free")?;
207 }
208 writeln!(f)?;
209 if variable.is_integer() {
210 integers.push(name);
211 }
212 }
213 if !integers.is_empty() {
214 writeln!(f, "\nGenerals")?;
215 for name in integers.iter() {
216 writeln!(f, " {}", name)?;
217 }
218 }
219 Ok(())
220}