1use log::{debug, info, trace};
24
25#[cfg(feature = "config_deserialize")]
26use serde::Deserialize;
27
28use std::{
29 collections::{BTreeSet, HashMap, HashSet},
30 fmt::{self},
31};
32
33use taco_smt_encoder::{
34 ProvidesSMTSolverBuilder, SMTSolverBuilder, SMTSolverContext,
35 expression_encoding::{
36 EncodeToSMT, SMTVariableContext, StaticSMTContext,
37 ta_encoding::{encode_initial_constraints, encode_resilience_condition},
38 },
39};
40use taco_threshold_automaton::{
41 ActionDefinition, ModifiableThresholdAutomaton, RuleDefinition, ThresholdAutomaton,
42 VariableConstraint,
43 expressions::{
44 Atomic, BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, Location,
45 Variable,
46 },
47 general_threshold_automaton::{GeneralThresholdAutomaton, Rule},
48};
49
50use crate::{ModelCheckerContext, TASpecification};
51
52pub trait Preprocessor<
57 T: ModifiableThresholdAutomaton + ThresholdAutomaton,
58 S: TASpecification,
59 C: ModelCheckerContext,
60>
61{
62 fn process(&self, ta: &mut T, spec: &S, ctx: &C);
64}
65
66pub struct DropSelfLoops {}
76
77impl Default for DropSelfLoops {
78 fn default() -> Self {
79 Self::new()
80 }
81}
82
83impl DropSelfLoops {
84 pub fn new() -> Self {
86 Self {}
87 }
88}
89
90impl<T: ModifiableThresholdAutomaton, S: TASpecification, C: ModelCheckerContext>
91 Preprocessor<T, S, C> for DropSelfLoops
92{
93 fn process(&self, ta: &mut T, _spec: &S, _ctx: &C) {
94 let n_rules = ta.rules().count();
95
96 ta.retain_rules(|rule| {
97 rule.source() != rule.target() || rule.actions().any(|action| !action.is_unchanged())
98 });
99
100 info!(
101 "Preprocessor 'DropSelfLoops' on '{}' removed {} of {} rules: Removed rules are self-loops that do not update any shared variable.",
102 ta.name(),
103 n_rules - ta.rules().count(),
104 n_rules
105 );
106 }
107}
108
109#[derive(Default)]
123pub struct DropUnreachableLocations {}
124
125impl DropUnreachableLocations {
126 pub fn new() -> Self {
128 Self {}
129 }
130
131 fn compute_unreachable_locations<T: ThresholdAutomaton>(
133 &self,
134 ta: &T,
135 locs_to_keep: HashSet<&Location>,
136 ) -> HashSet<Location> {
137 let mut reached = ta
138 .locations()
139 .filter(|loc| ta.can_be_initial_location(loc))
140 .collect::<HashSet<_>>();
141
142 let mut found_new_loc = true;
143 while found_new_loc {
144 found_new_loc = false;
145
146 for rule in ta.rules() {
147 if reached.contains(&rule.source()) && !reached.contains(&rule.target()) {
148 reached.insert(rule.target());
149 found_new_loc = true;
150 }
151 }
152 }
153
154 ta.locations()
155 .filter(|loc| !reached.contains(loc) && !locs_to_keep.contains(loc))
156 .filter(|loc| {
157 !ta.initial_location_constraints().any(|ic| {
158 ic.contains_atom_a(loc) && !ic.try_check_expr_constraints_to_zero(loc)
159 })
160 })
161 .cloned()
162 .collect::<HashSet<_>>()
163 }
164}
165
166impl<S: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
167 for DropUnreachableLocations
168{
169 fn process(&self, ta: &mut GeneralThresholdAutomaton, spec: &S, _ctx: &C) {
178 let locations_to_keep = spec.locs_appearing().into_iter().collect();
179
180 let unreachable_locations = self.compute_unreachable_locations(ta, locations_to_keep);
181 if unreachable_locations.is_empty() {
182 debug!("Preprocessor 'DropUnreachableLocations' did not find locations to remove.");
183 return;
184 }
185
186 let n_locations = ta.locations().count();
187 let n_rules = ta.rules().count();
188
189 for loc in unreachable_locations {
190 ta.remove_location_and_replace_with(&loc, IntegerExpression::Const(0));
191 }
192
193 info!(
194 "Preprocessor 'DropUnreachableLocations' on '{}' removed {} of {} locations and {} of {} rules: Removed locations were unreachable in the underlying graph of the threshold automaton.",
195 ta.name(),
196 n_locations - ta.locations().count(),
197 n_locations,
198 n_rules - ta.rules().count(),
199 n_rules
200 );
201 }
202}
203
204pub struct ReplaceTrivialGuardsStatic {}
214
215impl Default for ReplaceTrivialGuardsStatic {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl ReplaceTrivialGuardsStatic {
222 fn is_trivially_true<T: Atomic>(expr: &BooleanExpression<T>) -> bool {
224 match expr {
225 BooleanExpression::BinaryExpression(lhs, BooleanConnective::Or, rhs) => {
227 match (lhs.as_ref(), rhs.as_ref()) {
228 (
229 BooleanExpression::ComparisonExpression(expr_r, ComparisonOp::Eq, c_r),
230 BooleanExpression::ComparisonExpression(expr_l, ComparisonOp::Geq, c_l),
231 )
232 | (
233 BooleanExpression::ComparisonExpression(expr_l, ComparisonOp::Geq, c_l),
234 BooleanExpression::ComparisonExpression(expr_r, ComparisonOp::Eq, c_r),
235 ) => {
236 if c_l.as_ref().try_to_evaluate_to_const() == Option::Some(1)
237 && c_r.as_ref().try_to_evaluate_to_const() == Option::Some(0)
238 {
239 return expr_l == expr_r;
240 }
241 false
242 }
243 _ => false,
244 }
245 }
246 BooleanExpression::ComparisonExpression(_, ComparisonOp::Geq, rhs)
248 | BooleanExpression::ComparisonExpression(rhs, ComparisonOp::Leq, _) => {
249 if rhs.try_to_evaluate_to_const() == Option::Some(0) {
250 return true;
251 }
252 false
253 }
254 _ => false,
255 }
256 }
257
258 fn replace_trivial_boolean_expr<T: Atomic>(expr: &mut BooleanExpression<T>) {
263 if Self::is_trivially_true(expr) {
264 *expr = BooleanExpression::True;
265 return;
266 }
267
268 match expr {
269 BooleanExpression::ComparisonExpression(_, _, _) => (),
270 BooleanExpression::BinaryExpression(lhs, _, rhs) => {
271 Self::replace_trivial_boolean_expr(lhs.as_mut());
272 Self::replace_trivial_boolean_expr(rhs.as_mut());
273 }
274 BooleanExpression::Not(expr) => Self::replace_trivial_boolean_expr(expr.as_mut()),
275 BooleanExpression::True => (),
276 BooleanExpression::False => (),
277 }
278 }
279
280 pub fn new() -> Self {
282 Self {}
283 }
284}
285
286impl<S: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
287 for ReplaceTrivialGuardsStatic
288{
289 fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, _ctx: &C) {
299 ta.transform_rules(|rule| {
300 rule.transform_guard(Self::replace_trivial_boolean_expr);
301 });
302 }
303}
304
305pub struct ReplaceTrivialGuardsSMT {}
320
321impl Default for ReplaceTrivialGuardsSMT {
322 fn default() -> Self {
323 Self::new()
324 }
325}
326
327impl ReplaceTrivialGuardsSMT {
328 pub fn new() -> Self {
330 Self {}
331 }
332
333 fn is_trivially_true(expr: &BooleanExpression<Variable>, ctx: &mut StaticSMTContext) -> bool {
337 ctx.get_smt_solver_mut()
338 .push()
339 .expect("Failed to push SMT frame in preprocessor");
340
341 let s_expr = (!expr.clone())
342 .encode_to_smt_with_ctx(ctx.get_smt_solver(), ctx)
343 .expect("Failed to encode rule guard in preprocessor");
344
345 let res = ctx
346 .assert_and_check_expr(s_expr)
347 .expect("Failed to check SMT query in preprocessor");
348
349 ctx.get_smt_solver_mut()
350 .pop()
351 .expect("Failed to pop SMT frame in preprocessor");
352
353 !res.is_sat()
354 }
355
356 fn is_trivially_true_with_cache(
359 expr: &BooleanExpression<Variable>,
360 ctx: &mut StaticSMTContext,
361 cached_checks: &mut HashMap<BooleanExpression<Variable>, bool>,
362 ) -> bool {
363 if cached_checks.contains_key(expr) {
364 return cached_checks[expr];
365 }
366
367 let res = Self::is_trivially_true(expr, ctx);
368 cached_checks.insert(expr.clone(), res);
369 res
370 }
371
372 fn check_expr_and_replace_tautologies(
381 expr: &mut BooleanExpression<Variable>,
382 ctx: &mut StaticSMTContext,
383 cached_checks: &mut HashMap<BooleanExpression<Variable>, bool>,
384 ) {
385 if expr == &BooleanExpression::True || expr == &BooleanExpression::False {
386 return;
387 }
388
389 if Self::is_trivially_true_with_cache(expr, ctx, cached_checks) {
390 trace!("Replaced guard \"{expr}\" with true");
391 *expr = BooleanExpression::True;
392 return;
393 }
394
395 match expr {
397 BooleanExpression::BinaryExpression(lhs, _, rhs) => {
398 Self::check_expr_and_replace_tautologies(lhs.as_mut(), ctx, cached_checks);
399 Self::check_expr_and_replace_tautologies(rhs.as_mut(), ctx, cached_checks);
400 }
401 BooleanExpression::Not(expr) => {
402 Self::check_expr_and_replace_tautologies(expr.as_mut(), ctx, cached_checks)
403 }
404 BooleanExpression::ComparisonExpression(_, _, _)
405 | BooleanExpression::True
406 | BooleanExpression::False => (),
407 }
408 }
409}
410
411impl<S: TASpecification, C: ModelCheckerContext + ProvidesSMTSolverBuilder>
412 Preprocessor<GeneralThresholdAutomaton, S, C> for ReplaceTrivialGuardsSMT
413{
414 fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, ctx: &C) {
415 let solver_builder = ctx.get_solver_builder().clone();
416
417 let mut ctx = StaticSMTContext::new(
418 solver_builder,
419 ta.parameters().cloned(),
420 Vec::new(),
421 ta.variables().cloned(),
422 )
423 .expect("Failed to create SMT context for preprocessing");
424
425 let rc = encode_resilience_condition(ta, ctx.get_smt_solver(), &ctx);
426 ctx.get_smt_solver_mut()
427 .assert(rc)
428 .expect("Failed to assert resilience condition");
429
430 ta.variables().for_each(|var| {
431 let solver = ctx.get_smt_solver();
432 let gt_0 = solver.gte(ctx.get_expr_for(var).unwrap(), solver.numeral(0));
433
434 ctx.get_smt_solver_mut()
435 .assert(gt_0)
436 .expect("Failed to assert gt_0");
437 });
438
439 let mut cache = HashMap::new();
440
441 ta.transform_rules(|rule| {
442 rule.transform_guard(|expr| {
443 Self::check_expr_and_replace_tautologies(expr, &mut ctx, &mut cache);
444 });
445 });
446
447 if !cache.is_empty() {
448 info!(
449 "Preprocessor 'ReplaceTrivialGuardsSMT' on '{}' replaced {} trivial guards",
450 ta.name(),
451 cache.iter().filter(|(_, trivial)| **trivial).count(),
452 );
453 }
454 }
455}
456
457pub struct RemoveUnusedVariables {}
459
460impl Default for RemoveUnusedVariables {
461 fn default() -> Self {
462 Self::new()
463 }
464}
465
466impl RemoveUnusedVariables {
467 pub fn new() -> Self {
469 Self {}
470 }
471
472 fn get_unused_variables(ta: &GeneralThresholdAutomaton) -> impl Iterator<Item = &Variable> {
473 ta.variables()
474 .filter(|var| {
475 !ta.rules()
476 .map(|r| r.guard())
477 .any(|g| g.contains_atom_a(var))
478 })
479 .filter(|var| {
480 !ta.initial_variable_conditions().any(|ic| {
481 ic.contains_atom_a(var) && !ic.try_check_expr_constraints_to_zero(var)
482 })
483 })
484 }
485
486 fn remove_actions_using_variables(r: &mut Rule, vars: &[Variable]) {
487 r.retain_actions(|a| !vars.contains(a.variable()));
488 }
489}
490impl<S: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
491 for RemoveUnusedVariables
492{
493 fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, _ctx: &C) {
494 let unused_variables = Self::get_unused_variables(ta).cloned().collect::<Vec<_>>();
495
496 if unused_variables.is_empty() {
497 return;
498 }
499
500 ta.transform_rules(|rule| {
501 Self::remove_actions_using_variables(rule, &unused_variables);
502 });
503
504 unused_variables.iter().for_each(|var| {
505 ta.remove_variable_and_replace_with(var, IntegerExpression::Const(0));
506 });
507
508 info!(
509 "Preprocessor 'RemoveUnusedVariables' on '{}' removed {} unused variables",
510 ta.name(),
511 unused_variables.len(),
512 );
513 }
514}
515
516pub struct CheckInitCondSatSMT {}
520
521impl Default for CheckInitCondSatSMT {
522 fn default() -> Self {
523 Self::new()
524 }
525}
526
527impl CheckInitCondSatSMT {
528 pub fn new() -> Self {
530 Self {}
531 }
532}
533
534impl<S: TASpecification, C: ModelCheckerContext + ProvidesSMTSolverBuilder>
535 Preprocessor<GeneralThresholdAutomaton, S, C> for CheckInitCondSatSMT
536{
537 fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, ctx: &C) {
538 let solver_builder = ctx.get_solver_builder();
539
540 let mut ctx = StaticSMTContext::new(
541 solver_builder,
542 ta.parameters().cloned(),
543 ta.locations().cloned(),
544 ta.variables().cloned(),
545 )
546 .expect("Failed to create SMT context for preprocessing");
547
548 let init_cond = encode_initial_constraints(ta, ctx.get_smt_solver(), &ctx);
549
550 let init_cond_is_satisfiable = ctx
551 .assert_and_check_expr(init_cond)
552 .expect("Failed to encode initial conditions during preprocessing");
553
554 if !init_cond_is_satisfiable.is_sat() {
555 info!("Preprocessor 'CheckInitCondSatSMT' found unsatisfiable case.");
556 ta.retain_rules(|_| false);
557 ta.add_initial_location_constraints([BooleanExpression::False]);
558 ta.add_initial_variable_constraints([BooleanExpression::False]);
559 }
560 }
561}
562
563pub struct DropUnsatisfiableRules {}
575
576impl Default for DropUnsatisfiableRules {
577 fn default() -> Self {
578 Self::new()
579 }
580}
581
582impl DropUnsatisfiableRules {
583 pub fn new() -> Self {
585 Self {}
586 }
587
588 fn get_unsatisfiable_rules<T: ThresholdAutomaton>(
590 &self,
591 solver_builder: SMTSolverBuilder,
592 ta: &T,
593 ) -> impl Iterator<Item = T::Rule> {
594 let mut ctx = StaticSMTContext::new(
595 solver_builder,
596 ta.parameters().cloned(),
597 Vec::new(),
598 ta.variables().cloned(),
599 )
600 .expect("Failed to create SMT context for preprocessing");
601
602 let rc = ta
603 .resilience_conditions()
604 .map(|rc| rc.encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx))
605 .collect::<Result<Vec<_>, _>>()
606 .expect("Failed to encode resilience condition");
607
608 let rc = ctx.get_smt_solver().and_many(rc);
609
610 ctx.get_smt_solver_mut()
611 .assert(rc)
612 .expect("Failed to assert resilience condition");
613
614 ta.rules()
615 .filter(move |rule| {
616 ctx.get_smt_solver_mut()
618 .push()
619 .expect("Failed to create new SMT frame");
620
621 let guard = rule
622 .guard()
623 .as_boolean_expr()
624 .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
625 .expect("Failed to encode guard");
626
627 let res = ctx
628 .assert_and_check_expr(guard)
629 .expect("Failed to verify guard satisfiability SMT query");
630
631 ctx.get_smt_solver_mut()
633 .pop()
634 .expect("Failed to pop SMT frame");
635
636 !res.is_sat()
637 })
638 .cloned()
639 .collect::<Vec<_>>()
640 .into_iter()
641 }
642}
643
644impl<
645 T: ModifiableThresholdAutomaton,
646 S: TASpecification,
647 C: ModelCheckerContext + ProvidesSMTSolverBuilder,
648> Preprocessor<T, S, C> for DropUnsatisfiableRules
649{
650 fn process(&self, ta: &mut T, _spec: &S, ctx: &C) {
651 let n_rules = ta.rules().count();
652
653 let solver_builder = ctx.get_solver_builder().clone();
654
655 let unsat = self
656 .get_unsatisfiable_rules(solver_builder, ta)
657 .collect::<Vec<_>>();
658 ta.retain_rules(|r| !unsat.contains(r));
659
660 info!(
661 "Preprocessor 'DropUnsatisfiableRules' on '{}' found and removed {} of {} unsatisfiable rules",
662 ta.name(),
663 n_rules - ta.rules().count(),
664 n_rules
665 );
666 }
667}
668
669pub struct CollapseLocations {}
675
676impl Default for CollapseLocations {
677 fn default() -> Self {
678 Self::new()
679 }
680}
681
682impl CollapseLocations {
683 pub fn new() -> Self {
685 Self {}
686 }
687
688 fn compute_collapsable_locations<T: ThresholdAutomaton>(
690 &self,
691 ta: &T,
692 locs_to_keep: &HashSet<&Location>,
693 ) -> Vec<Vec<Location>>
694 where
695 <T as ThresholdAutomaton>::Rule: Eq + std::hash::Hash,
696 {
697 let mut collapsable_sets = Vec::new();
698 let unreferenced_locations: HashSet<Location> = ta
699 .locations()
700 .filter(|loc| !locs_to_keep.contains(loc) && !ta.can_be_initial_location(loc))
701 .cloned()
702 .collect();
703 for (i, loc1) in unreferenced_locations.iter().enumerate() {
704 let mut current_set = Vec::new();
705 current_set.push(loc1.clone());
706
707 for loc2 in unreferenced_locations.iter().skip(i + 1) {
708 if Self::have_same_outgoing_transitions(ta, loc1, loc2) {
709 current_set.push(loc2.clone());
710 }
711 }
712 if current_set.len() > 1 {
713 collapsable_sets.push(current_set);
714 }
715 }
716 collapsable_sets
717 }
718
719 fn have_same_outgoing_transitions<T: ThresholdAutomaton>(
721 ta: &T,
722 loc1: &Location,
723 loc2: &Location,
724 ) -> bool
725 where
726 <T as ThresholdAutomaton>::Rule: Eq + std::hash::Hash,
727 {
728 let rules1 = ta
729 .outgoing_rules(loc1)
730 .filter(|x| x.source() != x.target())
731 .map(|r| (r.target(), r.guard(), r.actions().collect::<BTreeSet<_>>()))
732 .collect::<BTreeSet<_>>();
733
734 let rules2 = ta
735 .outgoing_rules(loc2)
736 .map(|r| (r.target(), r.guard(), r.actions().collect::<BTreeSet<_>>()))
737 .collect::<BTreeSet<_>>();
738
739 rules1 == rules2
740 }
741}
742
743impl<S: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
744 for CollapseLocations
745{
746 fn process(&self, ta: &mut GeneralThresholdAutomaton, spec: &S, _ctx: &C) {
747 let locations_to_keep: HashSet<&Location> = spec.locs_appearing().into_iter().collect();
748
749 let n_locations = ta.locations().count();
750 let n_rules = ta.rules().count();
751
752 loop {
753 let sets = self.compute_collapsable_locations(ta, &locations_to_keep);
754 if sets.is_empty() {
755 break;
756 }
757
758 for collapsable_set in sets {
759 if let Some(first) = collapsable_set.first() {
760 let kept_location: Location = first.clone();
761 let collapsable_set = &collapsable_set[1..];
762
763 ta.retain_rules(|r| !collapsable_set.contains(r.source()));
764
765 ta.transform_rules(|rule| {
766 if collapsable_set.contains(rule.target()) {
767 rule.update_target(kept_location.clone());
768 }
769 });
770
771 for loc in collapsable_set {
772 ta.remove_location_and_replace_with(loc, IntegerExpression::Const(0));
773 }
774 }
775 }
776 }
777
778 info!(
779 "Preprocessing removed {} locations, also removing {} rules.",
780 n_locations - ta.locations().count(),
781 n_rules - ta.rules().count(),
782 );
783 }
784}
785
786#[derive(Debug, Clone, PartialEq)]
790#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
791pub enum ExistingPreprocessors {
792 DropSelfLoops,
795 DropUnreachableLocations,
798 ReplaceTrivialGuardsStatic,
801 ReplaceTrivialGuardsSMT,
804 RemoveUnusedVariables,
806 DropUnsatisfiableRules,
808 CollapseLocations,
810 CheckInitCondSatSMT,
813}
814
815impl<S, C> From<ExistingPreprocessors> for Box<dyn Preprocessor<GeneralThresholdAutomaton, S, C>>
816where
817 S: TASpecification,
818 C: ModelCheckerContext + ProvidesSMTSolverBuilder,
819{
820 fn from(val: ExistingPreprocessors) -> Self {
821 match val {
822 ExistingPreprocessors::DropSelfLoops => Box::new(DropSelfLoops::default()),
823 ExistingPreprocessors::DropUnreachableLocations => {
824 Box::new(DropUnreachableLocations::default())
825 }
826 ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
827 Box::new(ReplaceTrivialGuardsStatic::default())
828 }
829 ExistingPreprocessors::ReplaceTrivialGuardsSMT => {
830 Box::new(ReplaceTrivialGuardsSMT::default())
831 }
832 ExistingPreprocessors::RemoveUnusedVariables => {
833 Box::new(RemoveUnusedVariables::default())
834 }
835 ExistingPreprocessors::DropUnsatisfiableRules => {
836 Box::new(DropUnsatisfiableRules::default())
837 }
838 ExistingPreprocessors::CollapseLocations => Box::new(CollapseLocations::default()),
839 ExistingPreprocessors::CheckInitCondSatSMT => Box::new(CheckInitCondSatSMT::default()),
840 }
841 }
842}
843
844impl fmt::Display for ExistingPreprocessors {
845 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846 match self {
847 ExistingPreprocessors::DropSelfLoops => write!(f, "DropSelfLoops"),
848 ExistingPreprocessors::DropUnreachableLocations => {
849 write!(f, "DropUnreachableLocations")
850 }
851 ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
852 write!(f, "ReplaceTrivialGuardsStatic")
853 }
854 ExistingPreprocessors::ReplaceTrivialGuardsSMT => write!(f, "ReplaceTrivialGuardsSMT"),
855 ExistingPreprocessors::RemoveUnusedVariables => write!(f, "RemoveUnusedVariables"),
856 ExistingPreprocessors::DropUnsatisfiableRules => write!(f, "DropUnsatisfiableRules"),
857 ExistingPreprocessors::CollapseLocations => write!(f, "CollapseLocations"),
858 ExistingPreprocessors::CheckInitCondSatSMT => write!(f, "CheckInitCondSatSMT"),
859 }
860 }
861}
862
863#[cfg(test)]
864mod test {
865
866 use core::fmt;
867
868 use taco_threshold_automaton::{
869 BooleanVarConstraint,
870 expressions::{
871 BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
872 },
873 general_threshold_automaton::{
874 Action,
875 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
876 },
877 };
878
879 use crate::internal_spec::upwards_closed_set::UpwardsClosedSet;
880
881 use super::*;
882
883 pub struct DummySpec(HashSet<Location>);
884
885 impl DummySpec {
886 fn new() -> Self {
887 Self(HashSet::new())
888 }
889 }
890
891 impl TASpecification for DummySpec {
892 fn locs_appearing(&self) -> impl IntoIterator<Item = &Location> {
893 self.0.iter()
894 }
895
896 fn var_constraint(
897 &self,
898 ) -> impl IntoIterator<
899 Item = &taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint,
900 > {
901 unimplemented!();
902 #[allow(unreachable_code)]
903 Vec::new()
904 }
905 }
906
907 impl fmt::Display for DummySpec {
908 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
909 unimplemented!()
910 }
911 }
912
913 #[derive(Debug, Clone)]
914 pub struct DummyContext;
915
916 impl ModelCheckerContext for DummyContext {
917 type CreationError = std::convert::Infallible;
918
919 type ContextOptions = ();
920
921 fn try_new(_opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
922 unimplemented!()
923 }
924 }
925
926 #[test]
927 fn test_drop_self_loops() {
928 let loc1 = Location::new("loc1");
929 let loc2 = Location::new("loc2");
930 let loc3 = Location::new("loc3");
931
932 let var1 = Variable::new("var1");
933 let var2 = Variable::new("var2");
934
935 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
936 .with_guard(BooleanVarConstraint::True)
937 .build();
938 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
939 .with_guard(BooleanVarConstraint::True)
940 .build();
941 let r2 = RuleBuilder::new(2, loc1.clone(), loc1.clone())
942 .with_guard(BooleanVarConstraint::True)
943 .with_action(Action::new(var1.clone(), IntegerExpression::Atom(var1.clone())).unwrap())
944 .build();
945 let r3 = RuleBuilder::new(3, loc1.clone(), loc1.clone())
946 .with_guard(BooleanVarConstraint::True)
947 .with_action(
948 Action::new(
949 var1.clone(),
950 IntegerExpression::Atom(var1.clone()) + IntegerExpression::Const(1),
951 )
952 .unwrap(),
953 )
954 .build();
955 let r4 = RuleBuilder::new(4, loc2.clone(), loc2.clone())
956 .with_guard(BooleanVarConstraint::True)
957 .build();
958 let r5 = RuleBuilder::new(5, loc3.clone(), loc3.clone())
959 .with_guard(BooleanVarConstraint::True)
960 .build();
961
962 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
963 .with_variables([var1, var2])
964 .unwrap()
965 .with_locations([loc1.clone(), loc2.clone(), loc3.clone()])
966 .unwrap()
967 .initialize()
968 .with_rules([
969 r0.clone(),
970 r1.clone(),
971 r2.clone(),
972 r3.clone(),
973 r4.clone(),
974 r5.clone(),
975 ])
976 .unwrap()
977 .build();
978
979 DropSelfLoops::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
980
981 assert_eq!(ta.rules().count(), 2);
982 assert!(!ta.rules().any(|r| r0 == *r));
983 assert!(ta.rules().any(|r| r1 == *r));
984 assert!(!ta.rules().any(|r| r2 == *r));
985 assert!(ta.rules().any(|r| r3 == *r));
986 assert!(!ta.rules().any(|r| r4 == *r));
987 assert!(!ta.rules().any(|r| r5 == *r));
988 }
989
990 #[test]
991 fn test_drop_unreachable_locations() {
992 let loc1 = Location::new("loc1");
993 let loc2 = Location::new("loc2");
994 let loc3 = Location::new("loc3");
995 let loc4 = Location::new("loc4");
996 let loc5 = Location::new("loc5");
997
998 let var1 = Variable::new("var1");
999 let var2 = Variable::new("var2");
1000
1001 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1002 .with_guard(BooleanVarConstraint::True)
1003 .build();
1004 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1005 .with_guard(BooleanVarConstraint::True)
1006 .build();
1007 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1008 .with_guard(BooleanVarConstraint::True)
1009 .build();
1010 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1011 .with_guard(BooleanVarConstraint::True)
1012 .build();
1013
1014 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1015 .with_variables([var1, var2])
1016 .unwrap()
1017 .with_locations([
1018 loc1.clone(),
1019 loc2.clone(),
1020 loc3.clone(),
1021 loc4.clone(),
1022 loc5.clone(),
1023 ])
1024 .unwrap()
1025 .initialize()
1026 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1027 .unwrap()
1028 .with_initial_location_constraints([
1029 BooleanExpression::ComparisonExpression(
1030 Box::new(IntegerExpression::Atom(loc2.clone())),
1031 ComparisonOp::Eq,
1032 Box::new(IntegerExpression::Const(0)),
1033 ),
1034 BooleanExpression::ComparisonExpression(
1035 Box::new(IntegerExpression::Atom(loc3.clone())),
1036 ComparisonOp::Eq,
1037 Box::new(IntegerExpression::Const(0)),
1038 ),
1039 BooleanExpression::ComparisonExpression(
1040 Box::new(IntegerExpression::Atom(loc4.clone())),
1041 ComparisonOp::Eq,
1042 Box::new(IntegerExpression::Const(0)),
1043 ),
1044 ])
1045 .unwrap()
1046 .build();
1047
1048 DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1049
1050 assert_eq!(ta.locations().count(), 4);
1051 assert!(ta.locations().any(|l| *l == loc1));
1052 assert!(ta.locations().any(|l| *l == loc2));
1053 assert!(ta.locations().any(|l| *l == loc3));
1054 assert!(!ta.locations().any(|l| *l == loc4));
1055 assert!(ta.locations().any(|l| *l == loc5));
1056
1057 assert_eq!(ta.rules().count(), 3);
1058 assert!(ta.rules().any(|r| r0 == *r));
1059 assert!(ta.rules().any(|r| r1 == *r));
1060 assert!(ta.rules().any(|r| r2 == *r));
1061 assert!(!ta.rules().any(|r| r3 == *r));
1062 }
1063
1064 #[test]
1065 fn test_drop_unreachable_locations_and_modify_init_constraints() {
1066 let loc1 = Location::new("loc1");
1067 let loc2 = Location::new("loc2");
1068 let loc3 = Location::new("loc3");
1069 let loc4 = Location::new("loc4");
1070 let loc5 = Location::new("loc5");
1071
1072 let var1 = Variable::new("var1");
1073 let var2 = Variable::new("var2");
1074
1075 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1076 .with_guard(BooleanVarConstraint::True)
1077 .build();
1078 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1079 .with_guard(BooleanVarConstraint::True)
1080 .build();
1081 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1082 .with_guard(BooleanVarConstraint::True)
1083 .build();
1084 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1085 .with_guard(BooleanVarConstraint::True)
1086 .build();
1087
1088 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1089 .with_variables([var1, var2])
1090 .unwrap()
1091 .with_locations([
1092 loc1.clone(),
1093 loc2.clone(),
1094 loc3.clone(),
1095 loc4.clone(),
1096 loc5.clone(),
1097 ])
1098 .unwrap()
1099 .initialize()
1100 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1101 .unwrap()
1102 .with_initial_location_constraints([
1103 BooleanExpression::ComparisonExpression(
1104 Box::new(IntegerExpression::Atom(loc2.clone())),
1105 ComparisonOp::Eq,
1106 Box::new(IntegerExpression::Const(0)),
1107 ),
1108 BooleanExpression::ComparisonExpression(
1109 Box::new(IntegerExpression::Atom(loc3.clone())),
1110 ComparisonOp::Eq,
1111 Box::new(IntegerExpression::Const(0)),
1112 ),
1113 BooleanExpression::ComparisonExpression(
1114 Box::new(IntegerExpression::Atom(loc4.clone())),
1115 ComparisonOp::Eq,
1116 Box::new(IntegerExpression::Const(0)),
1117 ),
1118 ])
1119 .unwrap()
1120 .build();
1121
1122 DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1123
1124 assert_eq!(ta.locations().count(), 4);
1125 assert!(ta.locations().any(|l| *l == loc1));
1126 assert!(ta.locations().any(|l| *l == loc2));
1127 assert!(ta.locations().any(|l| *l == loc3));
1128 assert!(!ta.locations().any(|l| *l == loc4));
1129 assert!(ta.locations().any(|l| *l == loc5));
1130
1131 assert_eq!(ta.rules().count(), 3);
1132 assert!(ta.rules().any(|r| r0 == *r));
1133 assert!(ta.rules().any(|r| r1 == *r));
1134 assert!(ta.rules().any(|r| r2 == *r));
1135 assert!(!ta.rules().any(|r| r3 == *r));
1136
1137 assert!(ta.initial_location_conditions().any(|l| l
1138 == &BooleanExpression::ComparisonExpression(
1139 Box::new(IntegerExpression::Atom(loc2.clone())),
1140 ComparisonOp::Eq,
1141 Box::new(IntegerExpression::Const(0)),
1142 )));
1143 assert!(ta.initial_location_conditions().any(|l| l
1144 == &BooleanExpression::ComparisonExpression(
1145 Box::new(IntegerExpression::Atom(loc3.clone())),
1146 ComparisonOp::Eq,
1147 Box::new(IntegerExpression::Const(0)),
1148 )));
1149 assert!(!ta.initial_location_conditions().any(|l| l
1150 == &BooleanExpression::ComparisonExpression(
1151 Box::new(IntegerExpression::Atom(loc4.clone())),
1152 ComparisonOp::Eq,
1153 Box::new(IntegerExpression::Const(0)),
1154 )));
1155 }
1156
1157 #[test]
1158 fn test_combine_trivial_guards_static() {
1159 let loc1 = Location::new("loc1");
1160 let loc2 = Location::new("loc2");
1161 let loc3 = Location::new("loc3");
1162 let loc4 = Location::new("loc4");
1163 let loc5 = Location::new("loc5");
1164
1165 let var1 = Variable::new("var1");
1166 let var2 = Variable::new("var2");
1167
1168 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1169 .with_guard(BooleanVarConstraint::ComparisonExpression(
1170 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1171 ComparisonOp::Geq,
1172 Box::new(IntegerExpression::Const(0)),
1173 ))
1174 .build();
1175 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1176 .with_guard(BooleanVarConstraint::BinaryExpression(
1177 Box::new(BooleanVarConstraint::ComparisonExpression(
1178 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1179 ComparisonOp::Geq,
1180 Box::new(IntegerExpression::Const(1)),
1181 )),
1182 BooleanConnective::Or,
1183 Box::new(BooleanVarConstraint::ComparisonExpression(
1184 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1185 ComparisonOp::Eq,
1186 Box::new(IntegerExpression::Const(0)),
1187 )),
1188 ))
1189 .build();
1190 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1191 .with_guard(BooleanVarConstraint::BinaryExpression(
1192 Box::new(BooleanVarConstraint::ComparisonExpression(
1193 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1194 ComparisonOp::Neq,
1195 Box::new(IntegerExpression::Const(5)),
1196 )),
1197 BooleanConnective::Or,
1198 Box::new(BooleanVarConstraint::ComparisonExpression(
1199 Box::new(IntegerExpression::Const(6)),
1200 ComparisonOp::Neq,
1201 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1202 )),
1203 ))
1204 .build();
1205 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1206 .with_guard(BooleanVarConstraint::True)
1207 .build();
1208
1209 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1210 .with_variables([var1, var2])
1211 .unwrap()
1212 .with_locations([
1213 loc1.clone(),
1214 loc2.clone(),
1215 loc3.clone(),
1216 loc4.clone(),
1217 loc5.clone(),
1218 ])
1219 .unwrap()
1220 .initialize()
1221 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1222 .unwrap()
1223 .with_initial_location_constraints([
1224 BooleanExpression::ComparisonExpression(
1225 Box::new(IntegerExpression::Atom(loc2.clone())),
1226 ComparisonOp::Eq,
1227 Box::new(IntegerExpression::Const(0)),
1228 ),
1229 BooleanExpression::ComparisonExpression(
1230 Box::new(IntegerExpression::Atom(loc3.clone())),
1231 ComparisonOp::Eq,
1232 Box::new(IntegerExpression::Const(0)),
1233 ),
1234 BooleanExpression::ComparisonExpression(
1235 Box::new(IntegerExpression::Atom(loc4.clone())),
1236 ComparisonOp::Eq,
1237 Box::new(IntegerExpression::Const(0)),
1238 ),
1239 ])
1240 .unwrap()
1241 .build();
1242
1243 ReplaceTrivialGuardsStatic::new().process(
1244 &mut ta,
1245 &DummySpec::new(),
1246 &SMTSolverBuilder::default(),
1247 );
1248
1249 assert_eq!(ta.rules().count(), 4);
1250 assert!(!ta.rules().any(|r| r0 == *r));
1251 assert!(!ta.rules().any(|r| r1 == *r));
1252 assert!(ta.rules().any(|r| r2 == *r));
1253 assert!(ta.rules().any(|r| r3 == *r));
1254
1255 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1256 .with_guard(BooleanVarConstraint::True)
1257 .build();
1258 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1259 .with_guard(BooleanVarConstraint::True)
1260 .build();
1261
1262 assert!(ta.rules().any(|r| r0 == *r));
1263 assert!(ta.rules().any(|r| r1 == *r));
1264 }
1265
1266 #[test]
1267 fn test_combine_trivial_guards_smt() {
1268 let loc1 = Location::new("loc1");
1269 let loc2 = Location::new("loc2");
1270 let loc3 = Location::new("loc3");
1271 let loc4 = Location::new("loc4");
1272 let loc5 = Location::new("loc5");
1273
1274 let var1 = Variable::new("var1");
1275 let var2 = Variable::new("var2");
1276
1277 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1278 .with_guard(BooleanVarConstraint::ComparisonExpression(
1279 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1280 ComparisonOp::Geq,
1281 Box::new(IntegerExpression::Const(0)),
1282 ))
1283 .build();
1284 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1285 .with_guard(BooleanVarConstraint::BinaryExpression(
1286 Box::new(BooleanVarConstraint::ComparisonExpression(
1287 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1288 ComparisonOp::Geq,
1289 Box::new(IntegerExpression::Const(5)),
1290 )),
1291 BooleanConnective::Or,
1292 Box::new(BooleanVarConstraint::ComparisonExpression(
1293 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1294 ComparisonOp::Lt,
1295 Box::new(IntegerExpression::Const(5)),
1296 )),
1297 ))
1298 .build();
1299 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1300 .with_guard(
1301 BooleanVarConstraint::ComparisonExpression(
1302 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1303 ComparisonOp::Geq,
1304 Box::new(IntegerExpression::Const(0)),
1305 ) & BooleanVarConstraint::ComparisonExpression(
1306 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1307 ComparisonOp::Lt,
1308 Box::new(IntegerExpression::Const(42)),
1309 ),
1310 )
1311 .build();
1312 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1313 .with_guard(BooleanVarConstraint::True)
1314 .build();
1315
1316 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1317 .with_variables([var1, var2])
1318 .unwrap()
1319 .with_locations([
1320 loc1.clone(),
1321 loc2.clone(),
1322 loc3.clone(),
1323 loc4.clone(),
1324 loc5.clone(),
1325 ])
1326 .unwrap()
1327 .with_parameter(Parameter::new("n"))
1328 .unwrap()
1329 .initialize()
1330 .with_resilience_condition(BooleanExpression::ComparisonExpression(
1331 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1332 ComparisonOp::Geq,
1333 Box::new(IntegerExpression::Const(1)),
1334 ))
1335 .unwrap()
1336 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1337 .unwrap()
1338 .with_initial_location_constraints([
1339 BooleanExpression::ComparisonExpression(
1340 Box::new(IntegerExpression::Atom(loc2.clone())),
1341 ComparisonOp::Eq,
1342 Box::new(IntegerExpression::Const(0)),
1343 ),
1344 BooleanExpression::ComparisonExpression(
1345 Box::new(IntegerExpression::Atom(loc3.clone())),
1346 ComparisonOp::Eq,
1347 Box::new(IntegerExpression::Const(0)),
1348 ),
1349 BooleanExpression::ComparisonExpression(
1350 Box::new(IntegerExpression::Atom(loc4.clone())),
1351 ComparisonOp::Eq,
1352 Box::new(IntegerExpression::Const(0)),
1353 ),
1354 ])
1355 .unwrap()
1356 .build();
1357
1358 ReplaceTrivialGuardsSMT::new().process(
1359 &mut ta,
1360 &DummySpec::new(),
1361 &SMTSolverBuilder::default(),
1362 );
1363
1364 assert_eq!(ta.rules().count(), 4);
1365 assert!(!ta.rules().any(|r| r0 == *r));
1366 assert!(!ta.rules().any(|r| r1 == *r));
1367
1368 assert!(ta.rules().any(|r| r3 == *r));
1369
1370 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1371 .with_guard(BooleanVarConstraint::True)
1372 .build();
1373 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1374 .with_guard(BooleanVarConstraint::True)
1375 .build();
1376 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1377 .with_guard(
1378 BooleanExpression::True
1379 & BooleanVarConstraint::ComparisonExpression(
1380 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1381 ComparisonOp::Lt,
1382 Box::new(IntegerExpression::Const(42)),
1383 ),
1384 )
1385 .build();
1386
1387 assert!(ta.rules().any(|r| r0 == *r));
1388 assert!(ta.rules().any(|r| r1 == *r));
1389 assert!(ta.rules().any(|r| r2 == *r));
1390 }
1391
1392 #[test]
1393 fn test_remove_unused_variables() {
1394 let loc1 = Location::new("loc1");
1395 let loc2 = Location::new("loc2");
1396 let loc3 = Location::new("loc3");
1397 let loc4 = Location::new("loc4");
1398 let loc5 = Location::new("loc5");
1399
1400 let var1 = Variable::new("var1");
1401 let var2 = Variable::new("var2");
1402 let var3 = Variable::new("var3");
1403 let var4 = Variable::new("var4");
1404
1405 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1406 .with_guard(BooleanVarConstraint::True)
1407 .build();
1408 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1409 .with_guard(BooleanVarConstraint::True)
1410 .with_action(Action::new_reset(var1.clone()))
1411 .build();
1412 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1413 .with_guard(BooleanVarConstraint::ComparisonExpression(
1414 Box::new(IntegerExpression::Atom(var3.clone())),
1415 ComparisonOp::Geq,
1416 Box::new(IntegerExpression::Const(0)),
1417 ))
1418 .build();
1419 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1420 .with_guard(BooleanVarConstraint::True)
1421 .build();
1422
1423 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1424 .with_variables([var1.clone(), var2.clone(), var3.clone(), var4.clone()])
1425 .unwrap()
1426 .with_locations([
1427 loc1.clone(),
1428 loc2.clone(),
1429 loc3.clone(),
1430 loc4.clone(),
1431 loc5.clone(),
1432 ])
1433 .unwrap()
1434 .initialize()
1435 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1436 .unwrap()
1437 .with_initial_variable_constraints([
1438 BooleanExpression::ComparisonExpression(
1439 Box::new(IntegerExpression::Atom(var1.clone())),
1440 ComparisonOp::Eq,
1441 Box::new(IntegerExpression::Const(0)),
1442 ),
1443 BooleanExpression::ComparisonExpression(
1444 Box::new(IntegerExpression::Atom(var1.clone())),
1445 ComparisonOp::Eq,
1446 Box::new(IntegerExpression::Const(0)),
1447 ),
1448 BooleanExpression::ComparisonExpression(
1449 Box::new(IntegerExpression::Atom(var4.clone())),
1450 ComparisonOp::Gt,
1451 Box::new(IntegerExpression::Const(0)),
1452 ),
1453 ])
1454 .unwrap()
1455 .build();
1456
1457 RemoveUnusedVariables {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1458
1459 assert_eq!(ta.variables().count(), 2);
1460 assert!(!ta.variables().any(|v| *v == var1));
1461 assert!(!ta.variables().any(|v| *v == var2));
1462 assert!(ta.variables().any(|v| *v == var3));
1463 assert!(ta.variables().any(|v| *v == var4));
1464 }
1465
1466 #[test]
1467 fn test_init_cond_sat() {
1468 let loc1 = Location::new("loc1");
1469 let loc2 = Location::new("loc2");
1470 let loc3 = Location::new("loc3");
1471 let loc4 = Location::new("loc4");
1472 let loc5 = Location::new("loc5");
1473
1474 let var1 = Variable::new("var1");
1475 let var2 = Variable::new("var2");
1476
1477 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1478 .with_guard(BooleanVarConstraint::True)
1479 .build();
1480 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1481 .with_guard(BooleanVarConstraint::True)
1482 .build();
1483 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1484 .with_guard(BooleanVarConstraint::True)
1485 .build();
1486 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1487 .with_guard(BooleanVarConstraint::True)
1488 .build();
1489
1490 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1491 .with_variables([var1, var2])
1492 .unwrap()
1493 .with_locations([
1494 loc1.clone(),
1495 loc2.clone(),
1496 loc3.clone(),
1497 loc4.clone(),
1498 loc5.clone(),
1499 ])
1500 .unwrap()
1501 .initialize()
1502 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1503 .unwrap()
1504 .with_initial_location_constraints([
1505 BooleanExpression::ComparisonExpression(
1506 Box::new(IntegerExpression::Atom(loc2.clone())),
1507 ComparisonOp::Eq,
1508 Box::new(IntegerExpression::Const(0)),
1509 ),
1510 BooleanExpression::ComparisonExpression(
1511 Box::new(IntegerExpression::Atom(loc2.clone())),
1512 ComparisonOp::Eq,
1513 Box::new(IntegerExpression::Const(1)),
1514 ),
1515 BooleanExpression::ComparisonExpression(
1516 Box::new(IntegerExpression::Atom(loc3.clone())),
1517 ComparisonOp::Eq,
1518 Box::new(IntegerExpression::Const(0)),
1519 ),
1520 BooleanExpression::ComparisonExpression(
1521 Box::new(IntegerExpression::Atom(loc4.clone())),
1522 ComparisonOp::Eq,
1523 Box::new(IntegerExpression::Const(0)),
1524 ),
1525 ])
1526 .unwrap()
1527 .build();
1528
1529 CheckInitCondSatSMT::default().process(
1530 &mut ta,
1531 &DummySpec::new(),
1532 &SMTSolverBuilder::default(),
1533 );
1534
1535 assert_eq!(ta.rules().count(), 0);
1536 assert!(
1537 ta.initial_variable_conditions()
1538 .any(|c| c == &BooleanExpression::False)
1539 );
1540 assert!(
1541 ta.initial_location_conditions()
1542 .any(|c| c == &BooleanExpression::False)
1543 )
1544 }
1545
1546 #[test]
1547 fn test_collapse_locations_no_spec() {
1548 let loc1 = Location::new("loc1");
1549 let loc2 = Location::new("loc2");
1550 let loc3 = Location::new("loc3");
1551 let loc4 = Location::new("loc4");
1552 let loc5 = Location::new("loc5");
1553 let loc6 = Location::new("loc6");
1554 let loc7 = Location::new("loc7");
1555
1556 let var1 = Variable::new("var1");
1557 let var2 = Variable::new("var2");
1558
1559 let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1560 .with_guard(BooleanVarConstraint::True)
1561 .build();
1562 let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1563 .with_guard(BooleanVarConstraint::True)
1564 .build();
1565 let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1566 .with_guard(BooleanVarConstraint::True)
1567 .build();
1568 let r3 = RuleBuilder::new(3, loc1.clone(), loc5.clone())
1569 .with_guard(BooleanVarConstraint::True)
1570 .build();
1571 let r4 = RuleBuilder::new(4, loc2.clone(), loc6.clone())
1572 .with_guard(BooleanVarConstraint::ComparisonExpression(
1573 Box::new(IntegerExpression::Atom(var2.clone())),
1574 ComparisonOp::Geq,
1575 Box::new(IntegerExpression::Const(0)),
1576 ))
1577 .build();
1578 let r5 = RuleBuilder::new(5, loc3.clone(), loc6.clone())
1579 .with_guard(BooleanVarConstraint::ComparisonExpression(
1580 Box::new(IntegerExpression::Atom(var1.clone())),
1581 ComparisonOp::Geq,
1582 Box::new(IntegerExpression::Const(0)),
1583 ))
1584 .build();
1585 let r6 = RuleBuilder::new(6, loc4.clone(), loc6.clone())
1586 .with_guard(BooleanVarConstraint::ComparisonExpression(
1587 Box::new(IntegerExpression::Atom(var1.clone())),
1588 ComparisonOp::Geq,
1589 Box::new(IntegerExpression::Const(0)),
1590 ))
1591 .build();
1592 let r7 = RuleBuilder::new(7, loc5.clone(), loc7.clone())
1593 .with_guard(BooleanVarConstraint::ComparisonExpression(
1594 Box::new(IntegerExpression::Atom(var1.clone())),
1595 ComparisonOp::Geq,
1596 Box::new(IntegerExpression::Const(0)),
1597 ))
1598 .build();
1599
1600 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1601 .with_variables([var1.clone(), var2.clone()])
1602 .unwrap()
1603 .with_locations([
1604 loc1.clone(),
1605 loc2.clone(),
1606 loc3.clone(),
1607 loc4.clone(),
1608 loc5.clone(),
1609 loc6.clone(),
1610 loc7.clone(),
1611 ])
1612 .unwrap()
1613 .initialize()
1614 .with_rules([
1615 r0.clone(),
1616 r1.clone(),
1617 r2.clone(),
1618 r3.clone(),
1619 r4.clone(),
1620 r5.clone(),
1621 r6.clone(),
1622 r7.clone(),
1623 ])
1624 .unwrap()
1625 .with_initial_location_constraints([
1626 BooleanExpression::ComparisonExpression(
1627 Box::new(IntegerExpression::Atom(loc2.clone())),
1628 ComparisonOp::Eq,
1629 Box::new(IntegerExpression::Const(0)),
1630 ),
1631 BooleanExpression::ComparisonExpression(
1632 Box::new(IntegerExpression::Atom(loc3.clone())),
1633 ComparisonOp::Eq,
1634 Box::new(IntegerExpression::Const(0)),
1635 ),
1636 BooleanExpression::ComparisonExpression(
1637 Box::new(IntegerExpression::Atom(loc4.clone())),
1638 ComparisonOp::Eq,
1639 Box::new(IntegerExpression::Const(0)),
1640 ),
1641 BooleanExpression::ComparisonExpression(
1642 Box::new(IntegerExpression::Atom(loc5.clone())),
1643 ComparisonOp::Eq,
1644 Box::new(IntegerExpression::Const(0)),
1645 ),
1646 BooleanExpression::ComparisonExpression(
1647 Box::new(IntegerExpression::Atom(loc6.clone())),
1648 ComparisonOp::Eq,
1649 Box::new(IntegerExpression::Const(0)),
1650 ),
1651 BooleanExpression::ComparisonExpression(
1652 Box::new(IntegerExpression::Atom(loc7.clone())),
1653 ComparisonOp::Eq,
1654 Box::new(IntegerExpression::Const(0)),
1655 ),
1656 ])
1657 .unwrap()
1658 .build();
1659
1660 CollapseLocations {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1661
1662 assert_eq!(ta.locations().count(), 4);
1663
1664 assert_eq!(ta.rules().count(), 6);
1665 }
1666
1667 #[test]
1668 fn test_collapse_locations_with_spec() {
1669 let loc1 = Location::new("loc1");
1670 let loc2 = Location::new("loc2");
1671 let loc3 = Location::new("loc3");
1672 let loc4 = Location::new("loc4");
1673
1674 let var1 = Variable::new("var1");
1675
1676 let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1677 .with_guard(BooleanVarConstraint::ComparisonExpression(
1678 Box::new(IntegerExpression::Atom(var1.clone())),
1679 ComparisonOp::Geq,
1680 Box::new(IntegerExpression::Const(0)),
1681 ))
1682 .build();
1683 let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1684 .with_guard(BooleanVarConstraint::ComparisonExpression(
1685 Box::new(IntegerExpression::Atom(var1.clone())),
1686 ComparisonOp::Geq,
1687 Box::new(IntegerExpression::Const(0)),
1688 ))
1689 .build();
1690 let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1691 .with_guard(BooleanVarConstraint::ComparisonExpression(
1692 Box::new(IntegerExpression::Atom(var1.clone())),
1693 ComparisonOp::Geq,
1694 Box::new(IntegerExpression::Const(0)),
1695 ))
1696 .build();
1697
1698 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1699 .with_variables([var1.clone()])
1700 .unwrap()
1701 .with_locations([loc1.clone(), loc2.clone(), loc3.clone(), loc4.clone()])
1702 .unwrap()
1703 .initialize()
1704 .with_rules([r0.clone(), r1.clone(), r2.clone()])
1705 .unwrap()
1706 .with_initial_location_constraints([
1707 BooleanExpression::ComparisonExpression(
1708 Box::new(IntegerExpression::Atom(loc2.clone())),
1709 ComparisonOp::Eq,
1710 Box::new(IntegerExpression::Const(0)),
1711 ),
1712 BooleanExpression::ComparisonExpression(
1713 Box::new(IntegerExpression::Atom(loc3.clone())),
1714 ComparisonOp::Eq,
1715 Box::new(IntegerExpression::Const(0)),
1716 ),
1717 BooleanExpression::ComparisonExpression(
1718 Box::new(IntegerExpression::Atom(loc4.clone())),
1719 ComparisonOp::Eq,
1720 Box::new(IntegerExpression::Const(0)),
1721 ),
1722 ])
1723 .unwrap()
1724 .build();
1725
1726 let spec = UpwardsClosedSet::new_cover([loc2.clone()]);
1727
1728 CollapseLocations {}.process(&mut ta, &spec, &DummyContext {});
1729
1730 assert_eq!(ta.locations().count(), 3);
1731
1732 assert_eq!(ta.rules().count(), 3);
1733 }
1734
1735 #[test]
1736 fn test_display_available_preprocessor() {
1737 assert_eq!(
1738 format!("{}", ExistingPreprocessors::DropSelfLoops),
1739 "DropSelfLoops"
1740 );
1741 assert_eq!(
1742 format!("{}", ExistingPreprocessors::DropUnreachableLocations),
1743 "DropUnreachableLocations"
1744 );
1745 assert_eq!(
1746 format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsStatic),
1747 "ReplaceTrivialGuardsStatic"
1748 );
1749 assert_eq!(
1750 format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsSMT),
1751 "ReplaceTrivialGuardsSMT"
1752 );
1753 assert_eq!(
1754 format!("{}", ExistingPreprocessors::RemoveUnusedVariables),
1755 "RemoveUnusedVariables"
1756 );
1757 assert_eq!(
1758 format!("{}", ExistingPreprocessors::DropUnsatisfiableRules),
1759 "DropUnsatisfiableRules"
1760 );
1761 assert_eq!(
1762 format!("{}", ExistingPreprocessors::CollapseLocations),
1763 "CollapseLocations"
1764 );
1765 }
1766}