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::Result;
8
9use tempfile::NamedTempFile;
10
11/// Optimization sense
12#[derive(Clone, Copy, Eq, PartialEq, Debug)]
13pub enum LpObjective {
14    /// min
15    Minimize,
16    /// max
17    Maximize,
18}
19
20/// It's the user's responsibility to ensure
21/// that the variable names used by types implementing this trait
22/// follow the solver's requirements.
23pub trait WriteToLpFileFormat {
24    /// Write the object to the given formatter in the .lp format
25    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
34/// A type that represents a variable. See [crate::problem::Variable].
35pub trait AsVariable {
36    /// Variable name. Needs to be unique. See [crate::util::UniqueNameGenerator]
37    fn name(&self) -> &str;
38    /// Whether the variable is forced to take only integer values
39    fn is_integer(&self) -> bool;
40    /// Minimum allowed value for the variable
41    fn lower_bound(&self) -> f64;
42    /// Maximum allowed value for the variable
43    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
64/// A constraint expressing a relation between two expressions
65pub struct Constraint<E> {
66    /// left hand side of the constraint
67    pub lhs: E,
68    /// '<=' '=' or '>='
69    pub operator: Ordering,
70    /// Right-hand side of the constraint
71    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
90/// Implemented by type that can be formatted as an lp problem
91pub trait LpProblem<'a>: Sized {
92    /// variable type
93    type Variable: AsVariable;
94    /// expression type
95    type Expression: WriteToLpFileFormat;
96    /// Iterator over constraints
97    type ConstraintIterator: Iterator<Item = Constraint<Self::Expression>>;
98    /// Iterator over variables
99    type VariableIterator: Iterator<Item = Self::Variable>;
100
101    /// problem name. "lp_solvers_problem" by default
102    fn name(&self) -> &str {
103        "lp_solvers_problem"
104    }
105    /// Variables of the problem
106    fn variables(&'a self) -> Self::VariableIterator;
107    /// Target objective function
108    fn objective(&'a self) -> Self::Expression;
109    /// Whether to maximize or minimize the objective
110    fn sense(&'a self) -> LpObjective;
111    /// List of constraints to apply
112    fn constraints(&'a self) -> Self::ConstraintIterator;
113    /// Write the problem in the lp file format to the given formatter
114    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    /// Return an object whose [fmt::Display] implementation is the problem in the .lp format
123    fn display_lp(&'a self) -> DisplayedLp<'_, Self>
124    where
125        Self: Sized,
126    {
127        DisplayedLp(self)
128    }
129
130    /// Write the problem to a temporary file
131    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
145/// A problem whose `Display` implementation outputs valid .lp syntax
146pub 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    // Write objectives
159    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}