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, TargetSpec};
51
52pub trait Preprocessor<
57 T: ModifiableThresholdAutomaton + ThresholdAutomaton,
58 S: TargetSpec,
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: TargetSpec, C: ModelCheckerContext> Preprocessor<T, S, C>
91 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: TargetSpec, 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.get_locations_in_target().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: TargetSpec, 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: TargetSpec, 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: TargetSpec, 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: TargetSpec, 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: TargetSpec,
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: TargetSpec, 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> =
748 spec.get_locations_in_target().into_iter().collect();
749
750 let n_locations = ta.locations().count();
751 let n_rules = ta.rules().count();
752
753 loop {
754 let sets = self.compute_collapsable_locations(ta, &locations_to_keep);
755 if sets.is_empty() {
756 break;
757 }
758
759 for collapsable_set in sets {
760 if let Some(first) = collapsable_set.first() {
761 let kept_location: Location = first.clone();
762 let collapsable_set = &collapsable_set[1..];
763
764 ta.retain_rules(|r| !collapsable_set.contains(r.source()));
765
766 ta.transform_rules(|rule| {
767 if collapsable_set.contains(rule.target()) {
768 rule.update_target(kept_location.clone());
769 }
770 });
771
772 for loc in collapsable_set {
773 ta.remove_location_and_replace_with(loc, IntegerExpression::Const(0));
774 }
775 }
776 }
777 }
778
779 info!(
780 "Preprocessing removed {} locations, also removing {} rules.",
781 n_locations - ta.locations().count(),
782 n_rules - ta.rules().count(),
783 );
784 }
785}
786
787#[derive(Debug, Clone, PartialEq)]
791#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
792pub enum ExistingPreprocessors {
793 DropSelfLoops,
796 DropUnreachableLocations,
799 ReplaceTrivialGuardsStatic,
802 ReplaceTrivialGuardsSMT,
805 RemoveUnusedVariables,
807 DropUnsatisfiableRules,
809 CollapseLocations,
811 CheckInitCondSatSMT,
814}
815
816impl<S, C> From<ExistingPreprocessors> for Box<dyn Preprocessor<GeneralThresholdAutomaton, S, C>>
817where
818 S: TargetSpec,
819 C: ModelCheckerContext + ProvidesSMTSolverBuilder,
820{
821 fn from(val: ExistingPreprocessors) -> Self {
822 match val {
823 ExistingPreprocessors::DropSelfLoops => Box::new(DropSelfLoops::default()),
824 ExistingPreprocessors::DropUnreachableLocations => {
825 Box::new(DropUnreachableLocations::default())
826 }
827 ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
828 Box::new(ReplaceTrivialGuardsStatic::default())
829 }
830 ExistingPreprocessors::ReplaceTrivialGuardsSMT => {
831 Box::new(ReplaceTrivialGuardsSMT::default())
832 }
833 ExistingPreprocessors::RemoveUnusedVariables => {
834 Box::new(RemoveUnusedVariables::default())
835 }
836 ExistingPreprocessors::DropUnsatisfiableRules => {
837 Box::new(DropUnsatisfiableRules::default())
838 }
839 ExistingPreprocessors::CollapseLocations => Box::new(CollapseLocations::default()),
840 ExistingPreprocessors::CheckInitCondSatSMT => Box::new(CheckInitCondSatSMT::default()),
841 }
842 }
843}
844
845impl fmt::Display for ExistingPreprocessors {
846 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
847 match self {
848 ExistingPreprocessors::DropSelfLoops => write!(f, "DropSelfLoops"),
849 ExistingPreprocessors::DropUnreachableLocations => {
850 write!(f, "DropUnreachableLocations")
851 }
852 ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
853 write!(f, "ReplaceTrivialGuardsStatic")
854 }
855 ExistingPreprocessors::ReplaceTrivialGuardsSMT => write!(f, "ReplaceTrivialGuardsSMT"),
856 ExistingPreprocessors::RemoveUnusedVariables => write!(f, "RemoveUnusedVariables"),
857 ExistingPreprocessors::DropUnsatisfiableRules => write!(f, "DropUnsatisfiableRules"),
858 ExistingPreprocessors::CollapseLocations => write!(f, "CollapseLocations"),
859 ExistingPreprocessors::CheckInitCondSatSMT => write!(f, "CheckInitCondSatSMT"),
860 }
861 }
862}
863
864#[cfg(test)]
865mod test {
866
867 use core::fmt;
868
869 use taco_threshold_automaton::{
870 BooleanVarConstraint,
871 expressions::{
872 BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
873 },
874 general_threshold_automaton::{
875 Action,
876 builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
877 },
878 };
879
880 use crate::{
881 DummyError,
882 reachability_specification::{DisjunctionTargetConfig, TargetConfig},
883 };
884
885 use super::*;
886
887 pub struct DummySpec(HashSet<Location>);
888
889 impl DummySpec {
890 fn new() -> Self {
891 Self(HashSet::new())
892 }
893 }
894
895 impl TargetSpec for DummySpec {
896 fn get_locations_in_target(&self) -> impl IntoIterator<Item = &Location> {
897 self.0.iter()
898 }
899
900 fn get_variable_constraint(
901 &self,
902 ) -> impl IntoIterator<
903 Item = &taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint,
904 > {
905 unimplemented!();
906 #[allow(unreachable_code)]
907 Vec::new()
908 }
909 }
910
911 impl fmt::Display for DummySpec {
912 fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
913 unimplemented!()
914 }
915 }
916
917 #[derive(Debug, Clone)]
918 pub struct DummyContext;
919
920 impl ModelCheckerContext for DummyContext {
921 type CreationError = DummyError;
922
923 type ContextOptions = ();
924
925 fn try_new(_opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
926 unimplemented!()
927 }
928 }
929
930 #[test]
931 fn test_drop_self_loops() {
932 let loc1 = Location::new("loc1");
933 let loc2 = Location::new("loc2");
934 let loc3 = Location::new("loc3");
935
936 let var1 = Variable::new("var1");
937 let var2 = Variable::new("var2");
938
939 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
940 .with_guard(BooleanVarConstraint::True)
941 .build();
942 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
943 .with_guard(BooleanVarConstraint::True)
944 .build();
945 let r2 = RuleBuilder::new(2, loc1.clone(), loc1.clone())
946 .with_guard(BooleanVarConstraint::True)
947 .with_action(Action::new(var1.clone(), IntegerExpression::Atom(var1.clone())).unwrap())
948 .build();
949 let r3 = RuleBuilder::new(3, loc1.clone(), loc1.clone())
950 .with_guard(BooleanVarConstraint::True)
951 .with_action(
952 Action::new(
953 var1.clone(),
954 IntegerExpression::Atom(var1.clone()) + IntegerExpression::Const(1),
955 )
956 .unwrap(),
957 )
958 .build();
959 let r4 = RuleBuilder::new(4, loc2.clone(), loc2.clone())
960 .with_guard(BooleanVarConstraint::True)
961 .build();
962 let r5 = RuleBuilder::new(5, loc3.clone(), loc3.clone())
963 .with_guard(BooleanVarConstraint::True)
964 .build();
965
966 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
967 .with_variables([var1, var2])
968 .unwrap()
969 .with_locations([loc1.clone(), loc2.clone(), loc3.clone()])
970 .unwrap()
971 .initialize()
972 .with_rules([
973 r0.clone(),
974 r1.clone(),
975 r2.clone(),
976 r3.clone(),
977 r4.clone(),
978 r5.clone(),
979 ])
980 .unwrap()
981 .build();
982
983 DropSelfLoops::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
984
985 assert_eq!(ta.rules().count(), 2);
986 assert!(!ta.rules().any(|r| r0 == *r));
987 assert!(ta.rules().any(|r| r1 == *r));
988 assert!(!ta.rules().any(|r| r2 == *r));
989 assert!(ta.rules().any(|r| r3 == *r));
990 assert!(!ta.rules().any(|r| r4 == *r));
991 assert!(!ta.rules().any(|r| r5 == *r));
992 }
993
994 #[test]
995 fn test_drop_unreachable_locations() {
996 let loc1 = Location::new("loc1");
997 let loc2 = Location::new("loc2");
998 let loc3 = Location::new("loc3");
999 let loc4 = Location::new("loc4");
1000 let loc5 = Location::new("loc5");
1001
1002 let var1 = Variable::new("var1");
1003 let var2 = Variable::new("var2");
1004
1005 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1006 .with_guard(BooleanVarConstraint::True)
1007 .build();
1008 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1009 .with_guard(BooleanVarConstraint::True)
1010 .build();
1011 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1012 .with_guard(BooleanVarConstraint::True)
1013 .build();
1014 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1015 .with_guard(BooleanVarConstraint::True)
1016 .build();
1017
1018 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1019 .with_variables([var1, var2])
1020 .unwrap()
1021 .with_locations([
1022 loc1.clone(),
1023 loc2.clone(),
1024 loc3.clone(),
1025 loc4.clone(),
1026 loc5.clone(),
1027 ])
1028 .unwrap()
1029 .initialize()
1030 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1031 .unwrap()
1032 .with_initial_location_constraints([
1033 BooleanExpression::ComparisonExpression(
1034 Box::new(IntegerExpression::Atom(loc2.clone())),
1035 ComparisonOp::Eq,
1036 Box::new(IntegerExpression::Const(0)),
1037 ),
1038 BooleanExpression::ComparisonExpression(
1039 Box::new(IntegerExpression::Atom(loc3.clone())),
1040 ComparisonOp::Eq,
1041 Box::new(IntegerExpression::Const(0)),
1042 ),
1043 BooleanExpression::ComparisonExpression(
1044 Box::new(IntegerExpression::Atom(loc4.clone())),
1045 ComparisonOp::Eq,
1046 Box::new(IntegerExpression::Const(0)),
1047 ),
1048 ])
1049 .unwrap()
1050 .build();
1051
1052 DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1053
1054 assert_eq!(ta.locations().count(), 4);
1055 assert!(ta.locations().any(|l| *l == loc1));
1056 assert!(ta.locations().any(|l| *l == loc2));
1057 assert!(ta.locations().any(|l| *l == loc3));
1058 assert!(!ta.locations().any(|l| *l == loc4));
1059 assert!(ta.locations().any(|l| *l == loc5));
1060
1061 assert_eq!(ta.rules().count(), 3);
1062 assert!(ta.rules().any(|r| r0 == *r));
1063 assert!(ta.rules().any(|r| r1 == *r));
1064 assert!(ta.rules().any(|r| r2 == *r));
1065 assert!(!ta.rules().any(|r| r3 == *r));
1066 }
1067
1068 #[test]
1069 fn test_drop_unreachable_locations_and_modify_init_constraints() {
1070 let loc1 = Location::new("loc1");
1071 let loc2 = Location::new("loc2");
1072 let loc3 = Location::new("loc3");
1073 let loc4 = Location::new("loc4");
1074 let loc5 = Location::new("loc5");
1075
1076 let var1 = Variable::new("var1");
1077 let var2 = Variable::new("var2");
1078
1079 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1080 .with_guard(BooleanVarConstraint::True)
1081 .build();
1082 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1083 .with_guard(BooleanVarConstraint::True)
1084 .build();
1085 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1086 .with_guard(BooleanVarConstraint::True)
1087 .build();
1088 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1089 .with_guard(BooleanVarConstraint::True)
1090 .build();
1091
1092 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1093 .with_variables([var1, var2])
1094 .unwrap()
1095 .with_locations([
1096 loc1.clone(),
1097 loc2.clone(),
1098 loc3.clone(),
1099 loc4.clone(),
1100 loc5.clone(),
1101 ])
1102 .unwrap()
1103 .initialize()
1104 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1105 .unwrap()
1106 .with_initial_location_constraints([
1107 BooleanExpression::ComparisonExpression(
1108 Box::new(IntegerExpression::Atom(loc2.clone())),
1109 ComparisonOp::Eq,
1110 Box::new(IntegerExpression::Const(0)),
1111 ),
1112 BooleanExpression::ComparisonExpression(
1113 Box::new(IntegerExpression::Atom(loc3.clone())),
1114 ComparisonOp::Eq,
1115 Box::new(IntegerExpression::Const(0)),
1116 ),
1117 BooleanExpression::ComparisonExpression(
1118 Box::new(IntegerExpression::Atom(loc4.clone())),
1119 ComparisonOp::Eq,
1120 Box::new(IntegerExpression::Const(0)),
1121 ),
1122 ])
1123 .unwrap()
1124 .build();
1125
1126 DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1127
1128 assert_eq!(ta.locations().count(), 4);
1129 assert!(ta.locations().any(|l| *l == loc1));
1130 assert!(ta.locations().any(|l| *l == loc2));
1131 assert!(ta.locations().any(|l| *l == loc3));
1132 assert!(!ta.locations().any(|l| *l == loc4));
1133 assert!(ta.locations().any(|l| *l == loc5));
1134
1135 assert_eq!(ta.rules().count(), 3);
1136 assert!(ta.rules().any(|r| r0 == *r));
1137 assert!(ta.rules().any(|r| r1 == *r));
1138 assert!(ta.rules().any(|r| r2 == *r));
1139 assert!(!ta.rules().any(|r| r3 == *r));
1140
1141 assert!(ta.initial_location_conditions().any(|l| l
1142 == &BooleanExpression::ComparisonExpression(
1143 Box::new(IntegerExpression::Atom(loc2.clone())),
1144 ComparisonOp::Eq,
1145 Box::new(IntegerExpression::Const(0)),
1146 )));
1147 assert!(ta.initial_location_conditions().any(|l| l
1148 == &BooleanExpression::ComparisonExpression(
1149 Box::new(IntegerExpression::Atom(loc3.clone())),
1150 ComparisonOp::Eq,
1151 Box::new(IntegerExpression::Const(0)),
1152 )));
1153 assert!(!ta.initial_location_conditions().any(|l| l
1154 == &BooleanExpression::ComparisonExpression(
1155 Box::new(IntegerExpression::Atom(loc4.clone())),
1156 ComparisonOp::Eq,
1157 Box::new(IntegerExpression::Const(0)),
1158 )));
1159 }
1160
1161 #[test]
1162 fn test_combine_trivial_guards_static() {
1163 let loc1 = Location::new("loc1");
1164 let loc2 = Location::new("loc2");
1165 let loc3 = Location::new("loc3");
1166 let loc4 = Location::new("loc4");
1167 let loc5 = Location::new("loc5");
1168
1169 let var1 = Variable::new("var1");
1170 let var2 = Variable::new("var2");
1171
1172 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1173 .with_guard(BooleanVarConstraint::ComparisonExpression(
1174 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1175 ComparisonOp::Geq,
1176 Box::new(IntegerExpression::Const(0)),
1177 ))
1178 .build();
1179 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1180 .with_guard(BooleanVarConstraint::BinaryExpression(
1181 Box::new(BooleanVarConstraint::ComparisonExpression(
1182 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1183 ComparisonOp::Geq,
1184 Box::new(IntegerExpression::Const(1)),
1185 )),
1186 BooleanConnective::Or,
1187 Box::new(BooleanVarConstraint::ComparisonExpression(
1188 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1189 ComparisonOp::Eq,
1190 Box::new(IntegerExpression::Const(0)),
1191 )),
1192 ))
1193 .build();
1194 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1195 .with_guard(BooleanVarConstraint::BinaryExpression(
1196 Box::new(BooleanVarConstraint::ComparisonExpression(
1197 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1198 ComparisonOp::Neq,
1199 Box::new(IntegerExpression::Const(5)),
1200 )),
1201 BooleanConnective::Or,
1202 Box::new(BooleanVarConstraint::ComparisonExpression(
1203 Box::new(IntegerExpression::Const(6)),
1204 ComparisonOp::Neq,
1205 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1206 )),
1207 ))
1208 .build();
1209 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1210 .with_guard(BooleanVarConstraint::True)
1211 .build();
1212
1213 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1214 .with_variables([var1, var2])
1215 .unwrap()
1216 .with_locations([
1217 loc1.clone(),
1218 loc2.clone(),
1219 loc3.clone(),
1220 loc4.clone(),
1221 loc5.clone(),
1222 ])
1223 .unwrap()
1224 .initialize()
1225 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1226 .unwrap()
1227 .with_initial_location_constraints([
1228 BooleanExpression::ComparisonExpression(
1229 Box::new(IntegerExpression::Atom(loc2.clone())),
1230 ComparisonOp::Eq,
1231 Box::new(IntegerExpression::Const(0)),
1232 ),
1233 BooleanExpression::ComparisonExpression(
1234 Box::new(IntegerExpression::Atom(loc3.clone())),
1235 ComparisonOp::Eq,
1236 Box::new(IntegerExpression::Const(0)),
1237 ),
1238 BooleanExpression::ComparisonExpression(
1239 Box::new(IntegerExpression::Atom(loc4.clone())),
1240 ComparisonOp::Eq,
1241 Box::new(IntegerExpression::Const(0)),
1242 ),
1243 ])
1244 .unwrap()
1245 .build();
1246
1247 ReplaceTrivialGuardsStatic::new().process(
1248 &mut ta,
1249 &DummySpec::new(),
1250 &SMTSolverBuilder::default(),
1251 );
1252
1253 assert_eq!(ta.rules().count(), 4);
1254 assert!(!ta.rules().any(|r| r0 == *r));
1255 assert!(!ta.rules().any(|r| r1 == *r));
1256 assert!(ta.rules().any(|r| r2 == *r));
1257 assert!(ta.rules().any(|r| r3 == *r));
1258
1259 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1260 .with_guard(BooleanVarConstraint::True)
1261 .build();
1262 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1263 .with_guard(BooleanVarConstraint::True)
1264 .build();
1265
1266 assert!(ta.rules().any(|r| r0 == *r));
1267 assert!(ta.rules().any(|r| r1 == *r));
1268 }
1269
1270 #[test]
1271 fn test_combine_trivial_guards_smt() {
1272 let loc1 = Location::new("loc1");
1273 let loc2 = Location::new("loc2");
1274 let loc3 = Location::new("loc3");
1275 let loc4 = Location::new("loc4");
1276 let loc5 = Location::new("loc5");
1277
1278 let var1 = Variable::new("var1");
1279 let var2 = Variable::new("var2");
1280
1281 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1282 .with_guard(BooleanVarConstraint::ComparisonExpression(
1283 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1284 ComparisonOp::Geq,
1285 Box::new(IntegerExpression::Const(0)),
1286 ))
1287 .build();
1288 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1289 .with_guard(BooleanVarConstraint::BinaryExpression(
1290 Box::new(BooleanVarConstraint::ComparisonExpression(
1291 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1292 ComparisonOp::Geq,
1293 Box::new(IntegerExpression::Const(5)),
1294 )),
1295 BooleanConnective::Or,
1296 Box::new(BooleanVarConstraint::ComparisonExpression(
1297 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1298 ComparisonOp::Lt,
1299 Box::new(IntegerExpression::Const(5)),
1300 )),
1301 ))
1302 .build();
1303 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1304 .with_guard(
1305 BooleanVarConstraint::ComparisonExpression(
1306 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1307 ComparisonOp::Geq,
1308 Box::new(IntegerExpression::Const(0)),
1309 ) & BooleanVarConstraint::ComparisonExpression(
1310 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1311 ComparisonOp::Lt,
1312 Box::new(IntegerExpression::Const(42)),
1313 ),
1314 )
1315 .build();
1316 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1317 .with_guard(BooleanVarConstraint::True)
1318 .build();
1319
1320 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1321 .with_variables([var1, var2])
1322 .unwrap()
1323 .with_locations([
1324 loc1.clone(),
1325 loc2.clone(),
1326 loc3.clone(),
1327 loc4.clone(),
1328 loc5.clone(),
1329 ])
1330 .unwrap()
1331 .with_parameter(Parameter::new("n"))
1332 .unwrap()
1333 .initialize()
1334 .with_resilience_condition(BooleanExpression::ComparisonExpression(
1335 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1336 ComparisonOp::Geq,
1337 Box::new(IntegerExpression::Const(1)),
1338 ))
1339 .unwrap()
1340 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1341 .unwrap()
1342 .with_initial_location_constraints([
1343 BooleanExpression::ComparisonExpression(
1344 Box::new(IntegerExpression::Atom(loc2.clone())),
1345 ComparisonOp::Eq,
1346 Box::new(IntegerExpression::Const(0)),
1347 ),
1348 BooleanExpression::ComparisonExpression(
1349 Box::new(IntegerExpression::Atom(loc3.clone())),
1350 ComparisonOp::Eq,
1351 Box::new(IntegerExpression::Const(0)),
1352 ),
1353 BooleanExpression::ComparisonExpression(
1354 Box::new(IntegerExpression::Atom(loc4.clone())),
1355 ComparisonOp::Eq,
1356 Box::new(IntegerExpression::Const(0)),
1357 ),
1358 ])
1359 .unwrap()
1360 .build();
1361
1362 ReplaceTrivialGuardsSMT::new().process(
1363 &mut ta,
1364 &DummySpec::new(),
1365 &SMTSolverBuilder::default(),
1366 );
1367
1368 assert_eq!(ta.rules().count(), 4);
1369 assert!(!ta.rules().any(|r| r0 == *r));
1370 assert!(!ta.rules().any(|r| r1 == *r));
1371
1372 assert!(ta.rules().any(|r| r3 == *r));
1373
1374 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1375 .with_guard(BooleanVarConstraint::True)
1376 .build();
1377 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1378 .with_guard(BooleanVarConstraint::True)
1379 .build();
1380 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1381 .with_guard(
1382 BooleanExpression::True
1383 & BooleanVarConstraint::ComparisonExpression(
1384 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1385 ComparisonOp::Lt,
1386 Box::new(IntegerExpression::Const(42)),
1387 ),
1388 )
1389 .build();
1390
1391 assert!(ta.rules().any(|r| r0 == *r));
1392 assert!(ta.rules().any(|r| r1 == *r));
1393 assert!(ta.rules().any(|r| r2 == *r));
1394 }
1395
1396 #[test]
1397 fn test_remove_unused_variables() {
1398 let loc1 = Location::new("loc1");
1399 let loc2 = Location::new("loc2");
1400 let loc3 = Location::new("loc3");
1401 let loc4 = Location::new("loc4");
1402 let loc5 = Location::new("loc5");
1403
1404 let var1 = Variable::new("var1");
1405 let var2 = Variable::new("var2");
1406 let var3 = Variable::new("var3");
1407 let var4 = Variable::new("var4");
1408
1409 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1410 .with_guard(BooleanVarConstraint::True)
1411 .build();
1412 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1413 .with_guard(BooleanVarConstraint::True)
1414 .with_action(Action::new_reset(var1.clone()))
1415 .build();
1416 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1417 .with_guard(BooleanVarConstraint::ComparisonExpression(
1418 Box::new(IntegerExpression::Atom(var3.clone())),
1419 ComparisonOp::Geq,
1420 Box::new(IntegerExpression::Const(0)),
1421 ))
1422 .build();
1423 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1424 .with_guard(BooleanVarConstraint::True)
1425 .build();
1426
1427 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1428 .with_variables([var1.clone(), var2.clone(), var3.clone(), var4.clone()])
1429 .unwrap()
1430 .with_locations([
1431 loc1.clone(),
1432 loc2.clone(),
1433 loc3.clone(),
1434 loc4.clone(),
1435 loc5.clone(),
1436 ])
1437 .unwrap()
1438 .initialize()
1439 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1440 .unwrap()
1441 .with_initial_variable_constraints([
1442 BooleanExpression::ComparisonExpression(
1443 Box::new(IntegerExpression::Atom(var1.clone())),
1444 ComparisonOp::Eq,
1445 Box::new(IntegerExpression::Const(0)),
1446 ),
1447 BooleanExpression::ComparisonExpression(
1448 Box::new(IntegerExpression::Atom(var1.clone())),
1449 ComparisonOp::Eq,
1450 Box::new(IntegerExpression::Const(0)),
1451 ),
1452 BooleanExpression::ComparisonExpression(
1453 Box::new(IntegerExpression::Atom(var4.clone())),
1454 ComparisonOp::Gt,
1455 Box::new(IntegerExpression::Const(0)),
1456 ),
1457 ])
1458 .unwrap()
1459 .build();
1460
1461 RemoveUnusedVariables {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1462
1463 assert_eq!(ta.variables().count(), 2);
1464 assert!(!ta.variables().any(|v| *v == var1));
1465 assert!(!ta.variables().any(|v| *v == var2));
1466 assert!(ta.variables().any(|v| *v == var3));
1467 assert!(ta.variables().any(|v| *v == var4));
1468 }
1469
1470 #[test]
1471 fn test_init_cond_sat() {
1472 let loc1 = Location::new("loc1");
1473 let loc2 = Location::new("loc2");
1474 let loc3 = Location::new("loc3");
1475 let loc4 = Location::new("loc4");
1476 let loc5 = Location::new("loc5");
1477
1478 let var1 = Variable::new("var1");
1479 let var2 = Variable::new("var2");
1480
1481 let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1482 .with_guard(BooleanVarConstraint::True)
1483 .build();
1484 let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1485 .with_guard(BooleanVarConstraint::True)
1486 .build();
1487 let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1488 .with_guard(BooleanVarConstraint::True)
1489 .build();
1490 let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1491 .with_guard(BooleanVarConstraint::True)
1492 .build();
1493
1494 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1495 .with_variables([var1, var2])
1496 .unwrap()
1497 .with_locations([
1498 loc1.clone(),
1499 loc2.clone(),
1500 loc3.clone(),
1501 loc4.clone(),
1502 loc5.clone(),
1503 ])
1504 .unwrap()
1505 .initialize()
1506 .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1507 .unwrap()
1508 .with_initial_location_constraints([
1509 BooleanExpression::ComparisonExpression(
1510 Box::new(IntegerExpression::Atom(loc2.clone())),
1511 ComparisonOp::Eq,
1512 Box::new(IntegerExpression::Const(0)),
1513 ),
1514 BooleanExpression::ComparisonExpression(
1515 Box::new(IntegerExpression::Atom(loc2.clone())),
1516 ComparisonOp::Eq,
1517 Box::new(IntegerExpression::Const(1)),
1518 ),
1519 BooleanExpression::ComparisonExpression(
1520 Box::new(IntegerExpression::Atom(loc3.clone())),
1521 ComparisonOp::Eq,
1522 Box::new(IntegerExpression::Const(0)),
1523 ),
1524 BooleanExpression::ComparisonExpression(
1525 Box::new(IntegerExpression::Atom(loc4.clone())),
1526 ComparisonOp::Eq,
1527 Box::new(IntegerExpression::Const(0)),
1528 ),
1529 ])
1530 .unwrap()
1531 .build();
1532
1533 CheckInitCondSatSMT::default().process(
1534 &mut ta,
1535 &DummySpec::new(),
1536 &SMTSolverBuilder::default(),
1537 );
1538
1539 assert_eq!(ta.rules().count(), 0);
1540 assert!(
1541 ta.initial_variable_conditions()
1542 .any(|c| c == &BooleanExpression::False)
1543 );
1544 assert!(
1545 ta.initial_location_conditions()
1546 .any(|c| c == &BooleanExpression::False)
1547 )
1548 }
1549
1550 #[test]
1551 fn test_collapse_locations_no_spec() {
1552 let loc1 = Location::new("loc1");
1553 let loc2 = Location::new("loc2");
1554 let loc3 = Location::new("loc3");
1555 let loc4 = Location::new("loc4");
1556 let loc5 = Location::new("loc5");
1557 let loc6 = Location::new("loc6");
1558 let loc7 = Location::new("loc7");
1559
1560 let var1 = Variable::new("var1");
1561 let var2 = Variable::new("var2");
1562
1563 let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1564 .with_guard(BooleanVarConstraint::True)
1565 .build();
1566 let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1567 .with_guard(BooleanVarConstraint::True)
1568 .build();
1569 let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1570 .with_guard(BooleanVarConstraint::True)
1571 .build();
1572 let r3 = RuleBuilder::new(3, loc1.clone(), loc5.clone())
1573 .with_guard(BooleanVarConstraint::True)
1574 .build();
1575 let r4 = RuleBuilder::new(4, loc2.clone(), loc6.clone())
1576 .with_guard(BooleanVarConstraint::ComparisonExpression(
1577 Box::new(IntegerExpression::Atom(var2.clone())),
1578 ComparisonOp::Geq,
1579 Box::new(IntegerExpression::Const(0)),
1580 ))
1581 .build();
1582 let r5 = RuleBuilder::new(5, loc3.clone(), loc6.clone())
1583 .with_guard(BooleanVarConstraint::ComparisonExpression(
1584 Box::new(IntegerExpression::Atom(var1.clone())),
1585 ComparisonOp::Geq,
1586 Box::new(IntegerExpression::Const(0)),
1587 ))
1588 .build();
1589 let r6 = RuleBuilder::new(6, loc4.clone(), loc6.clone())
1590 .with_guard(BooleanVarConstraint::ComparisonExpression(
1591 Box::new(IntegerExpression::Atom(var1.clone())),
1592 ComparisonOp::Geq,
1593 Box::new(IntegerExpression::Const(0)),
1594 ))
1595 .build();
1596 let r7 = RuleBuilder::new(7, loc5.clone(), loc7.clone())
1597 .with_guard(BooleanVarConstraint::ComparisonExpression(
1598 Box::new(IntegerExpression::Atom(var1.clone())),
1599 ComparisonOp::Geq,
1600 Box::new(IntegerExpression::Const(0)),
1601 ))
1602 .build();
1603
1604 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1605 .with_variables([var1.clone(), var2.clone()])
1606 .unwrap()
1607 .with_locations([
1608 loc1.clone(),
1609 loc2.clone(),
1610 loc3.clone(),
1611 loc4.clone(),
1612 loc5.clone(),
1613 loc6.clone(),
1614 loc7.clone(),
1615 ])
1616 .unwrap()
1617 .initialize()
1618 .with_rules([
1619 r0.clone(),
1620 r1.clone(),
1621 r2.clone(),
1622 r3.clone(),
1623 r4.clone(),
1624 r5.clone(),
1625 r6.clone(),
1626 r7.clone(),
1627 ])
1628 .unwrap()
1629 .with_initial_location_constraints([
1630 BooleanExpression::ComparisonExpression(
1631 Box::new(IntegerExpression::Atom(loc2.clone())),
1632 ComparisonOp::Eq,
1633 Box::new(IntegerExpression::Const(0)),
1634 ),
1635 BooleanExpression::ComparisonExpression(
1636 Box::new(IntegerExpression::Atom(loc3.clone())),
1637 ComparisonOp::Eq,
1638 Box::new(IntegerExpression::Const(0)),
1639 ),
1640 BooleanExpression::ComparisonExpression(
1641 Box::new(IntegerExpression::Atom(loc4.clone())),
1642 ComparisonOp::Eq,
1643 Box::new(IntegerExpression::Const(0)),
1644 ),
1645 BooleanExpression::ComparisonExpression(
1646 Box::new(IntegerExpression::Atom(loc5.clone())),
1647 ComparisonOp::Eq,
1648 Box::new(IntegerExpression::Const(0)),
1649 ),
1650 BooleanExpression::ComparisonExpression(
1651 Box::new(IntegerExpression::Atom(loc6.clone())),
1652 ComparisonOp::Eq,
1653 Box::new(IntegerExpression::Const(0)),
1654 ),
1655 BooleanExpression::ComparisonExpression(
1656 Box::new(IntegerExpression::Atom(loc7.clone())),
1657 ComparisonOp::Eq,
1658 Box::new(IntegerExpression::Const(0)),
1659 ),
1660 ])
1661 .unwrap()
1662 .build();
1663
1664 CollapseLocations {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1665
1666 assert_eq!(ta.locations().count(), 4);
1667
1668 assert_eq!(ta.rules().count(), 6);
1669 }
1670
1671 #[test]
1672 fn test_collapse_locations_with_spec() {
1673 let loc1 = Location::new("loc1");
1674 let loc2 = Location::new("loc2");
1675 let loc3 = Location::new("loc3");
1676 let loc4 = Location::new("loc4");
1677
1678 let var1 = Variable::new("var1");
1679
1680 let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1681 .with_guard(BooleanVarConstraint::ComparisonExpression(
1682 Box::new(IntegerExpression::Atom(var1.clone())),
1683 ComparisonOp::Geq,
1684 Box::new(IntegerExpression::Const(0)),
1685 ))
1686 .build();
1687 let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1688 .with_guard(BooleanVarConstraint::ComparisonExpression(
1689 Box::new(IntegerExpression::Atom(var1.clone())),
1690 ComparisonOp::Geq,
1691 Box::new(IntegerExpression::Const(0)),
1692 ))
1693 .build();
1694 let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1695 .with_guard(BooleanVarConstraint::ComparisonExpression(
1696 Box::new(IntegerExpression::Atom(var1.clone())),
1697 ComparisonOp::Geq,
1698 Box::new(IntegerExpression::Const(0)),
1699 ))
1700 .build();
1701
1702 let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1703 .with_variables([var1.clone()])
1704 .unwrap()
1705 .with_locations([loc1.clone(), loc2.clone(), loc3.clone(), loc4.clone()])
1706 .unwrap()
1707 .initialize()
1708 .with_rules([r0.clone(), r1.clone(), r2.clone()])
1709 .unwrap()
1710 .with_initial_location_constraints([
1711 BooleanExpression::ComparisonExpression(
1712 Box::new(IntegerExpression::Atom(loc2.clone())),
1713 ComparisonOp::Eq,
1714 Box::new(IntegerExpression::Const(0)),
1715 ),
1716 BooleanExpression::ComparisonExpression(
1717 Box::new(IntegerExpression::Atom(loc3.clone())),
1718 ComparisonOp::Eq,
1719 Box::new(IntegerExpression::Const(0)),
1720 ),
1721 BooleanExpression::ComparisonExpression(
1722 Box::new(IntegerExpression::Atom(loc4.clone())),
1723 ComparisonOp::Eq,
1724 Box::new(IntegerExpression::Const(0)),
1725 ),
1726 ])
1727 .unwrap()
1728 .build();
1729
1730 let spec = DisjunctionTargetConfig::new_from_targets(
1731 "test".into(),
1732 [TargetConfig::new_cover([loc2.clone()]).unwrap()],
1733 );
1734
1735 CollapseLocations {}.process(&mut ta, &spec, &DummyContext {});
1736
1737 assert_eq!(ta.locations().count(), 3);
1738
1739 assert_eq!(ta.rules().count(), 3);
1740 }
1741
1742 #[test]
1743 fn test_display_available_preprocessor() {
1744 assert_eq!(
1745 format!("{}", ExistingPreprocessors::DropSelfLoops),
1746 "DropSelfLoops"
1747 );
1748 assert_eq!(
1749 format!("{}", ExistingPreprocessors::DropUnreachableLocations),
1750 "DropUnreachableLocations"
1751 );
1752 assert_eq!(
1753 format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsStatic),
1754 "ReplaceTrivialGuardsStatic"
1755 );
1756 assert_eq!(
1757 format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsSMT),
1758 "ReplaceTrivialGuardsSMT"
1759 );
1760 assert_eq!(
1761 format!("{}", ExistingPreprocessors::RemoveUnusedVariables),
1762 "RemoveUnusedVariables"
1763 );
1764 assert_eq!(
1765 format!("{}", ExistingPreprocessors::DropUnsatisfiableRules),
1766 "DropUnsatisfiableRules"
1767 );
1768 assert_eq!(
1769 format!("{}", ExistingPreprocessors::CollapseLocations),
1770 "CollapseLocations"
1771 );
1772 }
1773}