Skip to main content

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, TASpecification};
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: TASpecification,
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: TASpecification, C: ModelCheckerContext>
91    Preprocessor<T, S, C> for DropSelfLoops
92{
93    fn process(&self, ta: &mut T, _spec: &S, _ctx: &C) {
94        let n_rules = ta.rules().count();
95
96        ta.retain_rules(|rule| {
97            rule.source() != rule.target() || rule.actions().any(|action| !action.is_unchanged())
98        });
99
100        info!(
101            "Preprocessor 'DropSelfLoops' on '{}' removed {} of {} rules: Removed rules are self-loops that do not update any shared variable.",
102            ta.name(),
103            n_rules - ta.rules().count(),
104            n_rules
105        );
106    }
107}
108
109/// 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: TASpecification, 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.locs_appearing().into_iter().collect();
179
180        let unreachable_locations = self.compute_unreachable_locations(ta, locations_to_keep);
181        if unreachable_locations.is_empty() {
182            debug!("Preprocessor 'DropUnreachableLocations' did not find locations to remove.");
183            return;
184        }
185
186        let n_locations = ta.locations().count();
187        let n_rules = ta.rules().count();
188
189        for loc in unreachable_locations {
190            ta.remove_location_and_replace_with(&loc, IntegerExpression::Const(0));
191        }
192
193        info!(
194            "Preprocessor 'DropUnreachableLocations' on '{}' removed {} of {} locations and {} of {} rules: Removed locations were unreachable in the underlying graph of the threshold automaton.",
195            ta.name(),
196            n_locations - ta.locations().count(),
197            n_locations,
198            n_rules - ta.rules().count(),
199            n_rules
200        );
201    }
202}
203
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: TASpecification, 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: TASpecification, C: ModelCheckerContext + ProvidesSMTSolverBuilder>
412    Preprocessor<GeneralThresholdAutomaton, S, C> for ReplaceTrivialGuardsSMT
413{
414    fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, ctx: &C) {
415        let solver_builder = ctx.get_solver_builder().clone();
416
417        let mut ctx = StaticSMTContext::new(
418            solver_builder,
419            ta.parameters().cloned(),
420            Vec::new(),
421            ta.variables().cloned(),
422        )
423        .expect("Failed to create SMT context for preprocessing");
424
425        let rc = encode_resilience_condition(ta, ctx.get_smt_solver(), &ctx);
426        ctx.get_smt_solver_mut()
427            .assert(rc)
428            .expect("Failed to assert resilience condition");
429
430        ta.variables().for_each(|var| {
431            let solver = ctx.get_smt_solver();
432            let gt_0 = solver.gte(ctx.get_expr_for(var).unwrap(), solver.numeral(0));
433
434            ctx.get_smt_solver_mut()
435                .assert(gt_0)
436                .expect("Failed to assert gt_0");
437        });
438
439        let mut cache = HashMap::new();
440
441        ta.transform_rules(|rule| {
442            rule.transform_guard(|expr| {
443                Self::check_expr_and_replace_tautologies(expr, &mut ctx, &mut cache);
444            });
445        });
446
447        if !cache.is_empty() {
448            info!(
449                "Preprocessor 'ReplaceTrivialGuardsSMT' on '{}' replaced {} trivial guards",
450                ta.name(),
451                cache.iter().filter(|(_, trivial)| **trivial).count(),
452            );
453        }
454    }
455}
456
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: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
491    for RemoveUnusedVariables
492{
493    fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, _ctx: &C) {
494        let unused_variables = Self::get_unused_variables(ta).cloned().collect::<Vec<_>>();
495
496        if unused_variables.is_empty() {
497            return;
498        }
499
500        ta.transform_rules(|rule| {
501            Self::remove_actions_using_variables(rule, &unused_variables);
502        });
503
504        unused_variables.iter().for_each(|var| {
505            ta.remove_variable_and_replace_with(var, IntegerExpression::Const(0));
506        });
507
508        info!(
509            "Preprocessor 'RemoveUnusedVariables' on '{}' removed {} unused variables",
510            ta.name(),
511            unused_variables.len(),
512        );
513    }
514}
515
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: TASpecification, C: ModelCheckerContext + ProvidesSMTSolverBuilder>
535    Preprocessor<GeneralThresholdAutomaton, S, C> for CheckInitCondSatSMT
536{
537    fn process(&self, ta: &mut GeneralThresholdAutomaton, _spec: &S, ctx: &C) {
538        let solver_builder = ctx.get_solver_builder();
539
540        let mut ctx = StaticSMTContext::new(
541            solver_builder,
542            ta.parameters().cloned(),
543            ta.locations().cloned(),
544            ta.variables().cloned(),
545        )
546        .expect("Failed to create SMT context for preprocessing");
547
548        let init_cond = encode_initial_constraints(ta, ctx.get_smt_solver(), &ctx);
549
550        let init_cond_is_satisfiable = ctx
551            .assert_and_check_expr(init_cond)
552            .expect("Failed to encode initial conditions during preprocessing");
553
554        if !init_cond_is_satisfiable.is_sat() {
555            info!("Preprocessor 'CheckInitCondSatSMT' found unsatisfiable case.");
556            ta.retain_rules(|_| false);
557            ta.add_initial_location_constraints([BooleanExpression::False]);
558            ta.add_initial_variable_constraints([BooleanExpression::False]);
559        }
560    }
561}
562
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: TASpecification,
647    C: ModelCheckerContext + ProvidesSMTSolverBuilder,
648> Preprocessor<T, S, C> for DropUnsatisfiableRules
649{
650    fn process(&self, ta: &mut T, _spec: &S, ctx: &C) {
651        let n_rules = ta.rules().count();
652
653        let solver_builder = ctx.get_solver_builder().clone();
654
655        let unsat = self
656            .get_unsatisfiable_rules(solver_builder, ta)
657            .collect::<Vec<_>>();
658        ta.retain_rules(|r| !unsat.contains(r));
659
660        info!(
661            "Preprocessor 'DropUnsatisfiableRules' on '{}' found and removed {} of {} unsatisfiable rules",
662            ta.name(),
663            n_rules - ta.rules().count(),
664            n_rules
665        );
666    }
667}
668
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: TASpecification, C: ModelCheckerContext> Preprocessor<GeneralThresholdAutomaton, S, C>
744    for CollapseLocations
745{
746    fn process(&self, ta: &mut GeneralThresholdAutomaton, spec: &S, _ctx: &C) {
747        let locations_to_keep: HashSet<&Location> = spec.locs_appearing().into_iter().collect();
748
749        let n_locations = ta.locations().count();
750        let n_rules = ta.rules().count();
751
752        loop {
753            let sets = self.compute_collapsable_locations(ta, &locations_to_keep);
754            if sets.is_empty() {
755                break;
756            }
757
758            for collapsable_set in sets {
759                if let Some(first) = collapsable_set.first() {
760                    let kept_location: Location = first.clone();
761                    let collapsable_set = &collapsable_set[1..];
762
763                    ta.retain_rules(|r| !collapsable_set.contains(r.source()));
764
765                    ta.transform_rules(|rule| {
766                        if collapsable_set.contains(rule.target()) {
767                            rule.update_target(kept_location.clone());
768                        }
769                    });
770
771                    for loc in collapsable_set {
772                        ta.remove_location_and_replace_with(loc, IntegerExpression::Const(0));
773                    }
774                }
775            }
776        }
777
778        info!(
779            "Preprocessing removed {} locations, also removing {} rules.",
780            n_locations - ta.locations().count(),
781            n_rules - ta.rules().count(),
782        );
783    }
784}
785
786/// Enum representing all available preprocessors
787///
788/// This enum is used to configure what preprocessors can be used in TACO
789#[derive(Debug, Clone, PartialEq)]
790#[cfg_attr(feature = "config_deserialize", derive(Deserialize))]
791pub enum ExistingPreprocessors {
792    /// Removes self loops from the threshold automaton if the transition rule
793    /// has no effect on the shared variables.
794    DropSelfLoops,
795    /// Removes unreachable locations from the threshold automaton by
796    /// performing a graph search
797    DropUnreachableLocations,
798    /// Replaces some frequently generated guards that are always enabled with
799    /// true
800    ReplaceTrivialGuardsStatic,
801    /// Replaces guards which are always satisfied, uses SMT to determine
802    /// which guards are always satisfied.
803    ReplaceTrivialGuardsSMT,
804    /// Remove variables that do not appear in any guard
805    RemoveUnusedVariables,
806    /// Remove Rules for which a guard can never be satisfied
807    DropUnsatisfiableRules,
808    /// Collapse locations which have the same incoming rules
809    CollapseLocations,
810    /// Checks whether the initial location of the threshold automaton can be
811    /// satisfied at all
812    CheckInitCondSatSMT,
813}
814
815impl<S, C> From<ExistingPreprocessors> for Box<dyn Preprocessor<GeneralThresholdAutomaton, S, C>>
816where
817    S: TASpecification,
818    C: ModelCheckerContext + ProvidesSMTSolverBuilder,
819{
820    fn from(val: ExistingPreprocessors) -> Self {
821        match val {
822            ExistingPreprocessors::DropSelfLoops => Box::new(DropSelfLoops::default()),
823            ExistingPreprocessors::DropUnreachableLocations => {
824                Box::new(DropUnreachableLocations::default())
825            }
826            ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
827                Box::new(ReplaceTrivialGuardsStatic::default())
828            }
829            ExistingPreprocessors::ReplaceTrivialGuardsSMT => {
830                Box::new(ReplaceTrivialGuardsSMT::default())
831            }
832            ExistingPreprocessors::RemoveUnusedVariables => {
833                Box::new(RemoveUnusedVariables::default())
834            }
835            ExistingPreprocessors::DropUnsatisfiableRules => {
836                Box::new(DropUnsatisfiableRules::default())
837            }
838            ExistingPreprocessors::CollapseLocations => Box::new(CollapseLocations::default()),
839            ExistingPreprocessors::CheckInitCondSatSMT => Box::new(CheckInitCondSatSMT::default()),
840        }
841    }
842}
843
844impl fmt::Display for ExistingPreprocessors {
845    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
846        match self {
847            ExistingPreprocessors::DropSelfLoops => write!(f, "DropSelfLoops"),
848            ExistingPreprocessors::DropUnreachableLocations => {
849                write!(f, "DropUnreachableLocations")
850            }
851            ExistingPreprocessors::ReplaceTrivialGuardsStatic => {
852                write!(f, "ReplaceTrivialGuardsStatic")
853            }
854            ExistingPreprocessors::ReplaceTrivialGuardsSMT => write!(f, "ReplaceTrivialGuardsSMT"),
855            ExistingPreprocessors::RemoveUnusedVariables => write!(f, "RemoveUnusedVariables"),
856            ExistingPreprocessors::DropUnsatisfiableRules => write!(f, "DropUnsatisfiableRules"),
857            ExistingPreprocessors::CollapseLocations => write!(f, "CollapseLocations"),
858            ExistingPreprocessors::CheckInitCondSatSMT => write!(f, "CheckInitCondSatSMT"),
859        }
860    }
861}
862
863#[cfg(test)]
864mod test {
865
866    use core::fmt;
867
868    use taco_threshold_automaton::{
869        BooleanVarConstraint,
870        expressions::{
871            BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
872        },
873        general_threshold_automaton::{
874            Action,
875            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
876        },
877    };
878
879    use crate::internal_spec::upwards_closed_set::UpwardsClosedSet;
880
881    use super::*;
882
883    pub struct DummySpec(HashSet<Location>);
884
885    impl DummySpec {
886        fn new() -> Self {
887            Self(HashSet::new())
888        }
889    }
890
891    impl TASpecification for DummySpec {
892        fn locs_appearing(&self) -> impl IntoIterator<Item = &Location> {
893            self.0.iter()
894        }
895
896        fn var_constraint(
897            &self,
898        ) -> impl IntoIterator<
899            Item = &taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint,
900        > {
901            unimplemented!();
902            #[allow(unreachable_code)]
903            Vec::new()
904        }
905    }
906
907    impl fmt::Display for DummySpec {
908        fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
909            unimplemented!()
910        }
911    }
912
913    #[derive(Debug, Clone)]
914    pub struct DummyContext;
915
916    impl ModelCheckerContext for DummyContext {
917        type CreationError = std::convert::Infallible;
918
919        type ContextOptions = ();
920
921        fn try_new(_opt: Option<Self::ContextOptions>) -> Result<Self, Self::CreationError> {
922            unimplemented!()
923        }
924    }
925
926    #[test]
927    fn test_drop_self_loops() {
928        let loc1 = Location::new("loc1");
929        let loc2 = Location::new("loc2");
930        let loc3 = Location::new("loc3");
931
932        let var1 = Variable::new("var1");
933        let var2 = Variable::new("var2");
934
935        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
936            .with_guard(BooleanVarConstraint::True)
937            .build();
938        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
939            .with_guard(BooleanVarConstraint::True)
940            .build();
941        let r2 = RuleBuilder::new(2, loc1.clone(), loc1.clone())
942            .with_guard(BooleanVarConstraint::True)
943            .with_action(Action::new(var1.clone(), IntegerExpression::Atom(var1.clone())).unwrap())
944            .build();
945        let r3 = RuleBuilder::new(3, loc1.clone(), loc1.clone())
946            .with_guard(BooleanVarConstraint::True)
947            .with_action(
948                Action::new(
949                    var1.clone(),
950                    IntegerExpression::Atom(var1.clone()) + IntegerExpression::Const(1),
951                )
952                .unwrap(),
953            )
954            .build();
955        let r4 = RuleBuilder::new(4, loc2.clone(), loc2.clone())
956            .with_guard(BooleanVarConstraint::True)
957            .build();
958        let r5 = RuleBuilder::new(5, loc3.clone(), loc3.clone())
959            .with_guard(BooleanVarConstraint::True)
960            .build();
961
962        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
963            .with_variables([var1, var2])
964            .unwrap()
965            .with_locations([loc1.clone(), loc2.clone(), loc3.clone()])
966            .unwrap()
967            .initialize()
968            .with_rules([
969                r0.clone(),
970                r1.clone(),
971                r2.clone(),
972                r3.clone(),
973                r4.clone(),
974                r5.clone(),
975            ])
976            .unwrap()
977            .build();
978
979        DropSelfLoops::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
980
981        assert_eq!(ta.rules().count(), 2);
982        assert!(!ta.rules().any(|r| r0 == *r));
983        assert!(ta.rules().any(|r| r1 == *r));
984        assert!(!ta.rules().any(|r| r2 == *r));
985        assert!(ta.rules().any(|r| r3 == *r));
986        assert!(!ta.rules().any(|r| r4 == *r));
987        assert!(!ta.rules().any(|r| r5 == *r));
988    }
989
990    #[test]
991    fn test_drop_unreachable_locations() {
992        let loc1 = Location::new("loc1");
993        let loc2 = Location::new("loc2");
994        let loc3 = Location::new("loc3");
995        let loc4 = Location::new("loc4");
996        let loc5 = Location::new("loc5");
997
998        let var1 = Variable::new("var1");
999        let var2 = Variable::new("var2");
1000
1001        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1002            .with_guard(BooleanVarConstraint::True)
1003            .build();
1004        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1005            .with_guard(BooleanVarConstraint::True)
1006            .build();
1007        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1008            .with_guard(BooleanVarConstraint::True)
1009            .build();
1010        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1011            .with_guard(BooleanVarConstraint::True)
1012            .build();
1013
1014        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1015            .with_variables([var1, var2])
1016            .unwrap()
1017            .with_locations([
1018                loc1.clone(),
1019                loc2.clone(),
1020                loc3.clone(),
1021                loc4.clone(),
1022                loc5.clone(),
1023            ])
1024            .unwrap()
1025            .initialize()
1026            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1027            .unwrap()
1028            .with_initial_location_constraints([
1029                BooleanExpression::ComparisonExpression(
1030                    Box::new(IntegerExpression::Atom(loc2.clone())),
1031                    ComparisonOp::Eq,
1032                    Box::new(IntegerExpression::Const(0)),
1033                ),
1034                BooleanExpression::ComparisonExpression(
1035                    Box::new(IntegerExpression::Atom(loc3.clone())),
1036                    ComparisonOp::Eq,
1037                    Box::new(IntegerExpression::Const(0)),
1038                ),
1039                BooleanExpression::ComparisonExpression(
1040                    Box::new(IntegerExpression::Atom(loc4.clone())),
1041                    ComparisonOp::Eq,
1042                    Box::new(IntegerExpression::Const(0)),
1043                ),
1044            ])
1045            .unwrap()
1046            .build();
1047
1048        DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1049
1050        assert_eq!(ta.locations().count(), 4);
1051        assert!(ta.locations().any(|l| *l == loc1));
1052        assert!(ta.locations().any(|l| *l == loc2));
1053        assert!(ta.locations().any(|l| *l == loc3));
1054        assert!(!ta.locations().any(|l| *l == loc4));
1055        assert!(ta.locations().any(|l| *l == loc5));
1056
1057        assert_eq!(ta.rules().count(), 3);
1058        assert!(ta.rules().any(|r| r0 == *r));
1059        assert!(ta.rules().any(|r| r1 == *r));
1060        assert!(ta.rules().any(|r| r2 == *r));
1061        assert!(!ta.rules().any(|r| r3 == *r));
1062    }
1063
1064    #[test]
1065    fn test_drop_unreachable_locations_and_modify_init_constraints() {
1066        let loc1 = Location::new("loc1");
1067        let loc2 = Location::new("loc2");
1068        let loc3 = Location::new("loc3");
1069        let loc4 = Location::new("loc4");
1070        let loc5 = Location::new("loc5");
1071
1072        let var1 = Variable::new("var1");
1073        let var2 = Variable::new("var2");
1074
1075        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1076            .with_guard(BooleanVarConstraint::True)
1077            .build();
1078        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1079            .with_guard(BooleanVarConstraint::True)
1080            .build();
1081        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1082            .with_guard(BooleanVarConstraint::True)
1083            .build();
1084        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1085            .with_guard(BooleanVarConstraint::True)
1086            .build();
1087
1088        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1089            .with_variables([var1, var2])
1090            .unwrap()
1091            .with_locations([
1092                loc1.clone(),
1093                loc2.clone(),
1094                loc3.clone(),
1095                loc4.clone(),
1096                loc5.clone(),
1097            ])
1098            .unwrap()
1099            .initialize()
1100            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1101            .unwrap()
1102            .with_initial_location_constraints([
1103                BooleanExpression::ComparisonExpression(
1104                    Box::new(IntegerExpression::Atom(loc2.clone())),
1105                    ComparisonOp::Eq,
1106                    Box::new(IntegerExpression::Const(0)),
1107                ),
1108                BooleanExpression::ComparisonExpression(
1109                    Box::new(IntegerExpression::Atom(loc3.clone())),
1110                    ComparisonOp::Eq,
1111                    Box::new(IntegerExpression::Const(0)),
1112                ),
1113                BooleanExpression::ComparisonExpression(
1114                    Box::new(IntegerExpression::Atom(loc4.clone())),
1115                    ComparisonOp::Eq,
1116                    Box::new(IntegerExpression::Const(0)),
1117                ),
1118            ])
1119            .unwrap()
1120            .build();
1121
1122        DropUnreachableLocations::new().process(&mut ta, &DummySpec::new(), &DummyContext {});
1123
1124        assert_eq!(ta.locations().count(), 4);
1125        assert!(ta.locations().any(|l| *l == loc1));
1126        assert!(ta.locations().any(|l| *l == loc2));
1127        assert!(ta.locations().any(|l| *l == loc3));
1128        assert!(!ta.locations().any(|l| *l == loc4));
1129        assert!(ta.locations().any(|l| *l == loc5));
1130
1131        assert_eq!(ta.rules().count(), 3);
1132        assert!(ta.rules().any(|r| r0 == *r));
1133        assert!(ta.rules().any(|r| r1 == *r));
1134        assert!(ta.rules().any(|r| r2 == *r));
1135        assert!(!ta.rules().any(|r| r3 == *r));
1136
1137        assert!(ta.initial_location_conditions().any(|l| l
1138            == &BooleanExpression::ComparisonExpression(
1139                Box::new(IntegerExpression::Atom(loc2.clone())),
1140                ComparisonOp::Eq,
1141                Box::new(IntegerExpression::Const(0)),
1142            )));
1143        assert!(ta.initial_location_conditions().any(|l| l
1144            == &BooleanExpression::ComparisonExpression(
1145                Box::new(IntegerExpression::Atom(loc3.clone())),
1146                ComparisonOp::Eq,
1147                Box::new(IntegerExpression::Const(0)),
1148            )));
1149        assert!(!ta.initial_location_conditions().any(|l| l
1150            == &BooleanExpression::ComparisonExpression(
1151                Box::new(IntegerExpression::Atom(loc4.clone())),
1152                ComparisonOp::Eq,
1153                Box::new(IntegerExpression::Const(0)),
1154            )));
1155    }
1156
1157    #[test]
1158    fn test_combine_trivial_guards_static() {
1159        let loc1 = Location::new("loc1");
1160        let loc2 = Location::new("loc2");
1161        let loc3 = Location::new("loc3");
1162        let loc4 = Location::new("loc4");
1163        let loc5 = Location::new("loc5");
1164
1165        let var1 = Variable::new("var1");
1166        let var2 = Variable::new("var2");
1167
1168        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1169            .with_guard(BooleanVarConstraint::ComparisonExpression(
1170                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1171                ComparisonOp::Geq,
1172                Box::new(IntegerExpression::Const(0)),
1173            ))
1174            .build();
1175        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1176            .with_guard(BooleanVarConstraint::BinaryExpression(
1177                Box::new(BooleanVarConstraint::ComparisonExpression(
1178                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1179                    ComparisonOp::Geq,
1180                    Box::new(IntegerExpression::Const(1)),
1181                )),
1182                BooleanConnective::Or,
1183                Box::new(BooleanVarConstraint::ComparisonExpression(
1184                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1185                    ComparisonOp::Eq,
1186                    Box::new(IntegerExpression::Const(0)),
1187                )),
1188            ))
1189            .build();
1190        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1191            .with_guard(BooleanVarConstraint::BinaryExpression(
1192                Box::new(BooleanVarConstraint::ComparisonExpression(
1193                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1194                    ComparisonOp::Neq,
1195                    Box::new(IntegerExpression::Const(5)),
1196                )),
1197                BooleanConnective::Or,
1198                Box::new(BooleanVarConstraint::ComparisonExpression(
1199                    Box::new(IntegerExpression::Const(6)),
1200                    ComparisonOp::Neq,
1201                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1202                )),
1203            ))
1204            .build();
1205        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1206            .with_guard(BooleanVarConstraint::True)
1207            .build();
1208
1209        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1210            .with_variables([var1, var2])
1211            .unwrap()
1212            .with_locations([
1213                loc1.clone(),
1214                loc2.clone(),
1215                loc3.clone(),
1216                loc4.clone(),
1217                loc5.clone(),
1218            ])
1219            .unwrap()
1220            .initialize()
1221            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1222            .unwrap()
1223            .with_initial_location_constraints([
1224                BooleanExpression::ComparisonExpression(
1225                    Box::new(IntegerExpression::Atom(loc2.clone())),
1226                    ComparisonOp::Eq,
1227                    Box::new(IntegerExpression::Const(0)),
1228                ),
1229                BooleanExpression::ComparisonExpression(
1230                    Box::new(IntegerExpression::Atom(loc3.clone())),
1231                    ComparisonOp::Eq,
1232                    Box::new(IntegerExpression::Const(0)),
1233                ),
1234                BooleanExpression::ComparisonExpression(
1235                    Box::new(IntegerExpression::Atom(loc4.clone())),
1236                    ComparisonOp::Eq,
1237                    Box::new(IntegerExpression::Const(0)),
1238                ),
1239            ])
1240            .unwrap()
1241            .build();
1242
1243        ReplaceTrivialGuardsStatic::new().process(
1244            &mut ta,
1245            &DummySpec::new(),
1246            &SMTSolverBuilder::default(),
1247        );
1248
1249        assert_eq!(ta.rules().count(), 4);
1250        assert!(!ta.rules().any(|r| r0 == *r));
1251        assert!(!ta.rules().any(|r| r1 == *r));
1252        assert!(ta.rules().any(|r| r2 == *r));
1253        assert!(ta.rules().any(|r| r3 == *r));
1254
1255        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1256            .with_guard(BooleanVarConstraint::True)
1257            .build();
1258        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1259            .with_guard(BooleanVarConstraint::True)
1260            .build();
1261
1262        assert!(ta.rules().any(|r| r0 == *r));
1263        assert!(ta.rules().any(|r| r1 == *r));
1264    }
1265
1266    #[test]
1267    fn test_combine_trivial_guards_smt() {
1268        let loc1 = Location::new("loc1");
1269        let loc2 = Location::new("loc2");
1270        let loc3 = Location::new("loc3");
1271        let loc4 = Location::new("loc4");
1272        let loc5 = Location::new("loc5");
1273
1274        let var1 = Variable::new("var1");
1275        let var2 = Variable::new("var2");
1276
1277        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1278            .with_guard(BooleanVarConstraint::ComparisonExpression(
1279                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1280                ComparisonOp::Geq,
1281                Box::new(IntegerExpression::Const(0)),
1282            ))
1283            .build();
1284        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1285            .with_guard(BooleanVarConstraint::BinaryExpression(
1286                Box::new(BooleanVarConstraint::ComparisonExpression(
1287                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1288                    ComparisonOp::Geq,
1289                    Box::new(IntegerExpression::Const(5)),
1290                )),
1291                BooleanConnective::Or,
1292                Box::new(BooleanVarConstraint::ComparisonExpression(
1293                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1294                    ComparisonOp::Lt,
1295                    Box::new(IntegerExpression::Const(5)),
1296                )),
1297            ))
1298            .build();
1299        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1300            .with_guard(
1301                BooleanVarConstraint::ComparisonExpression(
1302                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1303                    ComparisonOp::Geq,
1304                    Box::new(IntegerExpression::Const(0)),
1305                ) & BooleanVarConstraint::ComparisonExpression(
1306                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1307                    ComparisonOp::Lt,
1308                    Box::new(IntegerExpression::Const(42)),
1309                ),
1310            )
1311            .build();
1312        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1313            .with_guard(BooleanVarConstraint::True)
1314            .build();
1315
1316        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1317            .with_variables([var1, var2])
1318            .unwrap()
1319            .with_locations([
1320                loc1.clone(),
1321                loc2.clone(),
1322                loc3.clone(),
1323                loc4.clone(),
1324                loc5.clone(),
1325            ])
1326            .unwrap()
1327            .with_parameter(Parameter::new("n"))
1328            .unwrap()
1329            .initialize()
1330            .with_resilience_condition(BooleanExpression::ComparisonExpression(
1331                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1332                ComparisonOp::Geq,
1333                Box::new(IntegerExpression::Const(1)),
1334            ))
1335            .unwrap()
1336            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1337            .unwrap()
1338            .with_initial_location_constraints([
1339                BooleanExpression::ComparisonExpression(
1340                    Box::new(IntegerExpression::Atom(loc2.clone())),
1341                    ComparisonOp::Eq,
1342                    Box::new(IntegerExpression::Const(0)),
1343                ),
1344                BooleanExpression::ComparisonExpression(
1345                    Box::new(IntegerExpression::Atom(loc3.clone())),
1346                    ComparisonOp::Eq,
1347                    Box::new(IntegerExpression::Const(0)),
1348                ),
1349                BooleanExpression::ComparisonExpression(
1350                    Box::new(IntegerExpression::Atom(loc4.clone())),
1351                    ComparisonOp::Eq,
1352                    Box::new(IntegerExpression::Const(0)),
1353                ),
1354            ])
1355            .unwrap()
1356            .build();
1357
1358        ReplaceTrivialGuardsSMT::new().process(
1359            &mut ta,
1360            &DummySpec::new(),
1361            &SMTSolverBuilder::default(),
1362        );
1363
1364        assert_eq!(ta.rules().count(), 4);
1365        assert!(!ta.rules().any(|r| r0 == *r));
1366        assert!(!ta.rules().any(|r| r1 == *r));
1367
1368        assert!(ta.rules().any(|r| r3 == *r));
1369
1370        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1371            .with_guard(BooleanVarConstraint::True)
1372            .build();
1373        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1374            .with_guard(BooleanVarConstraint::True)
1375            .build();
1376        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1377            .with_guard(
1378                BooleanExpression::True
1379                    & BooleanVarConstraint::ComparisonExpression(
1380                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1381                        ComparisonOp::Lt,
1382                        Box::new(IntegerExpression::Const(42)),
1383                    ),
1384            )
1385            .build();
1386
1387        assert!(ta.rules().any(|r| r0 == *r));
1388        assert!(ta.rules().any(|r| r1 == *r));
1389        assert!(ta.rules().any(|r| r2 == *r));
1390    }
1391
1392    #[test]
1393    fn test_remove_unused_variables() {
1394        let loc1 = Location::new("loc1");
1395        let loc2 = Location::new("loc2");
1396        let loc3 = Location::new("loc3");
1397        let loc4 = Location::new("loc4");
1398        let loc5 = Location::new("loc5");
1399
1400        let var1 = Variable::new("var1");
1401        let var2 = Variable::new("var2");
1402        let var3 = Variable::new("var3");
1403        let var4 = Variable::new("var4");
1404
1405        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1406            .with_guard(BooleanVarConstraint::True)
1407            .build();
1408        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1409            .with_guard(BooleanVarConstraint::True)
1410            .with_action(Action::new_reset(var1.clone()))
1411            .build();
1412        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1413            .with_guard(BooleanVarConstraint::ComparisonExpression(
1414                Box::new(IntegerExpression::Atom(var3.clone())),
1415                ComparisonOp::Geq,
1416                Box::new(IntegerExpression::Const(0)),
1417            ))
1418            .build();
1419        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1420            .with_guard(BooleanVarConstraint::True)
1421            .build();
1422
1423        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1424            .with_variables([var1.clone(), var2.clone(), var3.clone(), var4.clone()])
1425            .unwrap()
1426            .with_locations([
1427                loc1.clone(),
1428                loc2.clone(),
1429                loc3.clone(),
1430                loc4.clone(),
1431                loc5.clone(),
1432            ])
1433            .unwrap()
1434            .initialize()
1435            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1436            .unwrap()
1437            .with_initial_variable_constraints([
1438                BooleanExpression::ComparisonExpression(
1439                    Box::new(IntegerExpression::Atom(var1.clone())),
1440                    ComparisonOp::Eq,
1441                    Box::new(IntegerExpression::Const(0)),
1442                ),
1443                BooleanExpression::ComparisonExpression(
1444                    Box::new(IntegerExpression::Atom(var1.clone())),
1445                    ComparisonOp::Eq,
1446                    Box::new(IntegerExpression::Const(0)),
1447                ),
1448                BooleanExpression::ComparisonExpression(
1449                    Box::new(IntegerExpression::Atom(var4.clone())),
1450                    ComparisonOp::Gt,
1451                    Box::new(IntegerExpression::Const(0)),
1452                ),
1453            ])
1454            .unwrap()
1455            .build();
1456
1457        RemoveUnusedVariables {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1458
1459        assert_eq!(ta.variables().count(), 2);
1460        assert!(!ta.variables().any(|v| *v == var1));
1461        assert!(!ta.variables().any(|v| *v == var2));
1462        assert!(ta.variables().any(|v| *v == var3));
1463        assert!(ta.variables().any(|v| *v == var4));
1464    }
1465
1466    #[test]
1467    fn test_init_cond_sat() {
1468        let loc1 = Location::new("loc1");
1469        let loc2 = Location::new("loc2");
1470        let loc3 = Location::new("loc3");
1471        let loc4 = Location::new("loc4");
1472        let loc5 = Location::new("loc5");
1473
1474        let var1 = Variable::new("var1");
1475        let var2 = Variable::new("var2");
1476
1477        let r0 = RuleBuilder::new(0, loc1.clone(), loc1.clone())
1478            .with_guard(BooleanVarConstraint::True)
1479            .build();
1480        let r1 = RuleBuilder::new(1, loc1.clone(), loc2.clone())
1481            .with_guard(BooleanVarConstraint::True)
1482            .build();
1483        let r2 = RuleBuilder::new(2, loc2.clone(), loc3.clone())
1484            .with_guard(BooleanVarConstraint::True)
1485            .build();
1486        let r3 = RuleBuilder::new(3, loc4.clone(), loc5.clone())
1487            .with_guard(BooleanVarConstraint::True)
1488            .build();
1489
1490        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1491            .with_variables([var1, var2])
1492            .unwrap()
1493            .with_locations([
1494                loc1.clone(),
1495                loc2.clone(),
1496                loc3.clone(),
1497                loc4.clone(),
1498                loc5.clone(),
1499            ])
1500            .unwrap()
1501            .initialize()
1502            .with_rules([r0.clone(), r1.clone(), r2.clone(), r3.clone()])
1503            .unwrap()
1504            .with_initial_location_constraints([
1505                BooleanExpression::ComparisonExpression(
1506                    Box::new(IntegerExpression::Atom(loc2.clone())),
1507                    ComparisonOp::Eq,
1508                    Box::new(IntegerExpression::Const(0)),
1509                ),
1510                BooleanExpression::ComparisonExpression(
1511                    Box::new(IntegerExpression::Atom(loc2.clone())),
1512                    ComparisonOp::Eq,
1513                    Box::new(IntegerExpression::Const(1)),
1514                ),
1515                BooleanExpression::ComparisonExpression(
1516                    Box::new(IntegerExpression::Atom(loc3.clone())),
1517                    ComparisonOp::Eq,
1518                    Box::new(IntegerExpression::Const(0)),
1519                ),
1520                BooleanExpression::ComparisonExpression(
1521                    Box::new(IntegerExpression::Atom(loc4.clone())),
1522                    ComparisonOp::Eq,
1523                    Box::new(IntegerExpression::Const(0)),
1524                ),
1525            ])
1526            .unwrap()
1527            .build();
1528
1529        CheckInitCondSatSMT::default().process(
1530            &mut ta,
1531            &DummySpec::new(),
1532            &SMTSolverBuilder::default(),
1533        );
1534
1535        assert_eq!(ta.rules().count(), 0);
1536        assert!(
1537            ta.initial_variable_conditions()
1538                .any(|c| c == &BooleanExpression::False)
1539        );
1540        assert!(
1541            ta.initial_location_conditions()
1542                .any(|c| c == &BooleanExpression::False)
1543        )
1544    }
1545
1546    #[test]
1547    fn test_collapse_locations_no_spec() {
1548        let loc1 = Location::new("loc1");
1549        let loc2 = Location::new("loc2");
1550        let loc3 = Location::new("loc3");
1551        let loc4 = Location::new("loc4");
1552        let loc5 = Location::new("loc5");
1553        let loc6 = Location::new("loc6");
1554        let loc7 = Location::new("loc7");
1555
1556        let var1 = Variable::new("var1");
1557        let var2 = Variable::new("var2");
1558
1559        let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1560            .with_guard(BooleanVarConstraint::True)
1561            .build();
1562        let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1563            .with_guard(BooleanVarConstraint::True)
1564            .build();
1565        let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1566            .with_guard(BooleanVarConstraint::True)
1567            .build();
1568        let r3 = RuleBuilder::new(3, loc1.clone(), loc5.clone())
1569            .with_guard(BooleanVarConstraint::True)
1570            .build();
1571        let r4 = RuleBuilder::new(4, loc2.clone(), loc6.clone())
1572            .with_guard(BooleanVarConstraint::ComparisonExpression(
1573                Box::new(IntegerExpression::Atom(var2.clone())),
1574                ComparisonOp::Geq,
1575                Box::new(IntegerExpression::Const(0)),
1576            ))
1577            .build();
1578        let r5 = RuleBuilder::new(5, loc3.clone(), loc6.clone())
1579            .with_guard(BooleanVarConstraint::ComparisonExpression(
1580                Box::new(IntegerExpression::Atom(var1.clone())),
1581                ComparisonOp::Geq,
1582                Box::new(IntegerExpression::Const(0)),
1583            ))
1584            .build();
1585        let r6 = RuleBuilder::new(6, loc4.clone(), loc6.clone())
1586            .with_guard(BooleanVarConstraint::ComparisonExpression(
1587                Box::new(IntegerExpression::Atom(var1.clone())),
1588                ComparisonOp::Geq,
1589                Box::new(IntegerExpression::Const(0)),
1590            ))
1591            .build();
1592        let r7 = RuleBuilder::new(7, loc5.clone(), loc7.clone())
1593            .with_guard(BooleanVarConstraint::ComparisonExpression(
1594                Box::new(IntegerExpression::Atom(var1.clone())),
1595                ComparisonOp::Geq,
1596                Box::new(IntegerExpression::Const(0)),
1597            ))
1598            .build();
1599
1600        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1601            .with_variables([var1.clone(), var2.clone()])
1602            .unwrap()
1603            .with_locations([
1604                loc1.clone(),
1605                loc2.clone(),
1606                loc3.clone(),
1607                loc4.clone(),
1608                loc5.clone(),
1609                loc6.clone(),
1610                loc7.clone(),
1611            ])
1612            .unwrap()
1613            .initialize()
1614            .with_rules([
1615                r0.clone(),
1616                r1.clone(),
1617                r2.clone(),
1618                r3.clone(),
1619                r4.clone(),
1620                r5.clone(),
1621                r6.clone(),
1622                r7.clone(),
1623            ])
1624            .unwrap()
1625            .with_initial_location_constraints([
1626                BooleanExpression::ComparisonExpression(
1627                    Box::new(IntegerExpression::Atom(loc2.clone())),
1628                    ComparisonOp::Eq,
1629                    Box::new(IntegerExpression::Const(0)),
1630                ),
1631                BooleanExpression::ComparisonExpression(
1632                    Box::new(IntegerExpression::Atom(loc3.clone())),
1633                    ComparisonOp::Eq,
1634                    Box::new(IntegerExpression::Const(0)),
1635                ),
1636                BooleanExpression::ComparisonExpression(
1637                    Box::new(IntegerExpression::Atom(loc4.clone())),
1638                    ComparisonOp::Eq,
1639                    Box::new(IntegerExpression::Const(0)),
1640                ),
1641                BooleanExpression::ComparisonExpression(
1642                    Box::new(IntegerExpression::Atom(loc5.clone())),
1643                    ComparisonOp::Eq,
1644                    Box::new(IntegerExpression::Const(0)),
1645                ),
1646                BooleanExpression::ComparisonExpression(
1647                    Box::new(IntegerExpression::Atom(loc6.clone())),
1648                    ComparisonOp::Eq,
1649                    Box::new(IntegerExpression::Const(0)),
1650                ),
1651                BooleanExpression::ComparisonExpression(
1652                    Box::new(IntegerExpression::Atom(loc7.clone())),
1653                    ComparisonOp::Eq,
1654                    Box::new(IntegerExpression::Const(0)),
1655                ),
1656            ])
1657            .unwrap()
1658            .build();
1659
1660        CollapseLocations {}.process(&mut ta, &DummySpec::new(), &DummyContext {});
1661
1662        assert_eq!(ta.locations().count(), 4);
1663
1664        assert_eq!(ta.rules().count(), 6);
1665    }
1666
1667    #[test]
1668    fn test_collapse_locations_with_spec() {
1669        let loc1 = Location::new("loc1");
1670        let loc2 = Location::new("loc2");
1671        let loc3 = Location::new("loc3");
1672        let loc4 = Location::new("loc4");
1673
1674        let var1 = Variable::new("var1");
1675
1676        let r0 = RuleBuilder::new(0, loc1.clone(), loc2.clone())
1677            .with_guard(BooleanVarConstraint::ComparisonExpression(
1678                Box::new(IntegerExpression::Atom(var1.clone())),
1679                ComparisonOp::Geq,
1680                Box::new(IntegerExpression::Const(0)),
1681            ))
1682            .build();
1683        let r1 = RuleBuilder::new(1, loc1.clone(), loc3.clone())
1684            .with_guard(BooleanVarConstraint::ComparisonExpression(
1685                Box::new(IntegerExpression::Atom(var1.clone())),
1686                ComparisonOp::Geq,
1687                Box::new(IntegerExpression::Const(0)),
1688            ))
1689            .build();
1690        let r2 = RuleBuilder::new(2, loc1.clone(), loc4.clone())
1691            .with_guard(BooleanVarConstraint::ComparisonExpression(
1692                Box::new(IntegerExpression::Atom(var1.clone())),
1693                ComparisonOp::Geq,
1694                Box::new(IntegerExpression::Const(0)),
1695            ))
1696            .build();
1697
1698        let mut ta = GeneralThresholdAutomatonBuilder::new("ta".to_string())
1699            .with_variables([var1.clone()])
1700            .unwrap()
1701            .with_locations([loc1.clone(), loc2.clone(), loc3.clone(), loc4.clone()])
1702            .unwrap()
1703            .initialize()
1704            .with_rules([r0.clone(), r1.clone(), r2.clone()])
1705            .unwrap()
1706            .with_initial_location_constraints([
1707                BooleanExpression::ComparisonExpression(
1708                    Box::new(IntegerExpression::Atom(loc2.clone())),
1709                    ComparisonOp::Eq,
1710                    Box::new(IntegerExpression::Const(0)),
1711                ),
1712                BooleanExpression::ComparisonExpression(
1713                    Box::new(IntegerExpression::Atom(loc3.clone())),
1714                    ComparisonOp::Eq,
1715                    Box::new(IntegerExpression::Const(0)),
1716                ),
1717                BooleanExpression::ComparisonExpression(
1718                    Box::new(IntegerExpression::Atom(loc4.clone())),
1719                    ComparisonOp::Eq,
1720                    Box::new(IntegerExpression::Const(0)),
1721                ),
1722            ])
1723            .unwrap()
1724            .build();
1725
1726        let spec = UpwardsClosedSet::new_cover([loc2.clone()]);
1727
1728        CollapseLocations {}.process(&mut ta, &spec, &DummyContext {});
1729
1730        assert_eq!(ta.locations().count(), 3);
1731
1732        assert_eq!(ta.rules().count(), 3);
1733    }
1734
1735    #[test]
1736    fn test_display_available_preprocessor() {
1737        assert_eq!(
1738            format!("{}", ExistingPreprocessors::DropSelfLoops),
1739            "DropSelfLoops"
1740        );
1741        assert_eq!(
1742            format!("{}", ExistingPreprocessors::DropUnreachableLocations),
1743            "DropUnreachableLocations"
1744        );
1745        assert_eq!(
1746            format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsStatic),
1747            "ReplaceTrivialGuardsStatic"
1748        );
1749        assert_eq!(
1750            format!("{}", ExistingPreprocessors::ReplaceTrivialGuardsSMT),
1751            "ReplaceTrivialGuardsSMT"
1752        );
1753        assert_eq!(
1754            format!("{}", ExistingPreprocessors::RemoveUnusedVariables),
1755            "RemoveUnusedVariables"
1756        );
1757        assert_eq!(
1758            format!("{}", ExistingPreprocessors::DropUnsatisfiableRules),
1759            "DropUnsatisfiableRules"
1760        );
1761        assert_eq!(
1762            format!("{}", ExistingPreprocessors::CollapseLocations),
1763            "CollapseLocations"
1764        );
1765    }
1766}