1use std::{
24 collections::{BTreeMap, HashMap},
25 fmt::{self},
26};
27
28use crate::{
29 expressions::{
30 Atomic, BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
31 fraction::Fraction,
32 },
33 lia_threshold_automaton::{
34 ConstraintRewriteError,
35 general_to_lia::classify_into_lia::split_pairs_into_atom_and_threshold,
36 },
37};
38
39#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
45pub struct WeightedSum<T>
46where
47 T: Atomic,
48{
49 weight_map: BTreeMap<T, Fraction>,
51}
52
53impl<T: Atomic> WeightedSum<T> {
54 pub fn is_zero(&self) -> bool {
56 self.weight_map.is_empty()
57 }
58
59 pub fn is_integer_form(&self) -> bool {
85 self.weight_map.values().all(|coef| coef.is_integer())
86 }
87
88 pub fn new<F, I, V>(weight_map: I) -> Self
92 where
93 F: Into<Fraction>,
94 V: Into<T>,
95 I: IntoIterator<Item = (V, F)>,
96 {
97 let weight_map = weight_map
99 .into_iter()
100 .map(|(v, c)| (v.into(), c.into()))
101 .filter(|(_, coef)| *coef != Fraction::from(0))
102 .collect();
103
104 Self { weight_map }
105 }
106
107 pub fn new_empty() -> Self {
109 Self {
110 weight_map: BTreeMap::new(),
111 }
112 }
113
114 pub fn get_factor(&self, v: &T) -> Option<&Fraction> {
130 self.weight_map.get(v)
131 }
132
133 pub fn get_lcm_of_denominators(&self) -> u32 {
161 self.weight_map
162 .values()
163 .fold(1, |acc, coef| num::Integer::lcm(&acc, &coef.denominator()))
164 }
165
166 pub fn scale(&mut self, factor: Fraction) {
190 if factor == Fraction::from(0) {
191 self.weight_map.clear();
192 return;
193 }
194
195 for coef in self.weight_map.values_mut() {
196 *coef *= factor;
197 }
198 }
199
200 pub fn contains(&self, t: &T) -> bool {
220 self.weight_map.contains_key(t)
221 }
222
223 fn get_integer_expression<S>(&self) -> IntegerExpression<S>
230 where
231 T: Into<IntegerExpression<S>>,
232 S: Atomic,
233 {
234 self.weight_map
235 .iter()
236 .fold(IntegerExpression::Const(0), |acc, (v, c)| {
237 debug_assert!(c != &Fraction::from(0));
238 debug_assert!(c.is_integer());
239
240 let mut expr = v.clone().into();
241 if *c != Fraction::from(1) {
242 expr = IntegerExpression::from(*c) * expr;
243 }
244
245 if acc == IntegerExpression::Const(0) {
247 return expr;
248 }
249 acc + expr
250 })
251 }
252
253 pub fn get_atoms_appearing(&self) -> impl Iterator<Item = &T> {
255 self.weight_map.keys()
256 }
257}
258
259impl<T, F, I> From<I> for WeightedSum<T>
260where
261 T: Atomic,
262 F: Into<Fraction> + Clone,
263 I: IntoIterator<Item = (T, F)>,
264{
265 fn from(value: I) -> Self {
266 Self::new(value)
267 }
268}
269
270impl<T: Atomic> fmt::Display for WeightedSum<T> {
271 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
272 let mut it = self.weight_map.iter().collect::<Vec<_>>();
275 it.sort_by_key(|(v, _)| v.to_string());
276 let mut it = it.into_iter();
277
278 if let Some((v, coef)) = it.next() {
279 display_factor_pair_omit_one(f, coef, v)?;
280 }
281
282 it.try_for_each(|(v, coef)| {
283 write!(f, " + ")?;
284 display_factor_pair_omit_one(f, coef, v)
285 })?;
286
287 Ok(())
288 }
289}
290
291impl<'a, T: Atomic> IntoIterator for &'a WeightedSum<T> {
292 type Item = (&'a T, &'a Fraction);
293
294 type IntoIter = std::collections::btree_map::Iter<'a, T, Fraction>;
295
296 fn into_iter(self) -> Self::IntoIter {
297 self.weight_map.iter()
298 }
299}
300
301fn display_factor_pair_omit_one<T: fmt::Display>(
306 f: &mut std::fmt::Formatter<'_>,
307 factor: &Fraction,
308 x: &T,
309) -> std::fmt::Result {
310 if factor == &1.into() {
311 write!(f, "{x}")
312 } else {
313 write!(f, "{factor} * {x}")
314 }
315}
316
317#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
324pub struct Threshold {
325 weighted_parameters: WeightedSum<Parameter>,
326 constant: Fraction,
327}
328
329impl Threshold {
330 pub fn new<S, T>(weighted_parameters: S, constant: T) -> Self
332 where
333 S: Into<WeightedSum<Parameter>>,
334 T: Into<Fraction>,
335 {
336 Self {
337 weighted_parameters: weighted_parameters.into(),
338 constant: constant.into(),
339 }
340 }
341
342 pub fn from_integer_comp_expr<T: Atomic>(
346 lhs: IntegerExpression<T>,
347 rhs: IntegerExpression<T>,
348 ) -> Result<(HashMap<T, Fraction>, Threshold), ConstraintRewriteError> {
349 split_pairs_into_atom_and_threshold(lhs, rhs)
350 }
351
352 pub fn from_const<T: Into<Fraction>>(c: T) -> Self {
354 Self {
355 weighted_parameters: WeightedSum::new_empty(),
356 constant: c.into(),
357 }
358 }
359
360 pub fn scale<T: Into<Fraction>>(&mut self, factor: T) {
362 let factor = factor.into();
363 self.weighted_parameters.scale(factor);
364 self.constant *= factor;
365 }
366
367 pub fn is_constant(&self) -> bool {
369 self.weighted_parameters.is_zero()
370 }
371
372 pub fn is_zero(&self) -> bool {
375 self.weighted_parameters.is_zero() && self.constant == Fraction::from(0)
376 }
377
378 pub fn get_const(&self) -> Option<Fraction> {
382 if self.weighted_parameters.is_zero() {
383 return Some(self.constant);
384 }
385
386 None
387 }
388
389 pub fn add_const<F: Into<Fraction>>(&mut self, c: F) {
391 self.constant += c.into()
392 }
393
394 pub fn sub_const<F: Into<Fraction>>(&mut self, c: F) {
396 self.constant -= c.into()
397 }
398}
399
400impl fmt::Display for Threshold {
401 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
402 if self.weighted_parameters.is_zero() {
403 return write!(f, "{}", self.constant);
404 }
405
406 write!(f, "{}", self.weighted_parameters)?;
407 if self.constant != Fraction::from(0) {
408 write!(f, " + {}", self.constant)?;
409 }
410
411 Ok(())
412 }
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
417pub enum ThresholdCompOp {
418 Geq,
420 Lt,
422}
423
424impl From<ThresholdCompOp> for ComparisonOp {
425 fn from(value: ThresholdCompOp) -> Self {
426 match value {
427 ThresholdCompOp::Geq => ComparisonOp::Geq,
428 ThresholdCompOp::Lt => ComparisonOp::Lt,
429 }
430 }
431}
432
433impl fmt::Display for ThresholdCompOp {
434 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435 match self {
436 ThresholdCompOp::Geq => write!(f, ">="),
437 ThresholdCompOp::Lt => write!(f, "<"),
438 }
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
454pub struct ThresholdConstraint(ThresholdCompOp, Threshold);
455
456impl ThresholdConstraint {
457 pub fn new<S, T>(op: ThresholdCompOp, weighted_parameters: S, constant: T) -> Self
459 where
460 S: Into<WeightedSum<Parameter>>,
461 T: Into<Fraction>,
462 {
463 let thr = Threshold::new(weighted_parameters, constant);
464 Self(op, thr)
465 }
466
467 pub fn new_from_thr(op: ThresholdCompOp, thr: Threshold) -> Self {
469 Self(op, thr)
470 }
471
472 pub fn scale<T: Into<Fraction>>(&mut self, factor: T) {
474 let factor = factor.into();
475 if factor.is_negative() {
477 match self.0 {
478 ThresholdCompOp::Geq => {
479 self.0 = ThresholdCompOp::Lt;
480 self.1.add_const(1);
481 }
482 ThresholdCompOp::Lt => {
483 self.0 = ThresholdCompOp::Geq;
484 self.1.sub_const(1);
485 }
486 }
487 }
488
489 self.1.scale(factor);
490 }
491
492 pub fn get_op(&self) -> ThresholdCompOp {
494 self.0
495 }
496
497 pub fn get_threshold(&self) -> &Threshold {
499 &self.1
500 }
501}
502
503impl fmt::Display for ThresholdConstraint {
504 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
505 write!(f, "{} {}", self.0, self.1)
506 }
507}
508
509#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
513pub(crate) struct ThresholdConstraintOver<T> {
514 variable: T,
515 thr_constr: ThresholdConstraint,
516}
517
518impl<T> ThresholdConstraintOver<T> {
519 pub fn new(variable: T, thr_constr: ThresholdConstraint) -> Self {
521 Self {
522 variable,
523 thr_constr,
524 }
525 }
526
527 pub fn is_upper_guard(&self) -> bool {
532 matches!(self.thr_constr.get_op(), ThresholdCompOp::Lt)
533 }
534
535 pub fn is_lower_guard(&self) -> bool {
540 matches!(self.thr_constr.get_op(), ThresholdCompOp::Geq)
541 }
542
543 pub fn get_variable(&self) -> &T {
545 &self.variable
546 }
547
548 pub fn get_threshold(&self) -> &Threshold {
550 self.thr_constr.get_threshold()
551 }
552
553 pub fn get_threshold_constraint(&self) -> &ThresholdConstraint {
555 &self.thr_constr
556 }
557
558 pub fn encode_to_boolean_expr<S>(&self) -> BooleanExpression<S>
562 where
563 S: Atomic,
564 T: IntoNoDivBooleanExpr<S>,
565 Threshold: IntoNoDivBooleanExpr<S>,
566 {
567 self.variable.encode_comparison_to_boolean_expression(
568 self.thr_constr.get_op().into(),
569 self.thr_constr.get_threshold(),
570 )
571 }
572}
573
574impl<S, T> From<ThresholdConstraintOver<T>> for BooleanExpression<S>
575where
576 S: Atomic,
577 T: IntoNoDivBooleanExpr<S>,
578 Threshold: IntoNoDivBooleanExpr<S>,
579{
580 fn from(val: ThresholdConstraintOver<T>) -> Self {
581 val.encode_to_boolean_expr()
582 }
583}
584
585impl<T> fmt::Display for ThresholdConstraintOver<T>
586where
587 T: fmt::Display,
588{
589 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
590 write!(f, "{} {}", self.variable, self.thr_constr)
591 }
592}
593
594pub trait IntoNoDivBooleanExpr<T>
607where
608 T: Atomic,
609{
610 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<T>;
621
622 fn get_lcm_of_denominators(&self) -> u32;
627
628 fn encode_comparison_to_boolean_expression<Q>(
637 &self,
638 comparison_op: ComparisonOp,
639 other: &Q,
640 ) -> BooleanExpression<T>
641 where
642 Q: IntoNoDivBooleanExpr<T>,
643 {
644 let lcm_self = self.get_lcm_of_denominators();
645 let lcm_other = other.get_lcm_of_denominators();
646 let lcm = num::integer::lcm(lcm_self, lcm_other);
647
648 let self_s = self.get_scaled_integer_expression(lcm);
649 let other_s = other.get_scaled_integer_expression(lcm);
650
651 BooleanExpression::ComparisonExpression(Box::new(self_s), comparison_op, Box::new(other_s))
652 }
653}
654
655impl<T> IntoNoDivBooleanExpr<T> for Parameter
656where
657 Self: Into<IntegerExpression<T>>,
658 T: Atomic,
659{
660 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<T> {
661 if scaling_factor == 0 {
662 return IntegerExpression::Const(0);
663 }
664 if scaling_factor == 1 {
665 return IntegerExpression::from(self.clone());
666 }
667
668 IntegerExpression::from(scaling_factor) * IntegerExpression::from(self.clone())
669 }
670
671 fn get_lcm_of_denominators(&self) -> u32 {
672 1 }
674}
675
676impl<T> IntoNoDivBooleanExpr<T> for Location
677where
678 T: Atomic,
679 IntegerExpression<T>: From<Self>,
680 IntegerExpression<T>: From<u32>,
681{
682 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<T> {
683 if scaling_factor == 0 {
684 return IntegerExpression::Const(0);
685 }
686 if scaling_factor == 1 {
687 return IntegerExpression::from(self.clone());
688 }
689
690 IntegerExpression::<T>::from(scaling_factor) * IntegerExpression::<T>::from(self.clone())
691 }
692
693 fn get_lcm_of_denominators(&self) -> u32 {
694 1 }
696}
697
698impl<T> IntoNoDivBooleanExpr<T> for Variable
699where
700 T: Atomic,
701 IntegerExpression<T>: From<Self>,
702 IntegerExpression<T>: From<u32>,
703{
704 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<T> {
705 if scaling_factor == 0 {
706 return IntegerExpression::Const(0);
707 }
708 if scaling_factor == 1 {
709 return IntegerExpression::from(self.clone());
710 }
711
712 IntegerExpression::<T>::from(scaling_factor) * IntegerExpression::<T>::from(self.clone())
713 }
714
715 fn get_lcm_of_denominators(&self) -> u32 {
716 1 }
718}
719
720impl<T, S> IntoNoDivBooleanExpr<S> for WeightedSum<T>
721where
722 T: Atomic + IntoNoDivBooleanExpr<S>,
723 S: Atomic,
724 IntegerExpression<S>: From<T>,
725{
726 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<S> {
727 let mut scaled = self.clone();
728 scaled.scale(Fraction::from(scaling_factor));
729 debug_assert!(
730 scaled.is_integer_form(),
731 "Scaled weighted sum is not in integer form"
732 );
733
734 scaled.get_integer_expression()
735 }
736
737 fn get_lcm_of_denominators(&self) -> u32 {
738 self.get_lcm_of_denominators()
739 }
740}
741
742impl<T> IntoNoDivBooleanExpr<T> for Threshold
743where
744 WeightedSum<Parameter>: IntoNoDivBooleanExpr<T>,
745 T: Atomic,
746{
747 fn get_scaled_integer_expression(&self, scaling_factor: u32) -> IntegerExpression<T> {
748 let scaled_constant = self.constant * Fraction::from(scaling_factor);
749 debug_assert!(
750 scaled_constant.is_integer(),
751 "Scaled constant is not an integer"
752 );
753
754 if self.weighted_parameters.is_zero() {
755 return IntegerExpression::from(scaled_constant);
756 }
757
758 let intexpr_ws = self
759 .weighted_parameters
760 .get_scaled_integer_expression(scaling_factor);
761
762 if scaled_constant == Fraction::from(0) {
763 return intexpr_ws;
764 }
765
766 intexpr_ws + IntegerExpression::from(scaled_constant)
767 }
768
769 fn get_lcm_of_denominators(&self) -> u32 {
770 let lcm_ws = self.weighted_parameters.get_lcm_of_denominators();
771 let lcm_const = self.constant.denominator();
772 num::integer::lcm(lcm_ws, lcm_const)
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use std::collections::{BTreeMap, HashSet};
779
780 use crate::{
781 expressions::{
782 BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
783 fraction::Fraction,
784 },
785 lia_threshold_automaton::integer_thresholds::{
786 IntoNoDivBooleanExpr, Threshold, ThresholdCompOp, ThresholdConstraint,
787 ThresholdConstraintOver, WeightedSum,
788 },
789 };
790
791 #[test]
792 fn test_threshold_is_zero() {
793 let thr = Threshold::new(Vec::<(Parameter, Fraction)>::new(), 0);
794 assert!(thr.is_zero());
795
796 let thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 1)]), 0);
797 assert!(!thr.is_zero());
798
799 let thr = Threshold::new(Vec::<(Parameter, Fraction)>::new(), 1);
800 assert!(!thr.is_zero());
801
802 let thr = Threshold::new(
803 BTreeMap::from([(Parameter::new("n"), 1)]),
804 -Fraction::from(1),
805 );
806 assert!(!thr.is_zero());
807 }
808
809 #[test]
810 fn test_threshold_is_constant() {
811 let thr = Threshold::new(Vec::<(Parameter, Fraction)>::new(), 0);
812 assert!(thr.is_constant());
813
814 let thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 1)]), 0);
815 assert!(!thr.is_constant());
816
817 let thr = Threshold::new(Vec::<(Parameter, Fraction)>::new(), 1);
818 assert!(thr.is_constant());
819
820 let thr = Threshold::new(
821 BTreeMap::from([(Parameter::new("n"), 1)]),
822 -Fraction::from(1),
823 );
824 assert!(!thr.is_constant());
825 }
826
827 #[test]
828 fn test_threshold_scale() {
829 let mut thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 1)]), 0);
830 thr.scale(2);
831
832 let expected = Threshold::new(BTreeMap::from([(Parameter::new("n"), 2)]), 0);
833 assert_eq!(thr, expected);
834
835 let mut thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 0)]), 1);
836 thr.scale(-Fraction::from(2));
837
838 let expected = Threshold::new(
839 BTreeMap::from([(Parameter::new("n"), Fraction::from(0))]),
840 -Fraction::from(2),
841 );
842 assert_eq!(thr, expected);
843
844 let mut thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 1)]), 0);
845 thr.scale(0);
846
847 let expected = Threshold::new(BTreeMap::from([(Parameter::new("n"), 0)]), 0);
848 assert_eq!(thr, expected);
849 }
850
851 #[test]
852 fn test_threshold_add_cons() {
853 let mut thr = Threshold::new(Vec::<(Parameter, Fraction)>::new(), 0);
854 assert!(thr.is_constant());
855
856 thr.add_const(1);
857 assert_eq!(thr.get_const().unwrap(), Fraction::from(1));
858
859 thr.sub_const(1);
860 assert_eq!(thr.get_const().unwrap(), Fraction::from(0));
861
862 let mut thr = Threshold::new(BTreeMap::from([(Parameter::new("n"), 1)]), 0);
863 thr.add_const(1);
864 assert_eq!(thr.get_const(), None);
865
866 thr.sub_const(1);
867 assert_eq!(thr.get_const(), None);
868 }
869
870 #[test]
871 fn test_threshold_constr_getters() {
872 let thrc = ThresholdConstraint::new(
873 ThresholdCompOp::Geq,
874 BTreeMap::from([
875 (Parameter::new("n"), Fraction::from(1)),
876 (Parameter::new("m"), -Fraction::from(2)),
877 ]),
878 1,
879 );
880
881 let thr = Threshold::new(
882 [
883 (Parameter::new("n"), Fraction::from(1)),
884 (Parameter::new("m"), -Fraction::from(2)),
885 ],
886 1,
887 );
888
889 assert_eq!(thrc.get_threshold(), &thr)
890 }
891
892 #[test]
893 fn test_threshold_display() {
894 let thr = ThresholdConstraint::new(
895 ThresholdCompOp::Geq,
896 BTreeMap::from([
897 (Parameter::new("n"), Fraction::from(1)),
898 (Parameter::new("m"), -Fraction::from(2)),
899 ]),
900 1,
901 );
902
903 let expected = ">= -2 * m + n + 1";
904 assert_eq!(thr.to_string(), expected);
905
906 let thr =
907 ThresholdConstraint::new(ThresholdCompOp::Geq, Vec::<(Parameter, Fraction)>::new(), 0);
908 assert_eq!(thr.to_string(), ">= 0");
909 }
910
911 #[test]
912 fn test_contains_weighted_sum() {
913 let ws = WeightedSum::new(BTreeMap::from([
914 (Variable::new("var1"), 1),
915 (Variable::new("var2"), 2),
916 ]));
917
918 assert!(ws.contains(&Variable::new("var1")));
919 assert!(!ws.contains(&Variable::new("var3")));
920 }
921
922 #[test]
923 fn test_get_atoms_appearing_ws() {
924 let ws = WeightedSum::new(BTreeMap::from([
925 (Variable::new("var1"), 1),
926 (Variable::new("var2"), 2),
927 ]));
928
929 let got_atoms: HashSet<_> = ws.get_atoms_appearing().cloned().collect();
930 let expected_atoms = HashSet::from([Variable::new("var1"), Variable::new("var2")]);
931 assert_eq!(got_atoms, expected_atoms)
932 }
933
934 #[test]
935 fn test_into_scaled_integer_expression_weighted_sum() {
936 let ws: WeightedSum<Parameter> = WeightedSum::new(BTreeMap::from([
937 (Parameter::new("var1"), 1),
938 (Parameter::new("var2"), 2),
939 (Parameter::new("var3"), 0),
940 ]));
941
942 let expected: IntegerExpression<Parameter> =
943 IntegerExpression::Param(Parameter::new("var1"))
944 + (IntegerExpression::Const(2) * IntegerExpression::Param(Parameter::new("var2")));
945
946 let ws = ws.get_scaled_integer_expression(1);
947
948 assert_eq!(ws, expected);
949
950 let ws: WeightedSum<Parameter> = WeightedSum::new(BTreeMap::from([
951 (Parameter::new("var1"), 1),
952 (Parameter::new("var2"), 2),
953 (Parameter::new("var3"), 0),
954 ]));
955
956 let expected: IntegerExpression<Parameter> = (IntegerExpression::Const(5)
957 * IntegerExpression::Param(Parameter::new("var1")))
958 + (IntegerExpression::Const(10) * IntegerExpression::Param(Parameter::new("var2")));
959
960 let ws = ws.get_scaled_integer_expression(5);
961
962 assert_eq!(ws, expected);
963 }
964
965 #[test]
966 fn test_weighted_sum_into_iter() {
967 let ws: WeightedSum<Variable> = WeightedSum::new([
968 (Variable::new("var1"), 1),
969 (Variable::new("var2"), 2),
970 (Variable::new("var3"), 0),
971 ]);
972
973 let expected: Vec<(Variable, Fraction)> = vec![
974 (Variable::new("var1"), 1.into()),
975 (Variable::new("var2"), 2.into()),
976 ];
977
978 let result: Vec<(Variable, Fraction)> =
979 (&ws).into_iter().map(|(a, b)| (a.clone(), *b)).collect();
980 assert_eq!(result, expected);
981 }
982
983 #[test]
984 fn test_to_boolean_expr_thr_constr_over_var() {
985 let constr = ThresholdConstraintOver::new(
986 Variable::new("v"),
987 ThresholdConstraint::new(ThresholdCompOp::Geq, [(Parameter::new("n"), 1)], 1),
988 );
989
990 let b_expr = BooleanExpression::from(constr);
991
992 let expected_b_expr = BooleanExpression::ComparisonExpression(
993 Box::new(IntegerExpression::Atom(Variable::new("v"))),
994 ComparisonOp::Geq,
995 Box::new(IntegerExpression::Param(Parameter::new("n")) + IntegerExpression::Const(1)),
996 );
997
998 assert_eq!(b_expr, expected_b_expr)
999 }
1000
1001 #[test]
1002 fn test_into_no_div_for_param() {
1003 let param = Parameter::new("n");
1004
1005 assert_eq!(
1006 <Parameter as IntoNoDivBooleanExpr<Parameter>>::get_lcm_of_denominators(¶m),
1007 1
1008 );
1009
1010 let expected_int_expr: IntegerExpression<Parameter> =
1011 IntegerExpression::Const(42) * IntegerExpression::Param(param.clone());
1012 assert_eq!(param.get_scaled_integer_expression(42), expected_int_expr)
1013 }
1014
1015 #[test]
1016 fn test_into_no_div_for_loc() {
1017 let loc = Location::new("n");
1018
1019 assert_eq!(
1020 <Location as IntoNoDivBooleanExpr<Location>>::get_lcm_of_denominators(&loc),
1021 1
1022 );
1023
1024 let expected_int_expr: IntegerExpression<Location> =
1025 IntegerExpression::Const(42) * IntegerExpression::Atom(loc.clone());
1026 assert_eq!(loc.get_scaled_integer_expression(42), expected_int_expr)
1027 }
1028
1029 #[test]
1030 fn test_into_no_div_for_var() {
1031 let var = Variable::new("n");
1032
1033 assert_eq!(
1034 <Variable as IntoNoDivBooleanExpr<Variable>>::get_lcm_of_denominators(&var),
1035 1
1036 );
1037
1038 let expected_int_expr: IntegerExpression<Variable> =
1039 IntegerExpression::Const(42) * IntegerExpression::Atom(var.clone());
1040 assert_eq!(var.get_scaled_integer_expression(42), expected_int_expr)
1041 }
1042}