grb/
expr.rs

1#![allow(clippy::many_single_char_names)]
2//! Algebraic expressions involving variables used to construct constraints and a helper trait for pretty-printing.
3
4use fnv::FnvHashMap;
5use std::fmt;
6use std::fmt::Write;
7use std::iter::Sum;
8use std::ops::{Add, Mul, Neg, Sub};
9
10use crate::prelude::*;
11use crate::{Error, Result};
12
13/// An algbraic expression of variables.
14#[derive(Debug, Clone)]
15pub enum Expr {
16    /// A quadratic expression
17    Quad(QuadExpr),
18    /// A linear expression
19    Linear(LinExpr),
20    /// A single quadratic term
21    QTerm(f64, Var, Var),
22    /// A single linear term
23    Term(f64, Var),
24    /// A constant term
25    Constant(f64),
26}
27
28impl Expr {
29    fn into_higher_order(self) -> Expr {
30        use self::Expr::*;
31        match self {
32            Constant(x) => Linear(LinExpr::new()) + Constant(x),
33            Term(a, x) => Linear(LinExpr::new()) + Term(a, x),
34            QTerm(a, x, y) => Quad(QuadExpr::new()) + QTerm(a, x, y),
35            Linear(e) => QuadExpr {
36                linexpr: e,
37                qcoeffs: FnvHashMap::default(),
38            }
39            .into(),
40            Quad(_) => unreachable!(),
41        }
42    }
43
44    /// Returns `false` if quadratic terms are present.
45    pub fn is_linear(&self) -> bool {
46        !matches!(self, Expr::QTerm(..) | Expr::Quad(..))
47    }
48
49    /// Transform into a [`QuadExpr`], possibly with no quadratic terms)
50    pub fn into_quadexpr(self) -> QuadExpr {
51        use self::Expr::*;
52        match self {
53            Quad(e) => e,
54            other => other.into_higher_order().into_quadexpr(),
55        }
56    }
57
58    /// Transform into a [`LinExpr`], possible with no linear terms (just a constant)
59    ///
60    /// # Errors
61    /// Returns an [`Error::AlgebraicError`] if `Expr` is not linear.
62    pub fn into_linexpr(self) -> Result<LinExpr> {
63        use self::Expr::*;
64        match self {
65            Quad(..) | QTerm(..) => Err(Error::AlgebraicError(
66                "expression contains quadratic terms".to_string(),
67            )),
68            Linear(e) => Ok(e),
69            other => other.into_higher_order().into_linexpr(),
70        }
71    }
72}
73
74impl Default for Expr {
75    fn default() -> Self {
76        Expr::Constant(0.0)
77    }
78}
79
80/// Linear expression of variables
81///
82/// Represents an affine expression of variables: a constant term plus variables multiplied by coefficients.
83///
84/// A `LinExpr` object is typically created using [`Expr::into_linexpr`]. Most [`Model`] methods take
85/// [`Expr`] as arguments instead of `LinExpr`, so converting to `LinExpr` is rarely needed.
86#[derive(Debug, Clone, Default)]
87pub struct LinExpr {
88    coeff: FnvHashMap<Var, f64>,
89    offset: f64,
90}
91
92/// Quadratic expression of variables
93///
94/// Represents an linear summation of quadratic terms, plus a linear expression.
95///
96/// A `QuadExpr` object is typically created using [`Expr::into_quadexpr`]. Most [`Model`] methods take
97/// [`Expr`] as arguments instead of `QuadExpr`, so converting to `QuadExpr` is rarely needed.
98#[derive(Debug, Clone, Default)]
99pub struct QuadExpr {
100    linexpr: LinExpr,
101    qcoeffs: FnvHashMap<(Var, Var), f64>,
102}
103
104impl From<Var> for Expr {
105    fn from(var: Var) -> Expr {
106        Expr::Term(1.0, var)
107    }
108}
109
110macro_rules! impl_all_primitives {
111    ($macr:ident; $($args:tt),*) => {
112      $macr!{f64 $(,$args)*}
113      $macr!{f32 $(,$args)*}
114      $macr!{u8 $(,$args)*}
115      $macr!{u16 $(,$args)*}
116      $macr!{u32 $(,$args)*}
117      $macr!{u64 $(,$args)*}
118      $macr!{usize $(,$args)*}
119      $macr!{i8 $(,$args)*}
120      $macr!{i16 $(,$args)*}
121      $macr!{i32 $(,$args)*}
122      $macr!{i64 $(,$args)*}
123      $macr!{isize $(,$args)*}
124    };
125}
126
127impl LinExpr {
128    /// Create new an empty linear expression.
129    pub fn new() -> Self {
130        LinExpr::default()
131    }
132
133    /// Does this expression evaluate (close to) `0.0f64`?
134    ///
135    /// # Example
136    /// ```
137    /// assert!(grb::expr::LinExpr::new().is_empty());
138    /// ```
139    pub fn is_empty(&self) -> bool {
140        self.offset.abs() < f64::EPSILON && self.coeff.is_empty()
141    }
142
143    /// Add a linear term into the expression.
144    pub fn add_term(&mut self, coeff: f64, var: Var) -> &mut Self {
145        self.coeff
146            .entry(var)
147            .and_modify(|c| *c += coeff)
148            .or_insert(coeff);
149        self
150    }
151
152    /// Add a constant into the expression.
153    pub fn add_constant(&mut self, constant: f64) -> &mut Self {
154        self.offset += constant;
155        self
156    }
157
158    /// Get the constant offset
159    pub fn get_offset(&self) -> f64 {
160        self.offset
161    }
162
163    /// Set the constant offset,  returning the old one
164    pub fn set_offset(&mut self, val: f64) -> f64 {
165        std::mem::replace(&mut self.offset, val)
166    }
167
168    /// Get actual value of the expression.
169    pub fn get_value(&self, model: &Model) -> Result<f64> {
170        let coeff = self.coeff.values();
171        let vals = model.get_obj_attr_batch(attr::X, self.coeff.keys().copied())?;
172        let total = coeff.zip(vals).map(|(&a, x)| a * x).sum::<f64>() + self.offset;
173        Ok(total)
174    }
175
176    /// Decompose into variables, their coefficients and the offset, respectively.
177    pub fn into_parts(self) -> (FnvHashMap<Var, f64>, f64) {
178        (self.coeff, self.offset)
179    }
180
181    /// number of linear terms in the expression (excluding the constant)
182    pub fn num_terms(&self) -> usize {
183        self.coeff.len()
184    }
185
186    /// Returns an iterator over the terms excluding the offset (item type is `(&Var, &f64)`)
187    pub fn iter_terms(&self) -> std::collections::hash_map::Iter<Var, f64> {
188        self.coeff.iter()
189    }
190
191    /// Multiply expression by a scalar
192    pub fn mul_scalar(&mut self, val: f64) -> &mut Self {
193        self.offset *= val;
194        self.coeff.iter_mut().for_each(|(_, a)| *a *= val);
195        self
196    }
197
198    /// Remove variable terms whose coefficients are less than or equal to [`f64::EPSILON`].
199    pub fn sparsify(&mut self) {
200        self.coeff.retain(|_, a| a.abs() > f64::EPSILON);
201    }
202}
203
204impl QuadExpr {
205    /// Create a new empty quadratic expression
206    pub fn new() -> Self {
207        QuadExpr::default()
208    }
209
210    /// Does this expression evaluate (close to) `0.0f64`?
211    pub fn is_empty(&self) -> bool {
212        self.qcoeffs.is_empty() && self.linexpr.is_empty()
213    }
214
215    #[allow(clippy::type_complexity)]
216    /// Decompose the expression into the quadratic terms and the remaining linear expression.
217    ///
218    /// The quadratic terms are returned in a hashmap mapping the non-linear term to its coefficient.
219    /// The terms are simplified so the hashmap contains at most one of `(x,y)` and `(y,x)`.
220    pub fn into_parts(self) -> (FnvHashMap<(Var, Var), f64>, LinExpr) {
221        (self.qcoeffs, self.linexpr)
222    }
223
224    /// Add a linear term into the expression.
225    pub fn add_term(&mut self, coeff: f64, var: Var) -> &mut Self {
226        self.linexpr.add_term(coeff, var);
227        self
228    }
229
230    /// Add a quadratic term into the expression.
231    pub fn add_qterm(&mut self, coeff: f64, rowvar: Var, colvar: Var) -> &mut Self {
232        if rowvar.id > colvar.id {
233            // we don't bother checking the model_id here, it gets check when this object is passed to the model
234            return self.add_qterm(coeff, colvar, rowvar);
235        }
236        self.qcoeffs
237            .entry((rowvar, colvar))
238            .and_modify(|c| *c += coeff)
239            .or_insert(coeff);
240        self
241    }
242
243    /// Add a constant into the expression.
244    pub fn add_constant(&mut self, constant: f64) -> &mut Self {
245        self.linexpr.add_constant(constant);
246        self
247    }
248
249    /// Get the offset value (constant)
250    pub fn get_offset(&self) -> f64 {
251        self.linexpr.get_offset()
252    }
253
254    /// Set the constant offset,  returning the old one
255    pub fn set_offset(&mut self, val: f64) -> f64 {
256        self.linexpr.set_offset(val)
257    }
258
259    /// Get actual value of the expression.
260    pub fn get_value(&self, model: &Model) -> Result<f64> {
261        let coeff = self.qcoeffs.values();
262        let rowvals = model.get_obj_attr_batch(attr::X, self.qcoeffs.keys().map(|&(_, x)| x))?;
263        let colvals = model.get_obj_attr_batch(attr::X, self.qcoeffs.keys().map(|&(x, _)| x))?;
264        let total = coeff
265            .zip(rowvals)
266            .zip(colvals)
267            .map(|((&a, x), y)| a * x * y)
268            .sum::<f64>()
269            + self.linexpr.get_value(model)?;
270        Ok(total)
271    }
272
273    /// Multiply expression by a scalar
274    pub fn mul_scalar(&mut self, val: f64) -> &mut Self {
275        self.linexpr.mul_scalar(val);
276        self.qcoeffs.iter_mut().for_each(|(_, a)| *a *= val);
277        self
278    }
279
280    /// Return a reference to the linear + constant part of the expression
281    pub fn affine_part(&self) -> &LinExpr {
282        &self.linexpr
283    }
284
285    /// number of **linear** terms in the expression (excluding the constant)
286    pub fn num_terms(&self) -> usize {
287        self.linexpr.num_terms()
288    }
289
290    /// Returns an iterator over the terms excluding the offset (item type is `(&Var, &f64)`)
291    pub fn iter_terms(&self) -> std::collections::hash_map::Iter<Var, f64> {
292        self.linexpr.iter_terms()
293    }
294
295    /// number of quadtratic terms in the expression
296    pub fn num_qterms(&self) -> usize {
297        self.qcoeffs.len()
298    }
299
300    /// Returns an iterator over the terms excluding the offset (item type is `(&Var, &f64)`)
301    pub fn iter_qterms(&self) -> std::collections::hash_map::Iter<(Var, Var), f64> {
302        self.qcoeffs.iter()
303    }
304
305    /// Remove variable terms whose coefficients are less than or equal to [`f64::EPSILON`].
306    pub fn sparsify(&mut self) {
307        self.linexpr.sparsify();
308        self.qcoeffs.retain(|_, a| a.abs() > f64::EPSILON);
309    }
310}
311
312impl Add for Expr {
313    type Output = Self;
314    fn add(self, rhs: Self) -> Self {
315        use self::Expr::*;
316        match (self, rhs) {
317            (Constant(a), Constant(b)) => Constant(a + b),
318            (Constant(c), Term(a, x)) => {
319                let mut e = LinExpr::new();
320                e.add_constant(c);
321                e.add_term(a, x);
322                e.into()
323            }
324            (Constant(c), QTerm(a, x, y)) => {
325                let mut e = QuadExpr::new();
326                e.add_qterm(a, x, y);
327                e.add_constant(c);
328                e.into()
329            }
330            (Constant(c), Linear(mut e)) => {
331                e.add_constant(c);
332                e.into()
333            }
334            (Constant(c), Quad(mut e)) => {
335                e.add_constant(c);
336                e.into()
337            }
338            (Term(a, x), Term(b, y)) => {
339                let mut e = LinExpr::new();
340                e.add_term(a, x);
341                e.add_term(b, y);
342                e.into()
343            }
344            (Term(a, x), QTerm(b, y1, y2)) => {
345                let mut e = QuadExpr::new();
346                e.add_term(a, x);
347                e.add_qterm(b, y1, y2);
348                e.into()
349            }
350            (Term(a, x), Linear(mut e)) => {
351                e.add_term(a, x);
352                e.into()
353            }
354            (Term(a, x), Quad(mut e)) => {
355                e.add_term(a, x);
356                e.into()
357            }
358            (QTerm(a, x1, x2), QTerm(b, y1, y2)) => {
359                let mut e = QuadExpr::new();
360                e.add_qterm(a, x1, x2);
361                e.add_qterm(b, y1, y2);
362                e.into()
363            }
364            (QTerm(a, x1, x2), Linear(e)) => {
365                let mut e = QuadExpr {
366                    linexpr: e,
367                    qcoeffs: FnvHashMap::default(),
368                };
369                e.add_qterm(a, x1, x2);
370                e.into()
371            }
372            (QTerm(a, x1, x2), Quad(mut e)) => {
373                e.add_qterm(a, x1, x2);
374                e.into()
375            }
376            (Linear(mut e1), Linear(e2)) => {
377                let (coeffs, c) = e2.into_parts();
378                e1.add_constant(c);
379                for (x, a) in coeffs {
380                    e1.add_term(a, x);
381                }
382                e1.into()
383            }
384            (Linear(le), Quad(mut qe)) => {
385                qe.linexpr = (Linear(qe.linexpr) + Linear(le)).into_linexpr().unwrap();
386                qe.into()
387            }
388            (Quad(mut e1), Quad(e2)) => {
389                let (qcoeffs, linexpr) = e2.into_parts();
390                for ((x, y), a) in qcoeffs {
391                    e1.add_qterm(a, x, y);
392                }
393                e1.linexpr = (Linear(e1.linexpr) + Linear(linexpr))
394                    .into_linexpr()
395                    .unwrap();
396                e1.into()
397            }
398            // swap operands
399            (lhs, rhs) => rhs + lhs,
400        }
401    }
402}
403
404macro_rules! impl_from_prim_for_expr {
405    ($t:ty) => {
406        impl From<$t> for Expr {
407            fn from(val: $t) -> Expr {
408                Expr::Constant(val as f64)
409            }
410        }
411    };
412}
413
414impl_all_primitives!(impl_from_prim_for_expr; );
415
416impl From<LinExpr> for Expr {
417    fn from(val: LinExpr) -> Expr {
418        Expr::Linear(val)
419    }
420}
421
422impl From<QuadExpr> for Expr {
423    fn from(val: QuadExpr) -> Expr {
424        Expr::Quad(val)
425    }
426}
427
428impl<T: Copy + Into<Expr>> From<&T> for Expr {
429    fn from(val: &T) -> Expr {
430        (*val).into()
431    }
432}
433
434impl Sub for Expr {
435    type Output = Self;
436    fn sub(self, rhs: Self) -> Self {
437        self + (-rhs)
438    }
439}
440
441impl Add for Var {
442    type Output = Expr;
443    fn add(self, rhs: Self) -> Expr {
444        let lhs: Expr = self.into();
445        let rhs: Expr = rhs.into();
446        lhs + rhs
447    }
448}
449
450impl Mul for Var {
451    type Output = Expr;
452    fn mul(self, rhs: Self) -> Expr {
453        Expr::QTerm(1.0, self, rhs)
454    }
455}
456
457impl Sub for Var {
458    type Output = Expr;
459    fn sub(self, rhs: Self) -> Expr {
460        self + (-rhs)
461    }
462}
463
464macro_rules! impl_mul_t_expr {
465  ($p:ty, $($t:ty),+) => {
466    impl Mul<$p> for Expr {
467      type Output = Expr;
468      fn mul(self, rhs: $p) -> Expr {
469        use self::Expr::*;
470        let rhs = rhs as f64;
471        match self {
472          Constant(a) => Constant(a * rhs),
473          Term(a, x) => Term(a*rhs, x),
474          QTerm(a, x, y) => QTerm(a*rhs, x, y),
475          Linear(mut e) => {
476            e.mul_scalar(rhs);
477            e.into()
478          }
479          Quad(mut e) => {
480            e.mul_scalar(rhs);
481            e.into()
482          }
483        }
484      }
485    }
486
487    impl Mul<Expr> for $p {
488      type Output = Expr;
489      fn mul(self, rhs: Expr) -> Expr { rhs*self }
490    }
491
492    $(
493      impl Mul<$t> for $p {
494        type Output = Expr;
495        fn mul(self, rhs: $t) -> Expr { self * <$t as Into<Expr>>::into(rhs) }
496      }
497
498      impl Mul<$p> for $t {
499        type Output = Expr;
500        fn mul(self, rhs: $p) -> Expr { rhs*self }
501      }
502
503    )+
504  };
505}
506
507impl_all_primitives!(impl_mul_t_expr; Var, LinExpr, QuadExpr );
508
509macro_rules! impl_add_nonprim_expr {
510  ($($t:ty),+) => {
511    $(
512      impl Add<$t> for Expr {
513        type Output = Expr;
514        fn add(self, rhs: $t) -> Expr { self + Expr::from(rhs) }
515      }
516
517
518      impl Add<Expr> for $t {
519        type Output = Expr;
520        fn add(self, rhs: Expr) -> Expr { rhs + self }
521      }
522
523    )+
524  }
525}
526
527macro_rules! impl_add_prim_t {
528  ($p:ty, $($t:ty),+) => {
529    $(
530      impl Add<$p> for $t {
531        type Output = Expr;
532        fn add(self, rhs: $p) -> Expr { Expr::from(self) + Expr::from(rhs) }
533      }
534
535      impl Add<$t> for $p {
536        type Output = Expr;
537        fn add(self, rhs: $t) -> Expr { Expr::from(rhs) + Expr::from(self) }
538      }
539    )+
540  }
541}
542
543impl_add_nonprim_expr!(Var, LinExpr, QuadExpr);
544impl_all_primitives!(impl_add_prim_t; Expr, Var, LinExpr, QuadExpr );
545
546macro_rules! impl_sub_nonprim_expr {
547    ($($t:ty),+) => {
548    $(
549      impl Sub<$t> for Expr {
550        type Output = Expr;
551        fn sub(self, rhs : $t) -> Expr { self + (-Expr::from(rhs))}
552      }
553
554      impl Sub<Expr> for $t {
555        type Output = Expr;
556        fn sub(self, rhs: Expr) -> Expr { Expr::from(self) + (-rhs) }
557      }
558    )+
559  };
560}
561
562macro_rules! impl_sub_prim_t {
563  ($p:ty, $($t:ty),+) => {
564    $(
565      impl Sub<$p> for $t {
566        type Output = Expr;
567        fn sub(self, rhs: $p) -> Expr { Expr::from(self) + -Expr::from(rhs) }
568      }
569
570      impl Sub<$t> for $p {
571        type Output = Expr;
572        fn sub(self, rhs: $t) -> Expr { Expr::from(self) + -Expr::from(rhs)  }
573      }
574    )+
575  }
576}
577
578impl_sub_nonprim_expr!(Var, LinExpr, QuadExpr);
579impl_all_primitives!(impl_sub_prim_t; Expr, Var, LinExpr, QuadExpr);
580
581impl Neg for Var {
582    type Output = Expr;
583    fn neg(self) -> Expr {
584        Expr::Term(-1.0, self)
585    }
586}
587
588impl Neg for Expr {
589    type Output = Expr;
590    fn neg(self) -> Expr {
591        use self::Expr::*;
592        match self {
593            Constant(a) => Constant(-a),
594            Term(a, x) => Term(-a, x),
595            QTerm(a, x, y) => QTerm(-a, x, y),
596            other => -1.0 * other,
597        }
598    }
599}
600
601impl<A: Into<Expr>> Sum<A> for Expr {
602    fn sum<I>(mut iter: I) -> Expr
603    where
604        I: Iterator<Item = A>,
605    {
606        let mut total = iter.next().map_or(Expr::Constant(0.0), Into::into);
607        for x in iter {
608            total = total + x.into();
609        }
610        total
611    }
612}
613
614/// Convenience trait for summing over iterators to produce a single `Expr`.
615///
616/// The [`c!`](c) macro uses `Expr::from` to convert inputs to `Expr` objects.
617/// Because [`Sum`] is generic over the iterator item type, this code
618/// ```compile_fail
619/// # use grb::prelude::*;
620/// let mut model = Model::new("")?;
621/// let x = add_binvar!(model)?;
622/// let y = add_binvar!(model)?;
623/// let z = add_binvar!(model)?;
624/// let vars = [x, y, z];
625/// let constraint = c!( vars.iter().sum() == 1 );
626/// # Ok::<(), grb::Error>(())
627/// ```
628/// produces a compilation error:
629/// ```console
630///   | let constraint = c!( vars.iter().sum() == 1 );
631///   |                      ^^^^ cannot infer type for enum `Expr`
632/// ```
633/// `GurobiSum` specialises the type parameter to work around this, so the following compile:
634/// ```
635/// # use grb::prelude::*;
636/// # let mut model = Model::new("")?;
637/// # let x = add_binvar!(model)?;
638/// # let y = add_binvar!(model)?;
639/// # let z = add_binvar!(model)?;
640/// # let vars = [x, y, z];
641/// let constraint = c!( vars.iter().grb_sum() == 1 );
642/// # Ok::<(), grb::Error>(())
643/// ```
644/// Note that unlike [`Sum`], the iterator bound is [`IntoIterator`] rather than
645/// [`Iterator`], so the `.iter()` call above can be replaced with a borrow:
646/// ```
647/// # use grb::prelude::*;
648/// # let mut model = Model::new("")?;
649/// # let x = add_binvar!(model)?;
650/// # let y = add_binvar!(model)?;
651/// # let z = add_binvar!(model)?;
652/// # let vars = [x, y, z];
653/// let constraint = c!( (&vars).grb_sum() == 1 );
654/// # Ok::<(), grb::Error>(())
655/// ```
656/// This may or may not be more ergonomic.
657///
658/// TLDR: Use `.grb_sum()` instead of `sum()` when summing over an iterator of variables or variable expressions.
659pub trait GurobiSum {
660    /// Additively combine an iterator (or container) of one or more expressions into a single expression.
661    fn grb_sum(self) -> Expr;
662}
663
664impl<T, I> GurobiSum for I
665where
666    T: Into<Expr>,
667    I: IntoIterator<Item = T>,
668{
669    fn grb_sum(self) -> Expr {
670        self.into_iter().sum()
671    }
672}
673
674/// A helper struct for pretty-printing variables, expressions and constraints
675/// (see the [`AttachModel`] trait)
676pub struct Attached<'a, T> {
677    pub(crate) inner: &'a T,
678    pub(crate) model: &'a Model,
679}
680
681/// A convenvience trait for displaying variable names with the [`Debug`] trait.
682///
683/// Variable names are not stored inside [`Var`] objects, but instead are queried from the
684/// Gurobi C API.  This means the printing an expression with `println!("{:?}", &expr)`
685/// will give some rather ugly output
686/// ```
687/// # use grb::prelude::*;
688/// let mut model = Model::new("")?;
689/// let x: Vec<_> = (0..5)
690/// .map(|i| add_ctsvar!(model, name: &format!("x[{}]", i)).unwrap() )
691/// .collect();
692///
693/// println!("{:?}", x[0]);
694/// # Ok::<(), grb::Error>(())
695/// ```
696/// Output:
697/// ```shell
698/// Var { id: 0, model_id: 0 }
699/// ```
700/// Printing [`Expr`] and constraints this way quickly gets unreadable.
701///
702/// The `AttachModel` trait provides an `.attach(&model)` method, with simply bundles a `&`[`Model`] with
703/// a reference to the object. The [`Debug`] trait is implemented for this bundled type ([`Attached`])
704/// and properly queries the model for variable names. Because querying the `VarName` of a variable can fail
705/// (for example if the model hasn't been updated since the variable was added or the `.attach(...)` was
706/// called with the wrong model), a formatting error can occur.
707///
708///
709/// ```
710/// # use grb::prelude::*;
711/// # let mut model = Model::new("")?;
712/// # let x: Vec<_> = (0..5)
713/// # .map(|i| add_ctsvar!(model, name: &format!("x[{}]", i)).unwrap() )
714/// # .collect();
715/// model.update()?; // Important! Otherwise, the formatter will panic.
716/// println!("{:?}", x[0].attach(&model));
717/// println!("{:?}", (x[0] + x[1]).attach(&model));
718/// println!("{:?}", c!( x[1..].grb_sum() >= x[0] ).attach(&model));
719/// println!("{:?}", c!( x.grb_sum() in 0..1 ).attach(&model));
720/// # Ok::<(), grb::Error>(())
721/// ```
722/// Output:
723/// ```console
724/// x[0]
725/// x[1] + x[0]
726/// x[4] + x[1] + x[3] + x[2] ≥ x[0]
727/// x[4] + x[1] + x[0] + x[3] + x[2] ∈ [0, 1]
728/// ```
729pub trait AttachModel {
730    /// Attach a model reference to this object for formatting with [`Debug`]
731    fn attach<'a>(&'a self, model: &'a Model) -> Attached<'a, Self>
732    where
733        Self: Sized,
734    {
735        Attached { inner: self, model }
736    }
737}
738
739impl AttachModel for LinExpr {}
740impl AttachModel for QuadExpr {}
741impl AttachModel for Expr {}
742impl AttachModel for Var {}
743
744fn float_fmt_helper(x: f64, ignore_val: f64) -> (Option<f64>, bool) {
745    let positive = x > -f64::EPSILON;
746    if (x - ignore_val).abs() < f64::EPSILON {
747        (None, positive)
748    } else if positive {
749        (Some(x), positive)
750    } else {
751        (Some(-x), positive)
752    }
753}
754
755impl From<Error> for fmt::Error {
756    fn from(err: Error) -> fmt::Error {
757        eprintln!("fmt error cause by: {err}");
758        fmt::Error {}
759    }
760}
761
762impl fmt::Debug for Attached<'_, LinExpr> {
763    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
764        if self.inner.is_empty() {
765            return f.write_str("<empty LinExpr>");
766        }
767
768        let (offset, positive) = float_fmt_helper(self.inner.get_offset(), 0.0);
769
770        let mut is_first_term = false;
771        if let Some(offset) = offset {
772            write!(f, "{}", if positive { offset } else { -offset })?;
773        } else {
774            is_first_term = true;
775        }
776
777        for (var, &coeff) in self.inner.iter_terms() {
778            let varname = self.model.get_obj_attr(attr::VarName, var)?;
779            let (coeff, positive) = float_fmt_helper(coeff, 1.0);
780
781            // write the operator with the previous term
782            if is_first_term {
783                is_first_term = false;
784                if !positive {
785                    f.write_char('-')?;
786                }
787            } else {
788                f.write_str(if positive { " + " } else { " - " })?;
789            }
790            if let Some(coeff) = coeff {
791                write!(f, "{coeff} ")?;
792            }
793            f.write_str(&varname)?;
794        }
795        Ok(())
796    }
797}
798
799impl fmt::Debug for Attached<'_, QuadExpr> {
800    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
801        if self.inner.is_empty() {
802            return f.write_str("<empty QuadExpr>");
803        }
804
805        let mut is_first_term = false;
806        if self.inner.linexpr.is_empty() {
807            is_first_term = true;
808        } else {
809            self.inner.linexpr.attach(self.model).fmt(f)?;
810        }
811
812        for ((x, y), &coeff) in &self.inner.qcoeffs {
813            let xname = self.model.get_obj_attr(attr::VarName, x)?;
814            let yname = self.model.get_obj_attr(attr::VarName, y)?;
815            let (coeff, positive) = float_fmt_helper(coeff, 1.0);
816            if is_first_term {
817                is_first_term = false;
818                if !positive {
819                    f.write_char('-')?;
820                }
821            } else {
822                f.write_str(if positive { " + " } else { " - " })?;
823            }
824            if let Some(coeff) = coeff {
825                write!(f, "{coeff} ")?;
826            }
827            write!(f, "{xname}*{yname}")?;
828        }
829        Ok(())
830    }
831}
832
833impl fmt::Debug for Attached<'_, Expr> {
834    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
835        use self::Expr::*;
836        match &self.inner {
837            Constant(a) => f.write_str(&a.to_string()),
838            Term(a, x) => {
839                let varname = self.model.get_obj_attr(attr::VarName, x)?;
840                if (a - 1.0).abs() >= f64::EPSILON {
841                    write!(f, "{a} ")?;
842                }
843                f.write_str(&varname)
844            }
845            QTerm(a, x, y) => {
846                let xname = self.model.get_obj_attr(attr::VarName, x)?;
847                let yname = self.model.get_obj_attr(attr::VarName, y)?;
848                if (a - 1.0).abs() >= f64::EPSILON {
849                    write!(f, "{a} ")?;
850                }
851                write!(f, "{xname}*{yname}")
852            }
853            Linear(e) => e.attach(self.model).fmt(f),
854            Quad(e) => e.attach(self.model).fmt(f),
855        }
856    }
857}
858
859impl fmt::Debug for Attached<'_, Var> {
860    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
861        Expr::from(self.inner).attach(self.model).fmt(f)
862    }
863}
864
865#[allow(unused_variables)]
866#[cfg(test)]
867mod tests {
868    use super::*;
869    extern crate self as grb;
870
871    macro_rules! make_model_with_vars {
872    ($model:ident, $($var:ident),+) => {
873
874      let mut $model = {
875        let mut e = Env::empty().unwrap();
876        e.set(param::OutputFlag, 0).unwrap();
877        Model::with_env("test", &e.start().unwrap()).unwrap()
878       };
879      $(
880        let $var = add_binvar!($model, name: stringify!($var)).unwrap();
881      )+
882      $model.update().unwrap(); // necessary to retrieve variable attributes
883    }
884  }
885
886    #[test]
887    fn simple() {
888        make_model_with_vars!(model, x, y);
889        let e: Expr = x * y + 1 + x + 2.0 * y;
890        e.into_linexpr().unwrap_err(); // should be quadratic
891    }
892
893    #[test]
894    fn nested() {
895        make_model_with_vars!(model, x, y);
896        let e = (x * y) * 3 + 2 * (x + 2.0 * y);
897    }
898
899    #[test]
900    fn multiplication_commutes() {
901        make_model_with_vars!(model, x, y, z);
902        let _ = x - y;
903        let e = y * x - x * y;
904        dbg!(e.attach(&model));
905        let mut e = e.into_quadexpr();
906        assert!(!e.is_empty());
907        e.sparsify();
908        assert!(e.is_empty());
909    }
910
911    #[test]
912    fn multiplication() {
913        make_model_with_vars!(model, x, y);
914        let e = 2 * x;
915        let e = x * x;
916        let e = 2 * (x * x);
917    }
918
919    #[test]
920    fn addition() {
921        make_model_with_vars!(model, x, y);
922        let e = 2 + x;
923        let e = x + y;
924        let e = x + x;
925        let e = x + 2.8 * y + 2 * x;
926    }
927
928    #[test]
929    fn subtraction() {
930        make_model_with_vars!(model, x, y);
931        let e = 2 - x;
932        let mut e = (x - x).into_linexpr().unwrap();
933        e.sparsify();
934        assert!(e.is_empty());
935        let e = 2 * x - y - x;
936
937        let e1: Expr = 2 * x + 1.0 * y;
938        let e2: Expr = 4 - 3 * y;
939        let e: LinExpr = (e1 - e2).into_linexpr().unwrap();
940        assert!((e.get_offset() - -4.0).abs() < f64::EPSILON);
941
942        for (&var, &coeff) in e.iter_terms() {
943            if var == x {
944                assert!((coeff - 2.0).abs() < f64::EPSILON);
945            }
946            if var == x {
947                assert!((coeff - 4.0) < f64::EPSILON);
948            }
949        }
950    }
951
952    #[test]
953    fn negate() {
954        make_model_with_vars!(model, x);
955        let q = -x;
956        let y = -q;
957        if let Expr::Term(a, var) = y {
958            assert_eq!(x, var);
959            assert_eq!(a, 1.0);
960        } else {
961            panic!("{:?}", y);
962        }
963        let q = -(x * x);
964        eprintln!("{:?}", q.attach(&model));
965    }
966
967    #[test]
968    fn summation() {
969        make_model_with_vars!(model, x, y, z);
970        let vars = [x, y, z, x];
971        let e: Expr = vars.iter().copied().sum();
972        eprintln!("{:?}", &e);
973        let e = e.into_linexpr().unwrap();
974        assert_eq!(e.coeff.len(), 3);
975
976        let vars = [2 * x, -y, -z, 0.2 * x];
977        let e: Expr = vars.iter().cloned().sum();
978        let e = e.into_linexpr().unwrap();
979        assert_eq!(e.coeff.len(), 3);
980    }
981
982    #[test]
983    fn linexpr_debug_fmt() {
984        make_model_with_vars!(m, x, y);
985        let e = 2usize * y;
986        let s = format!("{:?}", e.attach(&m));
987        assert_eq!("2 y", s.to_string());
988        eprintln!("{s}");
989        let e = x * y - 2.0f64 * (x * x);
990        eprintln!("{:?}", e.attach(&m));
991    }
992}