problemreductions/models/algebraic/
ilp.rs1use crate::registry::{FieldInfo, ProblemSchemaEntry, VariantDimension};
11use crate::traits::Problem;
12use crate::types::Extremum;
13use serde::{Deserialize, Serialize};
14use std::marker::PhantomData;
15
16inventory::submit! {
17 ProblemSchemaEntry {
18 name: "ILP",
19 display_name: "ILP",
20 aliases: &[],
21 dimensions: &[VariantDimension::new("variable", "bool", &["bool", "i32"])],
22 module_path: module_path!(),
23 description: "Optimize linear objective subject to linear constraints",
24 fields: &[
25 FieldInfo { name: "num_vars", type_name: "usize", description: "Number of integer variables" },
26 FieldInfo { name: "constraints", type_name: "Vec<LinearConstraint>", description: "Linear constraints" },
27 FieldInfo { name: "objective", type_name: "Vec<(usize, f64)>", description: "Sparse objective coefficients" },
28 FieldInfo { name: "sense", type_name: "ObjectiveSense", description: "Optimization direction" },
29 ],
30 }
31}
32
33pub trait VariableDomain: 'static + Clone + std::fmt::Debug + Send + Sync {
37 const DIMS_PER_VAR: usize;
39 const NAME: &'static str;
41}
42
43impl VariableDomain for bool {
44 const DIMS_PER_VAR: usize = 2;
45 const NAME: &'static str = "bool";
46}
47
48impl VariableDomain for i32 {
49 const DIMS_PER_VAR: usize = (i32::MAX as usize) + 1;
50 const NAME: &'static str = "i32";
51}
52
53#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
55pub enum Comparison {
56 Le,
58 Ge,
60 Eq,
62}
63
64impl Comparison {
65 pub fn holds(&self, lhs: f64, rhs: f64) -> bool {
67 match self {
68 Comparison::Le => lhs <= rhs,
69 Comparison::Ge => lhs >= rhs,
70 Comparison::Eq => (lhs - rhs).abs() < 1e-9,
71 }
72 }
73}
74
75#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
79pub struct LinearConstraint {
80 pub terms: Vec<(usize, f64)>,
82 pub cmp: Comparison,
84 pub rhs: f64,
86}
87
88impl LinearConstraint {
89 pub(crate) fn new(terms: Vec<(usize, f64)>, cmp: Comparison, rhs: f64) -> Self {
91 Self { terms, cmp, rhs }
92 }
93
94 pub fn le(terms: Vec<(usize, f64)>, rhs: f64) -> Self {
96 Self::new(terms, Comparison::Le, rhs)
97 }
98
99 pub fn ge(terms: Vec<(usize, f64)>, rhs: f64) -> Self {
101 Self::new(terms, Comparison::Ge, rhs)
102 }
103
104 pub fn eq(terms: Vec<(usize, f64)>, rhs: f64) -> Self {
106 Self::new(terms, Comparison::Eq, rhs)
107 }
108
109 pub fn evaluate_lhs(&self, values: &[i64]) -> f64 {
111 self.terms
112 .iter()
113 .map(|&(var, coef)| coef * values.get(var).copied().unwrap_or(0) as f64)
114 .sum()
115 }
116
117 pub fn is_satisfied(&self, values: &[i64]) -> bool {
119 let lhs = self.evaluate_lhs(values);
120 self.cmp.holds(lhs, self.rhs)
121 }
122
123 pub fn variables(&self) -> Vec<usize> {
125 self.terms.iter().map(|&(var, _)| var).collect()
126 }
127}
128
129#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
131pub enum ObjectiveSense {
132 Maximize,
134 Minimize,
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
169#[serde(bound(serialize = "", deserialize = ""))]
170pub struct ILP<V: VariableDomain = bool> {
171 pub num_vars: usize,
173 pub constraints: Vec<LinearConstraint>,
175 pub objective: Vec<(usize, f64)>,
177 pub sense: ObjectiveSense,
179 #[serde(skip)]
180 _marker: PhantomData<V>,
181}
182
183impl<V: VariableDomain> ILP<V> {
184 pub fn new(
186 num_vars: usize,
187 constraints: Vec<LinearConstraint>,
188 objective: Vec<(usize, f64)>,
189 sense: ObjectiveSense,
190 ) -> Self {
191 Self {
192 num_vars,
193 constraints,
194 objective,
195 sense,
196 _marker: PhantomData,
197 }
198 }
199
200 pub fn empty() -> Self {
202 Self::new(0, vec![], vec![], ObjectiveSense::Minimize)
203 }
204
205 pub fn evaluate_objective(&self, values: &[i64]) -> f64 {
207 self.objective
208 .iter()
209 .map(|&(var, coef)| coef * values.get(var).copied().unwrap_or(0) as f64)
210 .sum()
211 }
212
213 pub fn constraints_satisfied(&self, values: &[i64]) -> bool {
215 self.constraints.iter().all(|c| c.is_satisfied(values))
216 }
217
218 pub fn is_feasible(&self, values: &[i64]) -> bool {
220 values.len() == self.num_vars && self.constraints_satisfied(values)
221 }
222
223 fn config_to_values(&self, config: &[usize]) -> Vec<i64> {
226 config.iter().map(|&c| c as i64).collect()
227 }
228
229 pub fn num_variables(&self) -> usize {
231 self.num_vars
232 }
233
234 pub fn num_vars(&self) -> usize {
236 self.num_variables()
237 }
238
239 pub fn num_constraints(&self) -> usize {
241 self.constraints.len()
242 }
243}
244
245impl<V: VariableDomain> Problem for ILP<V> {
246 const NAME: &'static str = "ILP";
247 type Value = Extremum<f64>;
248
249 fn dims(&self) -> Vec<usize> {
250 vec![V::DIMS_PER_VAR; self.num_vars]
251 }
252
253 fn evaluate(&self, config: &[usize]) -> Extremum<f64> {
254 let values = self.config_to_values(config);
255 if !self.is_feasible(&values) {
256 return match self.sense {
257 ObjectiveSense::Maximize => Extremum::maximize(None),
258 ObjectiveSense::Minimize => Extremum::minimize(None),
259 };
260 }
261 let objective = self.evaluate_objective(&values);
262 match self.sense {
263 ObjectiveSense::Maximize => Extremum::maximize(Some(objective)),
264 ObjectiveSense::Minimize => Extremum::minimize(Some(objective)),
265 }
266 }
267
268 fn variant() -> Vec<(&'static str, &'static str)> {
269 vec![("variable", V::NAME)]
270 }
271}
272
273crate::declare_variants! {
274 default ILP<bool> => "2^num_vars",
275 ILP<i32> => "num_vars^num_vars",
276}
277
278#[cfg(feature = "example-db")]
279pub(crate) fn canonical_model_example_specs() -> Vec<crate::example_db::specs::ModelExampleSpec> {
280 vec![crate::example_db::specs::ModelExampleSpec {
281 id: "ilp_i32",
282 instance: Box::new(ILP::<i32>::new(
283 2,
284 vec![
285 LinearConstraint::le(vec![(0, 1.0), (1, 1.0)], 5.0),
286 LinearConstraint::le(vec![(0, 4.0), (1, 7.0)], 28.0),
287 ],
288 vec![(0, -5.0), (1, -6.0)],
289 ObjectiveSense::Minimize,
290 )),
291 optimal_config: vec![3, 2],
292 optimal_value: serde_json::json!({
293 "sense": "Minimize",
294 "value": -27.0,
295 }),
296 }]
297}
298
299#[cfg(test)]
300#[path = "../../unit_tests/models/algebraic/ilp.rs"]
301mod tests;