1use std::cmp::Ordering;
4use std::fmt;
5use std::fmt::Formatter;
6use std::io::prelude::*;
7use std::io::Result;
8
9use tempfile::NamedTempFile;
10
11#[derive(Clone, Copy, Eq, PartialEq, Debug)]
13pub enum LpObjective {
14 Minimize,
16 Maximize,
18}
19
20pub trait WriteToLpFileFormat {
24 fn to_lp_file_format(&self, f: &mut fmt::Formatter) -> fmt::Result;
26}
27
28impl<'a, T: WriteToLpFileFormat> WriteToLpFileFormat for &'a T {
29 fn to_lp_file_format(&self, f: &mut Formatter) -> fmt::Result {
30 (*self).to_lp_file_format(f)
31 }
32}
33
34pub trait AsVariable {
36 fn name(&self) -> &str;
38 fn is_integer(&self) -> bool;
40 fn lower_bound(&self) -> f64;
42 fn upper_bound(&self) -> f64;
44}
45
46impl<'a, T: AsVariable> AsVariable for &'a T {
47 fn name(&self) -> &str {
48 (*self).name()
49 }
50
51 fn is_integer(&self) -> bool {
52 (*self).is_integer()
53 }
54
55 fn lower_bound(&self) -> f64 {
56 (*self).lower_bound()
57 }
58
59 fn upper_bound(&self) -> f64 {
60 (*self).upper_bound()
61 }
62}
63
64pub struct Constraint<E> {
66 pub lhs: E,
68 pub operator: Ordering,
70 pub rhs: f64,
72}
73
74impl<E: WriteToLpFileFormat> WriteToLpFileFormat for Constraint<E> {
75 fn to_lp_file_format(&self, f: &mut Formatter) -> fmt::Result {
76 self.lhs.to_lp_file_format(f)?;
77 write!(
78 f,
79 " {} {}",
80 match self.operator {
81 Ordering::Equal => "=",
82 Ordering::Less => "<=",
83 Ordering::Greater => ">=",
84 },
85 self.rhs
86 )
87 }
88}
89
90pub trait LpProblem<'a>: Sized {
92 type Variable: AsVariable;
94 type Expression: WriteToLpFileFormat;
96 type ConstraintIterator: Iterator<Item = Constraint<Self::Expression>>;
98 type VariableIterator: Iterator<Item = Self::Variable>;
100
101 fn name(&self) -> &str {
103 "lp_solvers_problem"
104 }
105 fn variables(&'a self) -> Self::VariableIterator;
107 fn objective(&'a self) -> Self::Expression;
109 fn sense(&'a self) -> LpObjective;
111 fn constraints(&'a self) -> Self::ConstraintIterator;
113 fn to_lp_file_format(&'a self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
115 write!(f, "\\ {}\n\n", self.name())?;
116 objective_lp_file_block(self, f)?;
117 write_constraints_lp_file_block(self, f)?;
118 write_bounds_lp_file_block(self, f)?;
119 write!(f, "\nEnd\n")?;
120 Ok(())
121 }
122 fn display_lp(&'a self) -> DisplayedLp<'_, Self>
124 where
125 Self: Sized,
126 {
127 DisplayedLp(self)
128 }
129
130 fn to_tmp_file(&'a self) -> Result<NamedTempFile>
132 where
133 Self: Sized,
134 {
135 let mut f = tempfile::Builder::new()
136 .prefix(self.name())
137 .suffix(".lp")
138 .tempfile()?;
139 write!(f, "{}", self.display_lp())?;
140 f.flush()?;
141 Ok(f)
142 }
143}
144
145pub struct DisplayedLp<'a, P>(&'a P);
147
148impl<'a, P: LpProblem<'a>> std::fmt::Display for DisplayedLp<'a, P> {
149 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
150 self.0.to_lp_file_format(f)
151 }
152}
153
154fn objective_lp_file_block<'a>(
155 prob: &'a impl LpProblem<'a>,
156 f: &mut std::fmt::Formatter,
157) -> std::fmt::Result {
158 let obj_type = match prob.sense() {
160 LpObjective::Maximize => "Maximize\n ",
161 LpObjective::Minimize => "Minimize\n ",
162 };
163 write!(f, "{}obj: ", obj_type)?;
164 prob.objective().to_lp_file_format(f)?;
165 Ok(())
166}
167
168fn write_constraints_lp_file_block<'a>(
169 prob: &'a impl LpProblem<'a>,
170 f: &mut std::fmt::Formatter,
171) -> std::fmt::Result {
172 write!(f, "\n\nSubject To\n")?;
173 for (idx, constraint) in prob.constraints().enumerate() {
174 write!(f, " c{}: ", idx)?;
175 constraint.to_lp_file_format(f)?;
176 writeln!(f)?;
177 }
178 Ok(())
179}
180
181fn write_bounds_lp_file_block<'a>(prob: &'a impl LpProblem<'a>, f: &mut Formatter) -> fmt::Result {
182 let mut integers = vec![];
183 write!(f, "\nBounds\n")?;
184 for variable in prob.variables() {
185 let low: f64 = variable.lower_bound();
186 let up: f64 = variable.upper_bound();
187 write!(f, " ")?;
188 if low > f64::NEG_INFINITY {
189 write!(f, "{} <= ", low)?;
190 }
191 let name = variable.name().to_string();
192 write!(f, "{}", name)?;
193 if up < f64::INFINITY {
194 write!(f, " <= {}", up)?;
195 }
196 if low.is_infinite() && up.is_infinite() {
197 write!(f, " free")?;
198 }
199 writeln!(f)?;
200 if variable.is_integer() {
201 integers.push(name);
202 }
203 }
204 if !integers.is_empty() {
205 writeln!(f, "\nGenerals")?;
206 for name in integers.iter() {
207 writeln!(f, " {}", name)?;
208 }
209 }
210 Ok(())
211}