1use core::fmt;
9use std::{
10 borrow::Borrow,
11 cmp::max,
12 collections::{HashMap, HashSet},
13};
14
15use taco_display_utils::join_iterator;
16use taco_smt_encoder::expression_encoding::{EncodeToSMT, SMTSolverError, SMTVariableContext};
17use taco_threshold_automaton::{
18 VariableConstraint,
19 expressions::{And, BooleanConnective, Location, Or, Parameter, Variable, fraction::Fraction},
20 impl_bitand, impl_bitor,
21 lia_threshold_automaton::{
22 ConstraintRewriteError, LIAVariableConstraint,
23 integer_thresholds::{DeriveFromIntegerComp, ThresholdCompOp, ThresholdConstraint},
24 },
25};
26
27use crate::{TASpecification, internal_spec::Disjunction};
28
29#[derive(Debug, Clone, PartialEq)]
41pub struct UpwardsClosedSet {
42 clauses: Disjunction<UpwardsClosedClause>,
44}
45
46impl UpwardsClosedSet {
47 pub fn new_top() -> Self {
50 Self {
51 clauses: [UpwardsClosedClause::new_top()].into(),
52 }
53 }
54
55 pub fn is_top(&self) -> bool {
58 self.clauses.iter().any(|c| c.is_top())
59 }
60
61 pub fn new_bot() -> Self {
64 Self { clauses: [].into() }
65 }
66
67 pub fn is_bot(&self) -> bool {
69 self.clauses.is_empty()
70 }
71
72 pub fn new_var_constraint(var_constr: LIAVariableConstraint) -> Self {
75 let apc = UpwardsClosedClause::new().with_variable_constraint(var_constr);
76
77 Self::new_from_clause(apc)
78 }
79
80 pub fn new_cover<L: Borrow<Location>, I: IntoIterator<Item = L>>(cover: I) -> Self {
84 let apc = UpwardsClosedClause::new().with_locs_to_cover(cover);
85
86 Self::new_from_clause(apc)
87 }
88
89 pub fn new_cover_int<L: Borrow<Location>, I: IntoIterator<Item = (L, u32)>>(cover: I) -> Self {
92 let apc = UpwardsClosedClause::new().with_lower_bounds(cover);
93
94 Self::new_from_clause(apc)
95 }
96
97 pub fn new_reach<
101 L: Borrow<Location>,
102 I: IntoIterator<Item = (L, u32)>,
103 II: IntoIterator<Item = L>,
104 >(
105 cover: I,
106 empty: II,
107 ) -> Self {
108 let apc = UpwardsClosedClause::new()
109 .with_lower_bounds(cover)
110 .with_locs_to_empty(empty);
111
112 Self::new_from_clause(apc)
113 }
114
115 fn new_from_clause(clause: UpwardsClosedClause) -> Self {
117 Self {
118 clauses: [clause].into(),
119 }
120 }
121
122 pub fn contains_reachability_constraint(&self) -> bool {
128 self.clauses
129 .iter()
130 .any(|a| a.contains_reachability_constraint())
131 }
132
133 pub fn upwards_closed_clauses(&self) -> impl Iterator<Item = &UpwardsClosedClause> {
135 self.clauses.iter()
136 }
137}
138
139impl TASpecification for UpwardsClosedSet {
140 fn locs_appearing(&self) -> impl IntoIterator<Item = &Location> {
141 self.clauses.iter().flat_map(|a| a.locs_appearing())
142 }
143
144 fn var_constraint(&self) -> impl IntoIterator<Item = &LIAVariableConstraint> {
145 self.clauses.iter().flat_map(|a| a.var_constraint())
146 }
147}
148
149impl DeriveFromIntegerComp<Location, UpwardsClosedSetExtractionError> for UpwardsClosedSet {
150 fn form_ordered_constr(
151 scaled_t: HashMap<Location, Fraction>,
152 mut thr_constr: ThresholdConstraint,
153 ) -> Result<Self, UpwardsClosedSetExtractionError> {
154 if scaled_t.len() != 1 {
155 return Err(
156 UpwardsClosedSetExtractionError::LocationConstraintMultiLocation(Box::new((
157 scaled_t, thr_constr,
158 ))),
159 );
160 }
161
162 let (loc, scale) = scaled_t.into_iter().next().unwrap();
163 if scale.is_zero() {
164 unreachable!(
165 "Invalid target constraint constructed containing only parameters (locations are multiplied by zero)."
166 );
167 }
168 thr_constr.scale(scale.inverse());
169
170 if !thr_constr.get_threshold().is_constant() {
171 return Err(
172 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new((
173 loc, thr_constr,
174 ))),
175 );
176 }
177 let mut c = thr_constr.get_threshold().get_const().unwrap();
178
179 match thr_constr.get_op() {
180 ThresholdCompOp::Geq | ThresholdCompOp::Gt => {
181 if ThresholdCompOp::Gt == thr_constr.get_op() && c.is_integer() {
183 c += Fraction::from(1);
184 }
185
186 if c.is_negative() || c.is_zero() {
187 return Ok(UpwardsClosedSet::new_top());
188 }
189
190 let c = c.get_ceil().unwrap();
191 Ok(UpwardsClosedSet::new_from_clause(
192 UpwardsClosedClause::new().with_lower_bounds([(loc, c)]),
193 ))
194 }
195 ThresholdCompOp::Lt | ThresholdCompOp::Leq => {
196 if ThresholdCompOp::Leq == thr_constr.get_op() && c.is_integer() {
198 c += Fraction::from(1);
199 }
200
201 if c.is_zero() || c.is_negative() {
204 return Ok(UpwardsClosedSet::new_bot());
205 }
206
207 if c.get_ceil() == Some(1) {
209 return Ok(UpwardsClosedSet::new_from_clause(
210 UpwardsClosedClause::new().with_locs_to_empty([loc]),
211 ));
212 }
213
214 Err(
215 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
216 (loc, thr_constr),
217 )),
218 )
219 }
220 }
221 }
222}
223
224impl Or for UpwardsClosedSet {
225 fn or(mut self, other: Self) -> Self {
226 if self.is_top() || other.is_top() {
227 return Self::new_top();
228 }
229
230 if self.is_bot() {
231 return other;
232 }
233 if other.is_bot() {
234 return self;
235 }
236
237 self.clauses = self.clauses.or(other.clauses);
238 self
239 }
240}
241impl_bitor!(UpwardsClosedSet);
242
243impl And for UpwardsClosedSet {
244 fn and(self, other: Self) -> Self {
245 if self.is_bot() || other.is_bot() {
246 return UpwardsClosedSet::new_bot();
247 }
248
249 if self.is_top() {
250 return other;
251 }
252 if other.is_top() {
253 return self;
254 }
255
256 let aps = self
257 .clauses
258 .into_iter()
259 .flat_map(|s_apc| {
260 other
261 .clauses
262 .iter()
263 .map(move |o_apc| s_apc.clone() & o_apc.clone())
264 })
265 .collect();
266
267 Self { clauses: aps }
268 }
269}
270impl_bitand!(UpwardsClosedSet);
271
272impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
273 EncodeToSMT<UpwardsClosedSet, C> for UpwardsClosedSet
274{
275 fn encode_to_smt_with_ctx(
276 &self,
277 solver: &taco_smt_encoder::SMTSolver,
278 ctx: &C,
279 ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
280 if self.clauses.is_empty() {
281 return Ok(solver.false_());
282 }
283
284 let disjuncts = self
285 .clauses
286 .iter()
287 .map(|c| c.encode_to_smt_with_ctx(solver, ctx))
288 .collect::<Result<Vec<_>, SMTSolverError>>()?;
289
290 Ok(solver.or_many(disjuncts))
291 }
292}
293
294impl fmt::Display for UpwardsClosedSet {
295 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
296 write!(f, "{}", join_iterator(self.clauses.iter(), " || "))
297 }
298}
299
300#[derive(Debug, Clone, PartialEq)]
303pub enum UpwardsClosedSetExtractionError {
304 LocationConstraintMultiLocation(Box<(HashMap<Location, Fraction>, ThresholdConstraint)>),
306 LIARewriteError(Box<ConstraintRewriteError>),
308 LocationConstraintNotUpwardsClosed(Box<(Location, ThresholdConstraint)>),
311 LocationConstraintWithParameters(Box<(Location, ThresholdConstraint)>),
313 NestedTemporalOperator,
317}
318
319impl From<ConstraintRewriteError> for UpwardsClosedSetExtractionError {
320 fn from(err: ConstraintRewriteError) -> Self {
321 Self::LIARewriteError(Box::new(err))
322 }
323}
324
325impl fmt::Display for UpwardsClosedSetExtractionError {
326 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
327 match self {
328 UpwardsClosedSetExtractionError::LocationConstraintMultiLocation(cstr) => {
329 write!(
330 f,
331 "Multiple locations referenced in constraint: {} {}",
332 join_iterator(cstr.0.iter().map(|(l, f)| format!("{f} * {l}")), "+ "),
333 cstr.1
334 )
335 }
336 UpwardsClosedSetExtractionError::LIARewriteError(err) => {
337 write!(f, "Failed to rewrite to linear arithmetic: {}", err)
338 }
339 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(x) => {
340 write!(
341 f,
342 "Constraint on location '{}' not upwards closed. Constraint: {}{}",
343 x.0, x.0, x.1
344 )
345 }
346 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(x) => write!(
347 f,
348 "Constraint on location '{}' uses parameters. Constraint: {}{}",
349 x.0, x.0, x.1
350 ),
351 UpwardsClosedSetExtractionError::NestedTemporalOperator => write!(
352 f,
353 "Expression contains the other temporal operator, which cannot be transformed into an upwards closed set"
354 ),
355 }
356 }
357}
358
359impl std::error::Error for UpwardsClosedSetExtractionError {}
360
361#[derive(Clone, Debug, PartialEq)]
377pub struct UpwardsClosedClause {
378 locs_no_procs: HashSet<Location>,
380 locs_lower_bounds: HashMap<Location, u32>,
382 variable_constr: LIAVariableConstraint,
384}
385
386impl UpwardsClosedClause {
387 pub fn variable_constr(&self) -> &LIAVariableConstraint {
389 &self.variable_constr
390 }
391
392 pub fn locs_to_cover(&self) -> impl Iterator<Item = (&Location, &u32)> {
395 self.locs_lower_bounds.iter()
396 }
397
398 pub fn locs_to_uncover(&self) -> impl Iterator<Item = &Location> {
400 self.locs_no_procs.iter()
401 }
402
403 pub fn new_top() -> Self {
405 Self::new()
406 }
407
408 pub fn is_top(&self) -> bool {
411 self.locs_lower_bounds.is_empty()
412 && self.locs_no_procs.is_empty()
413 && self.variable_constr == LIAVariableConstraint::True
414 }
415
416 fn new() -> Self {
421 Self {
422 locs_lower_bounds: HashMap::new(),
423 locs_no_procs: HashSet::new(),
424 variable_constr: LIAVariableConstraint::True,
425 }
426 }
427
428 fn with_locs_to_cover<I: Borrow<Location>, L: IntoIterator<Item = I>>(
434 mut self,
435 cover: L,
436 ) -> Self {
437 cover.into_iter().for_each(|l| {
438 if let Some(bound) = self.locs_lower_bounds.get_mut(l.borrow()) {
439 *bound = max(*bound, 1);
440 return;
441 }
442
443 self.locs_lower_bounds.insert(l.borrow().clone(), 1);
444 });
445
446 self
447 }
448
449 fn with_lower_bounds<I: Borrow<Location>, N: Into<u32>, L: IntoIterator<Item = (I, N)>>(
455 mut self,
456 cover: L,
457 ) -> Self {
458 cover.into_iter().for_each(|(l, n)| {
459 if let Some(bound) = self.locs_lower_bounds.get_mut(l.borrow()) {
460 *bound = max(*bound, n.into());
461 return;
462 }
463
464 self.locs_lower_bounds.insert(l.borrow().clone(), n.into());
465 });
466
467 self
468 }
469
470 fn with_locs_to_empty<I: Borrow<Location>, L: IntoIterator<Item = I>>(
472 mut self,
473 locs: L,
474 ) -> Self {
475 locs.into_iter().for_each(|l| {
476 self.locs_no_procs.insert(l.borrow().clone());
477 });
478 self
479 }
480
481 fn with_variable_constraint(mut self, constr: LIAVariableConstraint) -> Self {
486 if constr == LIAVariableConstraint::True {
487 return self;
488 }
489
490 if self.variable_constr == LIAVariableConstraint::True {
491 self.variable_constr = constr;
492 return self;
493 }
494
495 self.variable_constr = LIAVariableConstraint::BinaryGuard(
496 Box::new(self.variable_constr),
497 BooleanConnective::And,
498 Box::new(constr),
499 );
500
501 self
502 }
503
504 fn contains_reachability_constraint(&self) -> bool {
508 !self.locs_no_procs.is_empty()
509 }
510}
511
512impl TASpecification for UpwardsClosedClause {
513 fn locs_appearing(&self) -> impl IntoIterator<Item = &Location> {
514 self.locs_lower_bounds
515 .keys()
516 .chain(self.locs_no_procs.iter())
517 }
518
519 fn var_constraint(&self) -> impl IntoIterator<Item = &LIAVariableConstraint> {
520 [&self.variable_constr]
521 }
522}
523
524impl_bitand!(UpwardsClosedClause);
525
526impl And for UpwardsClosedClause {
527 fn and(self, other: Self) -> Self {
528 self.with_lower_bounds(other.locs_lower_bounds.iter().map(|(l, n)| (l, *n)))
529 .with_locs_to_empty(other.locs_no_procs)
530 .with_variable_constraint(other.variable_constr)
531 }
532}
533
534impl fmt::Display for UpwardsClosedClause {
535 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
536 let lower_bounds = join_iterator(
537 self.locs_lower_bounds
538 .iter()
539 .map(|(loc, n)| format!("({loc} >= {n})"))
540 .chain(self.locs_no_procs.iter().map(|loc| format!("({loc} == 0)"))),
541 " && ",
542 );
543
544 write!(f, "{lower_bounds}")?;
545
546 if self.variable_constr != LIAVariableConstraint::True {
547 if !lower_bounds.is_empty() {
548 write!(f, " && ")?;
549 }
550 write!(f, "{}", self.variable_constr)?;
551 }
552
553 Ok(())
554 }
555}
556
557impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
558 EncodeToSMT<UpwardsClosedClause, C> for UpwardsClosedClause
559{
560 fn encode_to_smt_with_ctx(
561 &self,
562 solver: &taco_smt_encoder::SMTSolver,
563 ctx: &C,
564 ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
565 let lower_bounds = self
567 .locs_lower_bounds
568 .iter()
569 .map(|(loc, n)| {
570 let loc = ctx.get_expr_for(loc)?;
571 let n = solver.numeral(*n);
572
573 Ok(solver.gte(loc, n))
574 })
575 .collect::<Result<Vec<_>, SMTSolverError>>()?;
576
577 let loc_uncover = self
579 .locs_no_procs
580 .iter()
581 .map(|loc| {
582 let loc = ctx.get_expr_for(loc)?;
583 let zero = solver.numeral(0);
584
585 Ok(solver.eq(loc, zero))
586 })
587 .collect::<Result<Vec<_>, SMTSolverError>>()?;
588
589 let var_constr = self
591 .variable_constr
592 .as_boolean_expr()
593 .encode_to_smt_with_ctx(solver, ctx)?;
594
595 let res = solver.and_many(
596 lower_bounds
597 .into_iter()
598 .chain(loc_uncover)
599 .chain([var_constr]),
600 );
601
602 Ok(res)
603 }
604}
605
606#[cfg(test)]
607mod tests {
608 use std::collections::{HashMap, HashSet};
609
610 use taco_smt_encoder::{
611 SMTSolverBuilder, SMTSolverContext,
612 expression_encoding::{EncodeToSMT, SMTVariableContext, StaticSMTContext},
613 };
614 use taco_threshold_automaton::{
615 BooleanVarConstraint,
616 expressions::{ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter, Variable},
617 lia_threshold_automaton::{
618 LIAVariableConstraint, integer_thresholds::DeriveFromIntegerComp,
619 },
620 };
621
622 use crate::{
623 TASpecification,
624 internal_spec::upwards_closed_set::{
625 UpwardsClosedClause, UpwardsClosedSet, UpwardsClosedSetExtractionError,
626 },
627 };
628
629 #[test]
630 fn test_target_config_new_cover() {
631 let cover = UpwardsClosedSet::new_cover([
632 Location::new("l1"),
633 Location::new("l2"),
634 Location::new("l3"),
635 ]);
636
637 let expected = UpwardsClosedSet {
638 clauses: [UpwardsClosedClause {
639 locs_lower_bounds: HashMap::from([
640 (Location::new("l1"), 1),
641 (Location::new("l2"), 1),
642 (Location::new("l3"), 1),
643 ]),
644 locs_no_procs: HashSet::new(),
645 variable_constr: LIAVariableConstraint::True,
646 }]
647 .into(),
648 };
649
650 assert_eq!(cover, expected);
651 }
652
653 #[test]
654 fn test_target_config_new_reach() {
655 let reach = UpwardsClosedSet::new_reach(
656 [
657 (Location::new("l1"), 1),
658 (Location::new("l2"), 1),
659 (Location::new("l3"), 1),
660 ],
661 [
662 Location::new("l4"),
663 Location::new("l5"),
664 Location::new("l6"),
665 ],
666 );
667
668 let expected = UpwardsClosedSet {
669 clauses: [UpwardsClosedClause {
670 locs_lower_bounds: HashMap::from([
671 (Location::new("l1"), 1),
672 (Location::new("l2"), 1),
673 (Location::new("l3"), 1),
674 ]),
675 locs_no_procs: HashSet::from([
676 Location::new("l4"),
677 Location::new("l5"),
678 Location::new("l6"),
679 ]),
680 variable_constr: LIAVariableConstraint::True,
681 }]
682 .into(),
683 };
684
685 assert_eq!(reach, expected);
686 }
687
688 #[test]
690 fn test_disjunct_from_integer() {
691 let got = UpwardsClosedSet::from_integer_expr(
693 IntegerExpression::Atom(Location::new("l")),
694 ComparisonOp::Eq,
695 IntegerExpression::Const(0),
696 );
697 assert!(got.is_ok());
698
699 let expected = UpwardsClosedSet {
700 clauses: [UpwardsClosedClause {
701 locs_lower_bounds: HashMap::new(),
702 locs_no_procs: HashSet::from([Location::new("l")]),
703 variable_constr: LIAVariableConstraint::True,
704 }]
705 .into(),
706 };
707
708 assert_eq!(got.unwrap(), expected);
709
710 let got = UpwardsClosedSet::from_integer_expr(
712 IntegerExpression::Atom(Location::new("l")),
713 ComparisonOp::Neq,
714 IntegerExpression::Const(0),
715 );
716 assert!(got.is_ok());
717
718 let expected = UpwardsClosedSet {
719 clauses: [UpwardsClosedClause {
720 locs_lower_bounds: HashMap::from([(Location::new("l"), 1)]),
721 locs_no_procs: HashSet::new(),
722 variable_constr: LIAVariableConstraint::True,
723 }]
724 .into(),
725 };
726
727 assert_eq!(got.unwrap(), expected);
728
729 let got = UpwardsClosedSet::from_integer_expr(
731 IntegerExpression::Atom(Location::new("l")),
732 ComparisonOp::Gt,
733 IntegerExpression::Const(0),
734 );
735 assert!(got.is_ok());
736
737 let expected = UpwardsClosedSet {
738 clauses: [UpwardsClosedClause {
739 locs_lower_bounds: HashMap::from([(Location::new("l"), 1)]),
740 locs_no_procs: HashSet::new(),
741 variable_constr: LIAVariableConstraint::True,
742 }]
743 .into(),
744 };
745
746 assert_eq!(got.unwrap(), expected);
747
748 let got = UpwardsClosedSet::from_integer_expr(
750 IntegerExpression::Atom(Location::new("l")),
751 ComparisonOp::Gt,
752 IntegerExpression::Const(42),
753 );
754 assert!(got.is_ok());
755
756 let expected = UpwardsClosedSet {
757 clauses: [UpwardsClosedClause {
758 locs_lower_bounds: HashMap::from([(Location::new("l"), 43)]),
759 locs_no_procs: HashSet::new(),
760 variable_constr: LIAVariableConstraint::True,
761 }]
762 .into(),
763 };
764
765 assert_eq!(got.unwrap(), expected);
766
767 let got = UpwardsClosedSet::from_integer_expr(
769 IntegerExpression::Atom(Location::new("l")),
770 ComparisonOp::Geq,
771 IntegerExpression::Const(42),
772 );
773 assert!(got.is_ok());
774
775 let expected = UpwardsClosedSet {
776 clauses: [UpwardsClosedClause {
777 locs_lower_bounds: HashMap::from([(Location::new("l"), 42)]),
778 locs_no_procs: HashSet::new(),
779 variable_constr: LIAVariableConstraint::True,
780 }]
781 .into(),
782 };
783
784 assert_eq!(got.unwrap(), expected);
785
786 let got = UpwardsClosedSet::from_integer_expr(
788 IntegerExpression::Atom(Location::new("l")),
789 ComparisonOp::Gt,
790 IntegerExpression::BinaryExpr(
791 Box::new(IntegerExpression::Const(2)),
792 IntegerOp::Div,
793 Box::new(IntegerExpression::Const(3)),
794 ),
795 );
796 assert!(got.is_ok());
797
798 let expected = UpwardsClosedSet {
799 clauses: [UpwardsClosedClause {
800 locs_lower_bounds: HashMap::from([(Location::new("l"), 1)]),
801 locs_no_procs: HashSet::new(),
802 variable_constr: LIAVariableConstraint::True,
803 }]
804 .into(),
805 };
806
807 assert_eq!(got.unwrap(), expected);
808
809 let got = UpwardsClosedSet::from_integer_expr(
811 IntegerExpression::Atom(Location::new("l")),
812 ComparisonOp::Geq,
813 IntegerExpression::BinaryExpr(
814 Box::new(IntegerExpression::Const(2)),
815 IntegerOp::Div,
816 Box::new(IntegerExpression::Const(3)),
817 ),
818 );
819 assert!(got.is_ok());
820
821 let expected = UpwardsClosedSet {
822 clauses: [UpwardsClosedClause {
823 locs_lower_bounds: HashMap::from([(Location::new("l"), 1)]),
824 locs_no_procs: HashSet::new(),
825 variable_constr: LIAVariableConstraint::True,
826 }]
827 .into(),
828 };
829
830 assert_eq!(got.unwrap(), expected);
831
832 let got = UpwardsClosedSet::from_integer_expr(
834 IntegerExpression::Const(42),
835 ComparisonOp::Lt,
836 IntegerExpression::Atom(Location::new("l")),
837 );
838 assert!(got.is_ok(), "{:#?}", got);
839
840 let expected = UpwardsClosedSet {
841 clauses: [UpwardsClosedClause {
842 locs_lower_bounds: HashMap::from([(Location::new("l"), 43)]),
843 locs_no_procs: HashSet::new(),
844 variable_constr: LIAVariableConstraint::True,
845 }]
846 .into(),
847 };
848
849 assert_eq!(got.unwrap(), expected);
850
851 let got = UpwardsClosedSet::from_integer_expr(
853 IntegerExpression::Atom(Location::new("l")),
854 ComparisonOp::Gt,
855 IntegerExpression::BinaryExpr(
856 Box::new(IntegerExpression::Const(0)),
857 IntegerOp::Add,
858 Box::new(IntegerExpression::BinaryExpr(
859 Box::new(IntegerExpression::Const(1)),
860 IntegerOp::Add,
861 Box::new(IntegerExpression::Const(2)),
862 )),
863 ),
864 );
865 assert!(got.is_ok());
866
867 let expected = UpwardsClosedSet {
868 clauses: [UpwardsClosedClause {
869 locs_lower_bounds: HashMap::from([(Location::new("l"), 4)]),
870 locs_no_procs: HashSet::new(),
871 variable_constr: LIAVariableConstraint::True,
872 }]
873 .into(),
874 };
875
876 assert_eq!(got.unwrap(), expected);
877
878 let got = UpwardsClosedSet::from_integer_expr(
880 IntegerExpression::Atom(Location::new("l")),
881 ComparisonOp::Lt,
882 IntegerExpression::Const(1),
883 );
884 assert!(got.is_ok());
885
886 let expected = UpwardsClosedSet {
887 clauses: [UpwardsClosedClause {
888 locs_lower_bounds: HashMap::new(),
889 locs_no_procs: HashSet::from([Location::new("l")]),
890 variable_constr: LIAVariableConstraint::True,
891 }]
892 .into(),
893 };
894
895 assert_eq!(got.unwrap(), expected);
896
897 let got = UpwardsClosedSet::from_integer_expr(
899 IntegerExpression::Atom(Location::new("l")),
900 ComparisonOp::Leq,
901 IntegerExpression::Const(0),
902 );
903 assert!(got.is_ok());
904
905 let expected = UpwardsClosedSet {
906 clauses: [UpwardsClosedClause {
907 locs_lower_bounds: HashMap::new(),
908 locs_no_procs: HashSet::from([Location::new("l")]),
909 variable_constr: LIAVariableConstraint::True,
910 }]
911 .into(),
912 };
913
914 assert_eq!(got.unwrap(), expected);
915 }
916
917 #[test]
919 fn test_target_config_try_loc_constr_error_cases() {
920 let got = UpwardsClosedSet::from_integer_expr(
922 IntegerExpression::BinaryExpr(
923 Box::new(IntegerExpression::Atom(Location::new("l1"))),
924 IntegerOp::Add,
925 Box::new(IntegerExpression::Atom(Location::new("l2"))),
926 ),
927 ComparisonOp::Eq,
928 IntegerExpression::Const(0),
929 );
930 assert!(got.is_err(), "{:#?}", got);
931
932 assert!(matches!(
933 got.unwrap_err(),
934 UpwardsClosedSetExtractionError::LocationConstraintMultiLocation(_)
935 ));
936
937 let got = UpwardsClosedSet::from_integer_expr(
939 IntegerExpression::BinaryExpr(
940 Box::new(IntegerExpression::Atom(Location::new("l"))),
941 IntegerOp::Mul,
942 Box::new(IntegerExpression::Atom(Location::new("l"))),
943 ),
944 ComparisonOp::Eq,
945 IntegerExpression::Const(0),
946 );
947 assert!(got.is_err(), "{:#?}", got);
948
949 assert!(matches!(
950 got.unwrap_err(),
951 UpwardsClosedSetExtractionError::LIARewriteError(_)
952 ));
953
954 let got = UpwardsClosedSet::from_integer_expr(
956 IntegerExpression::BinaryExpr(
957 Box::new(IntegerExpression::Atom(Location::new("l"))),
958 IntegerOp::Add,
959 Box::new(IntegerExpression::Param(Parameter::new("n"))),
960 ),
961 ComparisonOp::Eq,
962 IntegerExpression::Const(0),
963 );
964 assert!(got.is_err(), "{:#?}", got);
965
966 assert!(matches!(
967 got.unwrap_err(),
968 UpwardsClosedSetExtractionError::LocationConstraintWithParameters(_)
969 ));
970
971 let got = UpwardsClosedSet::from_integer_expr(
973 IntegerExpression::Atom(Location::new("l")),
974 ComparisonOp::Lt,
975 IntegerExpression::Const(3),
976 );
977 assert!(got.is_err(), "{:#?}", got);
978
979 assert!(matches!(
980 got.unwrap_err(),
981 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_)
982 ));
983
984 let got = UpwardsClosedSet::from_integer_expr(
986 IntegerExpression::Atom(Location::new("l")),
987 ComparisonOp::Leq,
988 IntegerExpression::Const(3),
989 );
990 assert!(got.is_err(), "{:#?}", got);
991
992 assert!(matches!(
993 got.unwrap_err(),
994 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_)
995 ));
996
997 let got = UpwardsClosedSet::from_integer_expr(
999 IntegerExpression::Atom(Location::new("l")),
1000 ComparisonOp::Eq,
1001 IntegerExpression::Const(3),
1002 );
1003 assert!(got.is_err(), "{:#?}", got);
1004
1005 assert!(matches!(
1006 got.unwrap_err(),
1007 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_)
1008 ));
1009
1010 let got = UpwardsClosedSet::from_integer_expr(
1012 IntegerExpression::Atom(Location::new("l")),
1013 ComparisonOp::Neq,
1014 IntegerExpression::Const(3),
1015 );
1016 assert!(got.is_err(), "{:#?}", got);
1017
1018 assert!(matches!(
1019 got.unwrap_err(),
1020 UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_)
1021 ));
1022 }
1023
1024 #[test]
1025 fn test_target_config_encode_target_config_unconstrained() {
1026 let solver_builder = SMTSolverBuilder::default();
1027
1028 let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1029
1030 let config = UpwardsClosedSet::new_top();
1031
1032 let got_expr = config
1033 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1034 .unwrap();
1035
1036 let expected_expr = ctx.get_true();
1037 assert_eq!(got_expr, expected_expr)
1038 }
1039
1040 #[test]
1041 fn test_target_config_encode_target_config_single_loc_cov() {
1042 let solver_builder = SMTSolverBuilder::default();
1043
1044 let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1045
1046 let config = UpwardsClosedSet::new_cover([Location::new("l")]);
1047
1048 let got_expr = config
1049 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1050 .unwrap();
1051
1052 let solver = ctx.get_smt_solver();
1053
1054 let expected_expr = solver.and(
1055 solver.gte(
1056 ctx.get_expr_for(&Location::new("l")).unwrap(),
1057 solver.numeral(1),
1058 ),
1059 solver.true_(),
1060 );
1061
1062 assert_eq!(
1063 got_expr,
1064 expected_expr,
1065 "got: {}, expected: {}",
1066 solver.display(got_expr),
1067 solver.display(expected_expr)
1068 )
1069 }
1070
1071 #[test]
1072 fn test_target_config_encode_target_config_single_loc_uncover() {
1073 let solver_builder = SMTSolverBuilder::default();
1074
1075 let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1076
1077 let config = UpwardsClosedSet::new_reach([], [Location::new("l")]);
1078
1079 let got_expr = config
1080 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1081 .unwrap();
1082
1083 let solver = ctx.get_smt_solver();
1084
1085 let expected_expr = solver.and(
1086 solver.eq(
1087 ctx.get_expr_for(&Location::new("l")).unwrap(),
1088 solver.numeral(0),
1089 ),
1090 solver.true_(),
1091 );
1092
1093 assert_eq!(
1094 got_expr,
1095 expected_expr,
1096 "got: {}, expected: {}",
1097 solver.display(got_expr),
1098 solver.display(expected_expr)
1099 )
1100 }
1101
1102 #[test]
1103 fn test_target_config_encode_target_config_var_constr() {
1104 let solver_builder = SMTSolverBuilder::default();
1105
1106 let ctx = StaticSMTContext::new(
1107 solver_builder,
1108 [],
1109 [Location::new("l")],
1110 [Variable::new("v")],
1111 )
1112 .unwrap();
1113
1114 let config = UpwardsClosedSet::new_var_constraint(
1115 BooleanVarConstraint::ComparisonExpression(
1116 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1117 ComparisonOp::Eq,
1118 Box::new(IntegerExpression::Const(1)),
1119 )
1120 .try_into()
1121 .unwrap(),
1122 );
1123
1124 let got_expr = config
1125 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1126 .unwrap();
1127
1128 let solver = ctx.get_smt_solver();
1129
1130 let expected_expr = solver.and(
1131 solver.lte(
1132 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1133 solver.numeral(1),
1134 ),
1135 solver.gte(
1136 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1137 solver.numeral(1),
1138 ),
1139 );
1140
1141 assert_eq!(
1142 got_expr,
1143 expected_expr,
1144 "got: {}, expected: {}",
1145 solver.display(got_expr),
1146 solver.display(expected_expr)
1147 )
1148 }
1149
1150 #[test]
1151 fn test_target_config_encode_target_config_all_constr() {
1152 let solver_builder = SMTSolverBuilder::default();
1153
1154 let ctx = StaticSMTContext::new(
1155 solver_builder,
1156 [],
1157 [Location::new("l1"), Location::new("l2")],
1158 [Variable::new("v")],
1159 )
1160 .unwrap();
1161
1162 let config =
1163 UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [Location::new("l2")])
1164 & UpwardsClosedSet::new_var_constraint(
1165 BooleanVarConstraint::ComparisonExpression(
1166 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1167 ComparisonOp::Eq,
1168 Box::new(IntegerExpression::Const(1)),
1169 )
1170 .try_into()
1171 .unwrap(),
1172 );
1173 let got_expr = config
1174 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1175 .unwrap();
1176
1177 let solver = ctx.get_smt_solver();
1178
1179 let expected_expr = solver.and_many([
1180 solver.gte(
1181 ctx.get_expr_for(&Location::new("l1")).unwrap(),
1182 solver.numeral(42),
1183 ),
1184 solver.eq(
1185 ctx.get_expr_for(&Location::new("l2")).unwrap(),
1186 solver.numeral(0),
1187 ),
1188 solver.and(
1189 solver.lte(
1190 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1191 solver.numeral(1),
1192 ),
1193 solver.gte(
1194 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1195 solver.numeral(1),
1196 ),
1197 ),
1198 ]);
1199
1200 assert_eq!(
1201 got_expr,
1202 expected_expr,
1203 "got: {}, expected: {}",
1204 solver.display(got_expr),
1205 solver.display(expected_expr)
1206 )
1207 }
1208
1209 #[test]
1210 fn test_target_config_locations_appearing() {
1211 let config =
1212 UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [Location::new("l2")])
1213 & UpwardsClosedSet::new_var_constraint(
1214 BooleanVarConstraint::ComparisonExpression(
1215 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1216 ComparisonOp::Eq,
1217 Box::new(IntegerExpression::Const(1)),
1218 )
1219 .try_into()
1220 .unwrap(),
1221 );
1222
1223 let got_expr: HashSet<Location> = config.locs_appearing().into_iter().cloned().collect();
1224
1225 let expected_locs = HashSet::from([Location::new("l1"), Location::new("l2")]);
1226
1227 assert_eq!(got_expr, expected_locs)
1228 }
1229
1230 #[test]
1231 fn test_target_config_is_reachability_constr() {
1232 let config =
1233 UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [Location::new("l2")])
1234 & UpwardsClosedSet::new_var_constraint(
1235 BooleanVarConstraint::ComparisonExpression(
1236 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1237 ComparisonOp::Eq,
1238 Box::new(IntegerExpression::Const(1)),
1239 )
1240 .try_into()
1241 .unwrap(),
1242 );
1243
1244 assert!(config.contains_reachability_constraint());
1245
1246 let config = UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [])
1247 & UpwardsClosedSet::new_var_constraint(
1248 BooleanVarConstraint::ComparisonExpression(
1249 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1250 ComparisonOp::Eq,
1251 Box::new(IntegerExpression::Const(1)),
1252 )
1253 .try_into()
1254 .unwrap(),
1255 );
1256
1257 assert!(!config.contains_reachability_constraint());
1258 }
1259
1260 #[test]
1261 fn test_disjunction_target_config_get_locations_in_target() {
1262 let config1 =
1263 UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [Location::new("l2")])
1264 & UpwardsClosedSet::new_var_constraint(
1265 BooleanVarConstraint::ComparisonExpression(
1266 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1267 ComparisonOp::Eq,
1268 Box::new(IntegerExpression::Const(1)),
1269 )
1270 .try_into()
1271 .unwrap(),
1272 );
1273
1274 let config2 =
1275 UpwardsClosedSet::new_reach([(Location::new("l3"), 42)], [Location::new("l4")])
1276 & UpwardsClosedSet::new_var_constraint(
1277 BooleanVarConstraint::ComparisonExpression(
1278 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1279 ComparisonOp::Eq,
1280 Box::new(IntegerExpression::Const(1)),
1281 )
1282 .try_into()
1283 .unwrap(),
1284 );
1285
1286 let dis = config1 | config2;
1287
1288 let got_set_locations: HashSet<Location> =
1289 dis.locs_appearing().into_iter().cloned().collect();
1290
1291 let expected_set_locations = HashSet::from([
1292 Location::new("l1"),
1293 Location::new("l2"),
1294 Location::new("l3"),
1295 Location::new("l4"),
1296 ]);
1297
1298 assert_eq!(got_set_locations, expected_set_locations);
1299
1300 let dis = UpwardsClosedSet::new_bot();
1301
1302 let got_set_locations: HashSet<_> = dis.locs_appearing().into_iter().collect();
1303
1304 let expected_set_locations = HashSet::from([]);
1305
1306 assert_eq!(got_set_locations, expected_set_locations);
1307 }
1308
1309 #[test]
1310 fn test_disj_target_config_contains_reachability_constraint() {
1311 let config1 = UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [])
1312 & UpwardsClosedSet::new_var_constraint(
1313 BooleanVarConstraint::ComparisonExpression(
1314 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1315 ComparisonOp::Eq,
1316 Box::new(IntegerExpression::Const(1)),
1317 )
1318 .try_into()
1319 .unwrap(),
1320 );
1321
1322 let config2 =
1323 UpwardsClosedSet::new_reach([(Location::new("l3"), 42)], [Location::new("l4")])
1324 & UpwardsClosedSet::new_var_constraint(
1325 BooleanVarConstraint::ComparisonExpression(
1326 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1327 ComparisonOp::Eq,
1328 Box::new(IntegerExpression::Const(1)),
1329 )
1330 .try_into()
1331 .unwrap(),
1332 );
1333
1334 let dis = config1 | config2;
1335
1336 assert!(dis.contains_reachability_constraint());
1337
1338 let disj = UpwardsClosedSet::new_bot();
1339
1340 assert!(!disj.contains_reachability_constraint())
1341 }
1342
1343 #[test]
1344 fn test_disj_target_config_encode_to_smt_empty() {
1345 let solver_builder = SMTSolverBuilder::default();
1346
1347 let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1348
1349 let solver = ctx.get_smt_solver();
1350
1351 let disj = UpwardsClosedSet::new_bot();
1352
1353 let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1354
1355 let expected_expr = solver.false_();
1356
1357 assert_eq!(
1358 got_expr,
1359 expected_expr,
1360 "got: {}, expected: {}",
1361 solver.display(got_expr),
1362 solver.display(expected_expr)
1363 )
1364 }
1365
1366 #[test]
1367 fn test_disj_target_config_encode_smt() {
1368 let solver_builder = SMTSolverBuilder::default();
1369
1370 let ctx = StaticSMTContext::new(
1371 solver_builder,
1372 [],
1373 [
1374 Location::new("l1"),
1375 Location::new("l2"),
1376 Location::new("l3"),
1377 Location::new("l4"),
1378 ],
1379 [Variable::new("v")],
1380 )
1381 .unwrap();
1382
1383 let solver = ctx.get_smt_solver();
1384
1385 let config1 =
1386 UpwardsClosedSet::new_reach([(Location::new("l1"), 42)], [Location::new("l2")])
1387 & UpwardsClosedSet::new_var_constraint(
1388 BooleanVarConstraint::ComparisonExpression(
1389 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1390 ComparisonOp::Eq,
1391 Box::new(IntegerExpression::Const(1)),
1392 )
1393 .try_into()
1394 .unwrap(),
1395 );
1396
1397 let config2 =
1398 UpwardsClosedSet::new_reach([(Location::new("l3"), 42)], [Location::new("l4")])
1399 & UpwardsClosedSet::new_var_constraint(
1400 BooleanVarConstraint::ComparisonExpression(
1401 Box::new(IntegerExpression::Atom(Variable::new("v"))),
1402 ComparisonOp::Eq,
1403 Box::new(IntegerExpression::Const(1)),
1404 )
1405 .try_into()
1406 .unwrap(),
1407 );
1408
1409 let disj = config1 | config2;
1410
1411 let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1412
1413 let expected_expr = solver.or_many([
1414 solver.and_many([
1415 solver.gte(
1416 ctx.get_expr_for(&Location::new("l1")).unwrap(),
1417 solver.numeral(42),
1418 ),
1419 solver.eq(
1420 ctx.get_expr_for(&Location::new("l2")).unwrap(),
1421 solver.numeral(0),
1422 ),
1423 solver.and(
1424 solver.lte(
1425 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1426 solver.numeral(1),
1427 ),
1428 solver.gte(
1429 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1430 solver.numeral(1),
1431 ),
1432 ),
1433 ]),
1434 solver.and_many([
1435 solver.gte(
1436 ctx.get_expr_for(&Location::new("l3")).unwrap(),
1437 solver.numeral(42),
1438 ),
1439 solver.eq(
1440 ctx.get_expr_for(&Location::new("l4")).unwrap(),
1441 solver.numeral(0),
1442 ),
1443 solver.and(
1444 solver.lte(
1445 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1446 solver.numeral(1),
1447 ),
1448 solver.gte(
1449 ctx.get_expr_for(&Variable::new("v")).unwrap(),
1450 solver.numeral(1),
1451 ),
1452 ),
1453 ]),
1454 ]);
1455
1456 assert_eq!(
1457 got_expr,
1458 expected_expr,
1459 "got: {}, expected: {}",
1460 solver.display(got_expr),
1461 solver.display(expected_expr)
1462 )
1463 }
1464
1465 #[test]
1466 fn test_or_with_top_and_bot() {
1467 let cover = UpwardsClosedSet::new_cover([Location::new("l1")]);
1468
1469 assert_eq!(
1471 UpwardsClosedSet::new_top() | cover.clone(),
1472 UpwardsClosedSet::new_top()
1473 );
1474 assert_eq!(
1475 cover.clone() | UpwardsClosedSet::new_top(),
1476 UpwardsClosedSet::new_top()
1477 );
1478
1479 assert_eq!(UpwardsClosedSet::new_bot() | cover.clone(), cover);
1481 assert_eq!(cover.clone() | UpwardsClosedSet::new_bot(), cover);
1482 }
1483
1484 #[test]
1485 fn test_and_with_top_and_bot() {
1486 let cover = UpwardsClosedSet::new_cover([Location::new("l1")]);
1487
1488 assert_eq!(
1490 UpwardsClosedSet::new_bot() & cover.clone(),
1491 UpwardsClosedSet::new_bot()
1492 );
1493 assert_eq!(
1494 cover.clone() & UpwardsClosedSet::new_bot(),
1495 UpwardsClosedSet::new_bot()
1496 );
1497
1498 assert_eq!(UpwardsClosedSet::new_top() & cover.clone(), cover);
1500 assert_eq!(cover.clone() & UpwardsClosedSet::new_top(), cover);
1501 }
1502
1503 #[test]
1504 fn test_from_integer_expr_rounding() {
1505 let l = Location::new("l");
1506 let mk = |k: u32, op: ComparisonOp, c: u32| {
1508 UpwardsClosedSet::from_integer_expr(
1509 IntegerExpression::Const(k) * IntegerExpression::Atom(l.clone()),
1510 op,
1511 IntegerExpression::Const(c),
1512 )
1513 };
1514 let empty = UpwardsClosedSet::new_from_clause(
1515 UpwardsClosedClause::new().with_locs_to_empty([l.clone()]),
1516 );
1517
1518 assert_eq!(mk(2, ComparisonOp::Lt, 1), Ok(empty.clone()));
1520 assert_eq!(mk(2, ComparisonOp::Leq, 1), Ok(empty.clone()));
1521 assert_eq!(mk(2, ComparisonOp::Lt, 2), Ok(empty.clone()));
1522 assert_eq!(mk(1, ComparisonOp::Leq, 0), Ok(empty.clone()));
1523
1524 assert!(matches!(
1526 mk(2, ComparisonOp::Lt, 3),
1527 Err(UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_))
1528 ));
1529 assert!(matches!(
1531 mk(2, ComparisonOp::Leq, 2),
1532 Err(UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(_))
1533 ));
1534
1535 assert_eq!(mk(1, ComparisonOp::Lt, 0), Ok(UpwardsClosedSet::new_bot()));
1537
1538 let geq2 = UpwardsClosedSet::new_cover_int([(l.clone(), 2)]);
1540 assert_eq!(mk(2, ComparisonOp::Gt, 3), Ok(geq2.clone()));
1541 assert_eq!(mk(2, ComparisonOp::Geq, 3), Ok(geq2.clone()));
1542 assert_eq!(mk(1, ComparisonOp::Gt, 1), Ok(geq2));
1544 assert_eq!(mk(1, ComparisonOp::Geq, 0), Ok(UpwardsClosedSet::new_top()));
1546 }
1547}