taco_model_checker/
preprocessing.rs

1//! Preprocessing for threshold automata
2//!
3//! This module contains the logic for preprocessing threshold automata.
4//! The goal of this preprocessing is to simplify the threshold automaton, such
5//! that the threshold automaton is easier to analyze.
6//!
7//! The following preprocessors are implemented:
8//! - [DropSelfLoops]: Removes self loops from the threshold automaton if the
9//!   transition rule has no effect on the shared variables.
10//! - [DropUnreachableLocations]: Removes unreachable locations from the
11//!   threshold automaton.
12//! - [ReplaceTrivialGuardsStatic]: Replaces some frequently generated guards
13//!   that are always enabled with true.
14//! - [ReplaceTrivialGuardsSMT]: Replaces guards which are always satisfied, uses
15//!   SMT to determine which guards are always satisfied.
16//! - [CollapseLocations]: Collapse locations which have the same incoming rules
17//! - [DropUnsatisfiableRules]: Removes rules where the guards are
18//!   unsatisfiable.
19//! - [CheckInitCondSatSMT]: Checks whether the initial condition of a threshold
20//!   automaton are satisfiable, if not, removes all rules, locations and adds
21//!   false to the initial locations.
22
23use 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
52/// Trait for preprocessing threshold automata
53///
54/// This trait is implemented by types that can process a threshold automaton
55/// to simplify it for further analysis.
56pub trait Preprocessor<
57    T: ModifiableThresholdAutomaton + ThresholdAutomaton,
58    S: TargetSpec,
59    C: ModelCheckerContext,
60>
61{
62    /// Process the threshold automaton and attempt to simplify it
63    fn process(&self, ta: &mut T, spec: &S, ctx: &C);
64}
65
66/// Preprocessor that drops self loops from the threshold automaton if the
67/// transition rule has no effect on the shared variables
68///
69/// This threshold automaton preprocessor removes self loops from the threshold
70/// automaton if the transition rule has no effect on the shared variables.
71///
72/// Such a self loop does not change the location of any process, nor does it
73/// change the valuations of any shared variables. Therefore, it can be safely
74/// removed from the threshold automaton.
75pub struct DropSelfLoops {}
76
77impl Default for DropSelfLoops {
78    fn default() -> Self {
79        Self::new()
80    }
81}
82
83impl DropSelfLoops {
84    /// Create a new instance of the `DropSelfLoop` preprocessor
85    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/// Preprocessor that drops unreachable locations from the threshold automaton
110///
111/// This preprocessor constructs the underlying graph of the threshold automaton
112/// without considering the guards of the transition rules.
113///
114/// It then performs a reachability analysis to determine the reachable
115/// locations. If a location is not reachable, i.e. it is not reachable through
116/// any transition and not initial, it is removed from the threshold automaton.
117/// If there are any outgoing rules, they are also removed from the threshold
118/// automaton.
119///
120/// This step should generally be applied, when rules have been removed from the
121/// threshold automaton, or if locations are no longer considered to be initial.
122#[derive(Default)]
123pub struct DropUnreachableLocations {}
124
125impl DropUnreachableLocations {
126    /// Create a new instance of the `DropUnreachableLocations` preprocessor
127    pub fn new() -> Self {
128        Self {}
129    }
130
131    /// Compute the set of unreachable locations in the threshold automaton
132    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    /// Preprocessor that drops unreachable locations from the threshold automaton
170    ///
171    /// This preprocessing step constructs the graph of the threshold automaton
172    /// without considering the guards of the transition rules.
173    ///
174    /// It then performs a reachability analysis to determine the reachable
175    /// locations. If a location is not reachable, it is removed from the threshold
176    /// automaton.
177    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
204/// A lot of automatically generated benchmarks have guards of the form
205/// `(var1 >= 1 || var1 == 0)`  or `var >= 0`  which are satisfied. This
206/// preprocessor replaces these guards with `true`.
207///
208/// This preprocessor only checks for a few trivial patterns, if you want a
209/// more general preprocessor use [`ReplaceTrivialGuardsSMT`].
210///
211/// **Note**: If you assume variables can have values smaller than 0 this
212/// preprocessor should not be used!
213pub struct ReplaceTrivialGuardsStatic {}
214
215impl Default for ReplaceTrivialGuardsStatic {
216    fn default() -> Self {
217        Self::new()
218    }
219}
220
221impl ReplaceTrivialGuardsStatic {
222    ///? Can we generalize this to more expressions ?
223    fn is_trivially_true<T: Atomic>(expr: &BooleanExpression<T>) -> bool {
224        match expr {
225            // var == 0 || var >= 1
226            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            // var >= 0
247            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    /// Replace trivial boolean expressions with `true`
259    ///
260    /// **Note**: If you assume variables can have values smaller than 0 this
261    /// preprocessor should not be used!
262    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    /// Create a new instance of the [`ReplaceTrivialGuardsStatic`] preprocessor
281    pub fn new() -> Self {
282        Self {}
283    }
284}
285
286impl<S: TargetSpec, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
287    for ReplaceTrivialGuardsStatic
288{
289    /// A lot of automatically generated benchmarks have guards of the form
290    /// `(var1 >= 1 || var1 == 0)`  or `var >= 0`  which are satisfied. This
291    /// preprocessor replaces these guards with `true`.
292    ///
293    /// This preprocessor only checks for a few trivial patterns, if you want a
294    /// more general preprocessor use [`ReplaceTrivialGuardsSMT`].
295    ///
296    /// **Note**: If you assume variables can have values smaller than 0 this
297    /// preprocessor should not be used!
298    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
305/// Preprocessor that checks for trivial guards using an SMT solver
306///
307/// This preprocessor replaces guards that are always satisfied with `true`. To
308/// do this, it uses an SMT solver to check whether the negation of the guard
309/// can be satisfied. If this is not possible, the guard is replaced with
310/// `true`.
311///
312/// This preprocessor can be very useful for automatically generated benchmarks
313/// where guards are often not meaningful. However, these checks can be very
314/// expensive. A more light weight preprocessor is
315/// [`ReplaceTrivialGuardsStatic`], but it is not complete.
316///
317/// **Note**: If you assume variables can have values smaller than 0 this
318/// preprocessor should not be used!
319pub struct ReplaceTrivialGuardsSMT {}
320
321impl Default for ReplaceTrivialGuardsSMT {
322    fn default() -> Self {
323        Self::new()
324    }
325}
326
327impl ReplaceTrivialGuardsSMT {
328    /// Create a new instance of the preprocessor [`ReplaceTrivialGuardsSMT`]
329    pub fn new() -> Self {
330        Self {}
331    }
332
333    /// Checks if a boolean expression is trivially true using an smt solver
334    ///
335    /// Returns true if the expression is trivially true, false otherwise.
336    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    /// Check if a boolean expression is trivially true using an SMT solver and
357    /// store the result in a cache
358    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    /// Replace trivially true boolean expressions with `true`
373    ///
374    /// This function checks if the expression is trivially true using an SMT
375    /// solver. If it is, the expression is replaced with `true`.
376    ///
377    /// This function also keeps track of known tautologies and guards to avoid
378    /// repeated checks. These are stored in the `known_tautologies` and
379    /// `known_guards` sets respectively.
380    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        // recursively check the subexpressions
396        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
457/// Preprocessor that removes unused variables from the threshold automaton
458pub struct RemoveUnusedVariables {}
459
460impl Default for RemoveUnusedVariables {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl RemoveUnusedVariables {
467    /// Create a new instance of the preprocessor
468    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
516/// Preprocessor that checks whether the initial conditions of threshold
517/// automaton are satisfiable at all.
518/// If this is not the case, replaces them with false and deletes all rules
519pub struct CheckInitCondSatSMT {}
520
521impl Default for CheckInitCondSatSMT {
522    fn default() -> Self {
523        Self::new()
524    }
525}
526
527impl CheckInitCondSatSMT {
528    /// Create a new instance of the [CheckInitCondSatSMT] preprocessor
529    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
563/// Preprocessor that drops rules where the guards are unsatisfiable
564///
565/// This preprocessor encodes the resilience condition of the threshold
566/// automaton into an SMT solver and checks whether rule guards are satisfiable.
567///
568/// If a rule guard is unsatisfiable, the rule is removed from the threshold
569/// automaton.
570///
571/// This preprocessing step is useful for machine generated threshold automata
572/// or if the resilience condition is complex. As it uses an SMT solver, it can
573/// be very expensive.
574pub struct DropUnsatisfiableRules {}
575
576impl Default for DropUnsatisfiableRules {
577    fn default() -> Self {
578        Self::new()
579    }
580}
581
582impl DropUnsatisfiableRules {
583    /// Create a new instance of the preprocessor
584    pub fn new() -> Self {
585        Self {}
586    }
587
588    /// Check whether the guard of the rule is satisfiable
589    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                // push new frame
617                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                // pop the frame
632                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
669/// Preprocessor that collapses locations that have the same outgoing transitions and are not mentioned in the specification into one location
670///
671/// This preprocessor checks whether there exists locations that can be collapsed together and handles the collapsing in that case.
672///
673/// Locations are collapsable if they have the same outgoing transitions and are not mentioned in the specification.
674pub struct CollapseLocations {}
675
676impl Default for CollapseLocations {
677    fn default() -> Self {
678        Self::new()
679    }
680}
681
682impl CollapseLocations {
683    /// Create a new instance of the `CollapsableLocations` preprocessor
684    pub fn new() -> Self {
685        Self {}
686    }
687
688    /// Return all the different sets of collapsable locations
689    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    /// Return true if the two locations have the same outgoing transitions
720    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/// Enum representing all available preprocessors
788///
789/// This enum is used to configure what preprocessors can be used in TACO
790#[derive(Debug, Clone, PartialEq)]
791#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
792pub enum ExistingPreprocessors {
793    /// Removes self loops from the threshold automaton if the transition rule
794    /// has no effect on the shared variables.
795    DropSelfLoops,
796    /// Removes unreachable locations from the threshold automaton by
797    /// performing a graph search
798    DropUnreachableLocations,
799    /// Replaces some frequently generated guards that are always enabled with
800    /// true
801    ReplaceTrivialGuardsStatic,
802    /// Replaces guards which are always satisfied, uses SMT to determine
803    /// which guards are always satisfied.
804    ReplaceTrivialGuardsSMT,
805    /// Remove variables that do not appear in any guard
806    RemoveUnusedVariables,
807    /// Remove Rules for which a guard can never be satisfied
808    DropUnsatisfiableRules,
809    /// Collapse locations which have the same incoming rules
810    CollapseLocations,
811    /// Checks whether the initial location of the threshold automaton can be
812    /// satisfied at all
813    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}