1#![allow(clippy::many_single_char_names)]
2use 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#[derive(Debug, Clone)]
15pub enum Expr {
16 Quad(QuadExpr),
18 Linear(LinExpr),
20 QTerm(f64, Var, Var),
22 Term(f64, Var),
24 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 pub fn is_linear(&self) -> bool {
46 !matches!(self, Expr::QTerm(..) | Expr::Quad(..))
47 }
48
49 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 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#[derive(Debug, Clone, Default)]
87pub struct LinExpr {
88 coeff: FnvHashMap<Var, f64>,
89 offset: f64,
90}
91
92#[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 pub fn new() -> Self {
130 LinExpr::default()
131 }
132
133 pub fn is_empty(&self) -> bool {
140 self.offset.abs() < f64::EPSILON && self.coeff.is_empty()
141 }
142
143 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 pub fn add_constant(&mut self, constant: f64) -> &mut Self {
154 self.offset += constant;
155 self
156 }
157
158 pub fn get_offset(&self) -> f64 {
160 self.offset
161 }
162
163 pub fn set_offset(&mut self, val: f64) -> f64 {
165 std::mem::replace(&mut self.offset, val)
166 }
167
168 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 pub fn into_parts(self) -> (FnvHashMap<Var, f64>, f64) {
178 (self.coeff, self.offset)
179 }
180
181 pub fn num_terms(&self) -> usize {
183 self.coeff.len()
184 }
185
186 pub fn iter_terms(&self) -> std::collections::hash_map::Iter<Var, f64> {
188 self.coeff.iter()
189 }
190
191 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 pub fn sparsify(&mut self) {
200 self.coeff.retain(|_, a| a.abs() > f64::EPSILON);
201 }
202}
203
204impl QuadExpr {
205 pub fn new() -> Self {
207 QuadExpr::default()
208 }
209
210 pub fn is_empty(&self) -> bool {
212 self.qcoeffs.is_empty() && self.linexpr.is_empty()
213 }
214
215 #[allow(clippy::type_complexity)]
216 pub fn into_parts(self) -> (FnvHashMap<(Var, Var), f64>, LinExpr) {
221 (self.qcoeffs, self.linexpr)
222 }
223
224 pub fn add_term(&mut self, coeff: f64, var: Var) -> &mut Self {
226 self.linexpr.add_term(coeff, var);
227 self
228 }
229
230 pub fn add_qterm(&mut self, coeff: f64, rowvar: Var, colvar: Var) -> &mut Self {
232 if rowvar.id > colvar.id {
233 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 pub fn add_constant(&mut self, constant: f64) -> &mut Self {
245 self.linexpr.add_constant(constant);
246 self
247 }
248
249 pub fn get_offset(&self) -> f64 {
251 self.linexpr.get_offset()
252 }
253
254 pub fn set_offset(&mut self, val: f64) -> f64 {
256 self.linexpr.set_offset(val)
257 }
258
259 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 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 pub fn affine_part(&self) -> &LinExpr {
282 &self.linexpr
283 }
284
285 pub fn num_terms(&self) -> usize {
287 self.linexpr.num_terms()
288 }
289
290 pub fn iter_terms(&self) -> std::collections::hash_map::Iter<Var, f64> {
292 self.linexpr.iter_terms()
293 }
294
295 pub fn num_qterms(&self) -> usize {
297 self.qcoeffs.len()
298 }
299
300 pub fn iter_qterms(&self) -> std::collections::hash_map::Iter<(Var, Var), f64> {
302 self.qcoeffs.iter()
303 }
304
305 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 (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
614pub trait GurobiSum {
660 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
674pub struct Attached<'a, T> {
677 pub(crate) inner: &'a T,
678 pub(crate) model: &'a Model,
679}
680
681pub trait AttachModel {
730 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 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(); }
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(); }
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}