lp_solvers/
lp_format.rs

1//! Traits to be implemented by structures that can be dumped in the .lp format
2//!
3use 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/// Optimization sense
13#[derive(Clone, Copy, Eq, PartialEq, Debug)]
14pub enum LpObjective {
15    /// min
16    Minimize,
17    /// max
18    Maximize,
19}
20
21/// It's the user's responsibility to ensure
22/// that the variable names used by types implementing this trait
23/// follow the solver's requirements.
24pub trait WriteToLpFileFormat {
25    /// Write the object to the given formatter in the .lp format
26    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
35/// A type that represents a variable. See [crate::problem::Variable].
36pub trait AsVariable {
37    /// Variable name. Needs to be unique. See [crate::util::UniqueNameGenerator]
38    fn name(&self) -> &str;
39    /// Whether the variable is forced to take only integer values
40    fn is_integer(&self) -> bool;
41    /// Minimum allowed value for the variable
42    fn lower_bound(&self) -> f64;
43    /// Maximum allowed value for the variable
44    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
65/// A constraint expressing a relation between two expressions
66pub struct Constraint<E> {
67    /// left hand side of the constraint
68    pub lhs: E,
69    /// '<=' '=' or '>='
70    pub operator: Ordering,
71    /// Right-hand side of the constraint
72    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
91/// Implemented by type that can be formatted as an lp problem
92pub trait LpProblem<'a>: Sized {
93    /// variable type
94    type Variable: AsVariable;
95    /// expression type
96    type Expression: WriteToLpFileFormat;
97    /// Iterator over constraints
98    type ConstraintIterator: Iterator<Item = Constraint<Self::Expression>>;
99    /// Iterator over variables
100    type VariableIterator: Iterator<Item = Self::Variable>;
101
102    /// problem name. "lp_solvers_problem" by default
103    fn name(&self) -> &str {
104        "lp_solvers_problem"
105    }
106    /// Variables of the problem
107    fn variables(&'a self) -> Self::VariableIterator;
108    /// Target objective function
109    fn objective(&'a self) -> Self::Expression;
110    /// Whether to maximize or minimize the objective
111    fn sense(&'a self) -> LpObjective;
112    /// List of constraints to apply
113    fn constraints(&'a self) -> Self::ConstraintIterator;
114    /// Write the problem in the lp file format to the given formatter
115    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    /// Return an object whose [fmt::Display] implementation is the problem in the .lp format
124    fn display_lp(&'a self) -> DisplayedLp<'a, Self>
125    where
126        Self: Sized,
127    {
128        DisplayedLp(self)
129    }
130
131    /// Write the problem to a temporary file
132    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        // Use a buffered writer to limit the number of syscalls
142        let mut buf_f = BufWriter::new(&mut f);
143        write!(buf_f, "{}", self.display_lp())?;
144        buf_f.flush()?;
145
146        // need to explicitly drop the buffered writer here,
147        // since it holds a reference to the actual file
148        drop(buf_f);
149
150        Ok(f)
151    }
152}
153
154/// A problem whose `Display` implementation outputs valid .lp syntax
155pub 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    // Write objectives
168    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}