taco_threshold_automaton/
general_threshold_automaton.rs

1//! Module implementing the most general form of threshold automata
2//! [`GeneralThresholdAutomaton`].
3//!
4//! This module contains the definition of the [`GeneralThresholdAutomaton`] type,
5//! which represents the most general form of a threshold automaton. This type
6//! can represent any threshold automaton, including those with variable
7//! comparisons, non-linear arithmetic thresholds and reset actions.
8//!
9//! Usually automata are expected to be parsed in to this form, and can then be
10//! transformed into a more restricted form, such as for example a
11//!  [`super::lia_threshold_automaton::LIAThresholdAutomaton`].
12
13use std::{
14    collections::{BTreeSet, HashMap, HashSet},
15    fmt::{Debug, Display},
16};
17
18use taco_display_utils::{
19    display_iterator_stable_order, indent_all, join_iterator, join_iterator_and_add_back,
20};
21
22use crate::{
23    ActionDefinition, BooleanVarConstraint, LocationConstraint, ModifiableThresholdAutomaton,
24    ParameterConstraint, RuleDefinition, ThresholdAutomaton, VariableConstraint,
25    expressions::{
26        BooleanExpression, IntegerExpression, IsDeclared, Location, Parameter, Variable,
27    },
28};
29use std::hash::Hash;
30
31pub mod builder;
32
33/// Type representing a general threshold automaton that can include variable
34/// comparisons, general non-linear arithmetic guards and as well as reset
35/// actions.
36#[derive(Debug, Eq, Clone)]
37pub struct GeneralThresholdAutomaton {
38    /// Name of the threshold automaton
39    name: String,
40    /// List of parameters appearing in the threshold automaton
41    parameters: HashSet<Parameter>,
42    /// List of shared variables appearing in the threshold automaton
43    variables: HashSet<Variable>,
44    /// List of locations appearing in the threshold automaton
45    locations: HashSet<Location>,
46    /// Transition rules of the threshold automaton indexed by source location
47    outgoing_rules: HashMap<Location, Vec<Rule>>,
48    /// Initial constraints on the number of processes in a location
49    initial_location_constraint: Vec<LocationConstraint>,
50    /// Initial constraint on the variable values
51    initial_variable_constraint: Vec<BooleanVarConstraint>,
52    /// Constraint over the parameters of the threshold automaton
53    resilience_condition: Vec<ParameterConstraint>,
54}
55
56impl GeneralThresholdAutomaton {
57    /// Get the threshold automaton in the ByMC format
58    pub fn to_bymc(&self) -> String {
59        self.to_string()
60    }
61
62    /// Returns an iterator over the initial location constraints of the threshold automaton
63    pub fn initial_variable_conditions(&self) -> impl Iterator<Item = &BooleanVarConstraint> {
64        self.initial_variable_constraint.iter()
65    }
66
67    /// Returns an iterator over the initial location constraints of the threshold automaton
68    pub fn initial_location_conditions(&self) -> impl Iterator<Item = &LocationConstraint> {
69        self.initial_location_constraint.iter()
70    }
71
72    /// Get the internal set of rules of the threshold automaton
73    pub(crate) fn get_rule_map(&self) -> HashMap<Location, Vec<Rule>> {
74        self.outgoing_rules.clone()
75    }
76
77    /// Returns the body of the threshold automaton, i.e. the definitions of
78    /// locations, variables, rules, initial assumptions, etc. in ByMC format
79    /// without the outer `proc`/ `skel`/ `thresholdAutomaton` declaration
80    pub fn get_ta_body_in_bymc_format(&self) -> String {
81        let variables = format!("shared {};", display_iterator_stable_order(&self.variables));
82        let parameters = format!(
83            "parameters {};",
84            display_iterator_stable_order(self.parameters())
85        );
86
87        let rc = format!(
88            "assumptions ({}) {{\n{}}}",
89            self.resilience_condition.len(),
90            indent_all(join_iterator_and_add_back(
91                self.resilience_conditions(),
92                ";\n"
93            ))
94        );
95
96        let mut location_str = String::new();
97        let mut locations = self.locations().collect::<Vec<_>>();
98        locations.sort();
99
100        for (i, loc) in locations.into_iter().enumerate() {
101            location_str += &format!("{loc}:[{i}];\n");
102        }
103        let locations = format!(
104            "locations ({}) {{\n{}}}",
105            self.locations.len(),
106            indent_all(location_str)
107        );
108
109        let initial_cond = format!(
110            "inits ({}) {{\n{}}}",
111            self.initial_location_constraint.len() + self.initial_variable_constraint.len(),
112            indent_all(
113                join_iterator_and_add_back(self.initial_location_constraints(), ";\n")
114                    + &join_iterator_and_add_back(self.initial_variable_constraints(), ";\n")
115            )
116        );
117
118        let mut sorted_rules_by_id = self.rules().collect::<Vec<_>>();
119        sorted_rules_by_id.sort_by_key(|r| &r.id);
120        let rules = format!(
121            "rules ({}) {{\n{}}}",
122            self.rules().count(),
123            indent_all(join_iterator_and_add_back(
124                sorted_rules_by_id.into_iter(),
125                ";\n"
126            ))
127        );
128
129        format!("{variables}\n\n{parameters}\n\n{rc}\n\n{locations}\n\n{initial_cond}\n\n{rules}")
130    }
131}
132
133impl ModifiableThresholdAutomaton for GeneralThresholdAutomaton {
134    fn set_name(&mut self, new_name: String) {
135        self.name = new_name;
136    }
137
138    fn add_rule(&mut self, rule: Self::Rule) {
139        self.outgoing_rules
140            .entry(rule.source().clone())
141            .or_default()
142            .push(rule.clone());
143    }
144
145    fn retain_rules<F>(&mut self, predicate: F)
146    where
147        F: Fn(&Self::Rule) -> bool,
148    {
149        self.outgoing_rules
150            .iter_mut()
151            .for_each(|(_, rs)| rs.retain(&predicate));
152        self.outgoing_rules.retain(|_, rs| !rs.is_empty());
153    }
154
155    fn transform_rules<F>(&mut self, mut f: F)
156    where
157        F: FnMut(&mut Self::Rule),
158    {
159        self.outgoing_rules.iter_mut().for_each(|(_, rs)| {
160            rs.iter_mut().for_each(&mut f);
161        });
162    }
163
164    fn remove_location_and_replace_with(
165        &mut self,
166        location: &Location,
167        subst: IntegerExpression<Location>,
168    ) {
169        self.locations.remove(location);
170
171        self.initial_location_constraint.iter_mut().for_each(|ic| {
172            ic.substitute_atom_with(location, &subst);
173        });
174
175        self.retain_rules(|r| r.source() != location && r.target() != location);
176    }
177
178    fn remove_variable_and_replace_with(
179        &mut self,
180        variable: &Variable,
181        subst: IntegerExpression<Variable>,
182    ) {
183        self.variables.remove(variable);
184
185        self.initial_variable_constraint.iter_mut().for_each(|ic| {
186            ic.substitute_atom_with(variable, &subst);
187        });
188
189        self.transform_rules(|r| {
190            r.guard.substitute_atom_with(variable, &subst);
191            r.actions.retain(|act| act.variable() != variable);
192        });
193    }
194
195    fn add_resilience_conditions<C: IntoIterator<Item = BooleanExpression<Parameter>>>(
196        &mut self,
197        conditions: C,
198    ) {
199        self.resilience_condition.extend(conditions);
200    }
201
202    fn add_initial_location_constraints<C: IntoIterator<Item = LocationConstraint>>(
203        &mut self,
204        constraints: C,
205    ) {
206        self.initial_location_constraint.extend(constraints);
207    }
208
209    fn add_initial_variable_constraints<C: IntoIterator<Item = BooleanVarConstraint>>(
210        &mut self,
211        constraints: C,
212    ) {
213        self.initial_variable_constraint.extend(constraints);
214    }
215
216    fn replace_initial_location_constraints<C: IntoIterator<Item = LocationConstraint>>(
217        &mut self,
218        constraints: C,
219    ) {
220        self.initial_location_constraint = constraints.into_iter().collect();
221    }
222
223    fn replace_initial_variable_constraints<C: IntoIterator<Item = BooleanVarConstraint>>(
224        &mut self,
225        constraints: C,
226    ) {
227        self.initial_variable_constraint = constraints.into_iter().collect();
228    }
229}
230
231impl ThresholdAutomaton for GeneralThresholdAutomaton {
232    type Rule = Rule;
233    type InitialVariableConstraintType = BooleanVarConstraint;
234
235    fn name(&self) -> &str {
236        self.name.as_str()
237    }
238
239    fn parameters(&self) -> impl Iterator<Item = &Parameter> {
240        self.parameters.iter()
241    }
242
243    fn resilience_conditions(&self) -> impl Iterator<Item = &BooleanExpression<Parameter>> {
244        self.resilience_condition.iter()
245    }
246
247    fn variables(&self) -> impl Iterator<Item = &Variable> {
248        self.variables.iter()
249    }
250
251    fn locations(&self) -> impl Iterator<Item = &Location> {
252        self.locations.iter()
253    }
254
255    /// Check if the given location has an initial condition that prevents it
256    /// from initially having any processes in it
257    ///
258    /// # Implementation
259    ///
260    /// Many threshold automata have initial constraints of the form
261    /// `loc1 == 0`, preventing any processes from starting in `loc1` initially.
262    /// This function checks if the given location has such a constraint.
263    ///
264    /// It will *not* attempt to evaluate all initial constraints and check
265    /// (for example by using an SMT solver) if the location can be initially
266    /// occupied.
267    ///
268    /// Instead, it will only check if the initial constraints contain a
269    /// constraint of the form `loc1 == <INTEGER EXPRESSION>`, where
270    /// `<INTEGER EXPRESSION>` evaluates to 0.
271    ///
272    /// This approach does not sacrifice correctness, as the initial constraints
273    /// are anyway encoded in the SMT query.
274    ///
275    /// # Example
276    ///
277    /// ```rust
278    /// use taco_threshold_automaton::expressions::*;
279    /// use taco_threshold_automaton::expressions::Location;
280    /// use taco_threshold_automaton::general_threshold_automaton::{GeneralThresholdAutomaton, builder::GeneralThresholdAutomatonBuilder};
281    /// use taco_threshold_automaton::ThresholdAutomaton;
282    /// use taco_threshold_automaton::LocationConstraint;
283    /// use std::collections::HashMap;
284    ///
285    /// let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
286    ///            .with_locations(vec![
287    ///                Location::new("loc1"),
288    ///                Location::new("loc2"),
289    ///                Location::new("loc3"),
290    ///            ]).unwrap()
291    ///            .initialize()
292    ///            .with_initial_location_constraints(vec![
293    ///                LocationConstraint::ComparisonExpression(
294    ///                     Box::new(IntegerExpression::Atom(Location::new("loc1"))),
295    ///                     ComparisonOp::Eq,
296    ///                     Box::new(IntegerExpression::Const(0)),
297    ///                 ),
298    ///                 LocationConstraint::ComparisonExpression(
299    ///                     Box::new(IntegerExpression::Atom(Location::new("loc2"))),
300    ///                     ComparisonOp::Geq,
301    ///                     Box::new(IntegerExpression::Const(0)),
302    ///                 ) & LocationConstraint::ComparisonExpression(
303    ///                     Box::new(IntegerExpression::Atom(Location::new("loc2"))),
304    ///                     ComparisonOp::Leq,
305    ///                     Box::new(IntegerExpression::Const(0)),
306    ///                 ),
307    ///            ]).unwrap()
308    ///            .build();
309    ///     
310    /// assert!(!ta.can_be_initial_location(&Location::new("loc1")));
311    /// assert!(!ta.can_be_initial_location(&Location::new("loc2")));
312    /// assert!(ta.can_be_initial_location(&Location::new("loc3")));
313    /// ```
314    fn can_be_initial_location(&self, loc: &Location) -> bool {
315        !self
316            .initial_location_conditions()
317            .any(|loc_constr| loc_constr.try_check_expr_constraints_to_zero(loc))
318    }
319
320    fn rules(&self) -> impl Iterator<Item = &Self::Rule> {
321        self.outgoing_rules.values().flatten()
322    }
323
324    fn incoming_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
325        let o = self
326            .outgoing_rules
327            .values()
328            .flatten()
329            .filter(|r| r.target == *location)
330            .collect::<HashSet<_>>();
331        o.into_iter()
332    }
333
334    fn outgoing_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
335        self.outgoing_rules.get(location).into_iter().flatten()
336    }
337
338    fn initial_location_constraints(&self) -> impl Iterator<Item = &LocationConstraint> {
339        self.initial_location_constraint.iter()
340    }
341
342    fn initial_variable_constraints(&self) -> impl Iterator<Item = &BooleanVarConstraint> {
343        self.initial_variable_constraint.iter()
344    }
345
346    fn has_decrements_or_resets(&self) -> bool {
347        self.rules().any(|r| r.has_decrements() || r.has_resets())
348    }
349
350    fn has_decrements(&self) -> bool {
351        self.rules().any(|r| r.has_decrements())
352    }
353
354    fn has_resets(&self) -> bool {
355        self.rules().any(|r| r.has_resets())
356    }
357}
358
359impl IsDeclared<Variable> for GeneralThresholdAutomaton {
360    fn is_declared(&self, var: &Variable) -> bool {
361        self.variables.contains(var)
362    }
363}
364
365impl IsDeclared<Location> for GeneralThresholdAutomaton {
366    fn is_declared(&self, loc: &Location) -> bool {
367        self.locations.contains(loc)
368    }
369}
370
371impl IsDeclared<Parameter> for GeneralThresholdAutomaton {
372    fn is_declared(&self, param: &Parameter) -> bool {
373        self.parameters.contains(param)
374    }
375}
376
377// Here we decided to use the bymc format to display a threshold automaton,
378// since it is well documented and easy to read.
379impl Display for GeneralThresholdAutomaton {
380    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
381        let ta_body = self.get_ta_body_in_bymc_format();
382
383        write!(
384            f,
385            "thresholdAutomaton {} {{\n{}\n}}\n",
386            self.name(),
387            indent_all(ta_body)
388        )
389    }
390}
391
392impl PartialEq for GeneralThresholdAutomaton {
393    fn eq(&self, other: &Self) -> bool {
394        let self_out_rules = self
395            .outgoing_rules
396            .iter()
397            .map(|(l, rs)| {
398                let r_set = rs.iter().collect::<HashSet<_>>();
399                (l, r_set)
400            })
401            .collect::<HashMap<_, HashSet<_>>>();
402        let other_out_rules = other
403            .outgoing_rules
404            .iter()
405            .map(|(l, rs)| {
406                let r_set = rs.iter().collect::<HashSet<_>>();
407                (l, r_set)
408            })
409            .collect::<HashMap<_, HashSet<_>>>();
410
411        let self_init_loc = self
412            .initial_location_constraint
413            .iter()
414            .collect::<HashSet<_>>();
415        let other_init_loc = other
416            .initial_location_constraint
417            .iter()
418            .collect::<HashSet<_>>();
419
420        let self_init_var = self
421            .initial_variable_constraint
422            .iter()
423            .collect::<HashSet<_>>();
424        let other_init_var = other
425            .initial_variable_constraint
426            .iter()
427            .collect::<HashSet<_>>();
428
429        let self_rc = self.resilience_condition.iter().collect::<HashSet<_>>();
430        let other_rc = other.resilience_condition.iter().collect::<HashSet<_>>();
431
432        self.name == other.name
433            && self.parameters == other.parameters
434            && self.variables == other.variables
435            && self.locations == other.locations
436            && self_out_rules == other_out_rules
437            && self_init_loc == other_init_loc
438            && self_init_var == other_init_var
439            && self_rc == other_rc
440    }
441}
442
443/// Rule type for a [`GeneralThresholdAutomaton`]
444///
445/// This type represents the rules appearing in a [`GeneralThresholdAutomaton`].
446#[derive(Debug, Eq, Clone, PartialOrd, Ord)]
447pub struct Rule {
448    /// Id assigned to the rule in the specification
449    id: u32,
450    /// Source location of the rule
451    source: Location,
452    /// Target location of the rule
453    target: Location,
454    /// Guards of the rule
455    guard: BooleanVarConstraint,
456    /// Effect of the rule
457    actions: Vec<Action>,
458}
459
460impl Rule {
461    /// Apply a transformation to the guard of the rule
462    ///
463    /// A transformation in this case is a function that mutates a
464    /// [`BooleanVarConstraint`].
465    pub fn transform_guard<F: FnMut(&mut BooleanVarConstraint)>(&mut self, mut t: F) {
466        t(&mut self.guard);
467    }
468
469    /// Retains only the actions specified by the predicate.
470    ///
471    /// In other words, it will remove all elements e for which f(&e) returns
472    /// false. This method operates in place, visiting each element exactly once
473    /// in the original order, and preserves the order of the retained elements.
474    pub fn retain_actions<F: Fn(&Action) -> bool>(&mut self, f: F) {
475        self.actions.retain(f);
476    }
477
478    /// Updates the target location
479    ///
480    /// Needed for the Preprocessor CollapseLocations
481    pub fn update_target(&mut self, loc: Location) {
482        self.target = loc;
483    }
484}
485
486impl RuleDefinition for Rule {
487    type Action = Action;
488    type Guard = BooleanVarConstraint;
489
490    fn id(&self) -> u32 {
491        self.id
492    }
493
494    fn source(&self) -> &Location {
495        &self.source
496    }
497
498    fn target(&self) -> &Location {
499        &self.target
500    }
501
502    fn guard(&self) -> &BooleanVarConstraint {
503        &self.guard
504    }
505
506    fn actions(&self) -> impl Iterator<Item = &Self::Action> {
507        self.actions.iter()
508    }
509}
510
511impl Display for Rule {
512    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
513        let guard_string = self.guard.to_string();
514        let update_string = join_iterator(self.actions.iter(), "; ");
515
516        let rule_body = indent_all(format!("when ( {guard_string} )\ndo {{ {update_string} }}"));
517
518        write!(
519            f,
520            "{}: {} -> {}\n{}",
521            self.id, self.source, self.target, rule_body
522        )
523    }
524}
525
526impl PartialEq for Rule {
527    fn eq(&self, other: &Self) -> bool {
528        let acts_self = self.actions.iter().collect::<HashSet<_>>();
529        let acts_other = other.actions.iter().collect::<HashSet<_>>();
530
531        self.id == other.id
532            && self.source == other.source
533            && self.target == other.target
534            && self.guard == other.guard
535            && acts_self == acts_other
536    }
537}
538
539impl Hash for Rule {
540    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
541        self.id.hash(state);
542        self.source.hash(state);
543        self.target.hash(state);
544        self.guard.hash(state);
545
546        let acts = self.actions.iter().collect::<BTreeSet<_>>();
547        acts.hash(state);
548    }
549}
550
551/// Action on a shared variables
552///
553/// This struct defines action in a [`GeneralThresholdAutomaton`], describing
554/// how executing a rule will update a shared variable.
555#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
556pub struct Action {
557    /// Variable to be updated
558    variable_to_update: Variable,
559    /// Expression specifying the update rule
560    update_expr: UpdateExpression,
561}
562
563/// Expressions defining an update to shared variables
564///
565/// This expression defines how a variable is updated. These expressions
566/// are primarily used in [`Action`]s.
567#[derive(Debug, PartialEq, Eq, Hash, Clone, PartialOrd, Ord)]
568pub enum UpdateExpression {
569    /// Increment of variable
570    Inc(u32),
571    /// Decrement of variable
572    Dec(u32),
573    /// Value indicating that a variable is reset
574    Reset,
575    /// Unchanged variable
576    Unchanged,
577}
578
579impl VariableConstraint for BooleanVarConstraint {
580    fn as_boolean_expr(&self) -> BooleanExpression<Variable> {
581        self.clone()
582    }
583}
584
585impl Action {
586    /// Get the update expression of the action
587    pub fn update(&self) -> &UpdateExpression {
588        &self.update_expr
589    }
590}
591
592impl ActionDefinition for Action {
593    fn variable(&self) -> &Variable {
594        &self.variable_to_update
595    }
596
597    fn is_unchanged(&self) -> bool {
598        match self.update_expr {
599            UpdateExpression::Unchanged => true,
600            UpdateExpression::Inc(_) | UpdateExpression::Dec(_) | UpdateExpression::Reset => false,
601        }
602    }
603
604    fn is_reset(&self) -> bool {
605        match self.update_expr {
606            UpdateExpression::Reset => true,
607            UpdateExpression::Inc(_) | UpdateExpression::Dec(_) | UpdateExpression::Unchanged => {
608                false
609            }
610        }
611    }
612
613    fn is_increment(&self) -> bool {
614        match &self.update_expr {
615            UpdateExpression::Inc(_) => true,
616            UpdateExpression::Reset | UpdateExpression::Dec(_) | UpdateExpression::Unchanged => {
617                false
618            }
619        }
620    }
621
622    fn is_decrement(&self) -> bool {
623        match &self.update_expr {
624            UpdateExpression::Inc(_) | UpdateExpression::Reset | UpdateExpression::Unchanged => {
625                false
626            }
627            UpdateExpression::Dec(_) => true,
628        }
629    }
630}
631
632impl Display for Action {
633    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
634        match self.update_expr {
635            UpdateExpression::Inc(_) | UpdateExpression::Dec(_) => {
636                write!(
637                    f,
638                    "{}' == {} {}",
639                    self.variable_to_update, self.variable_to_update, self.update_expr
640                )
641            }
642            UpdateExpression::Reset => {
643                write!(f, "{}' == {}", self.variable_to_update, self.update_expr)
644            }
645            UpdateExpression::Unchanged => write!(
646                f,
647                "{}' == {}",
648                self.variable_to_update, self.variable_to_update
649            ),
650        }
651    }
652}
653
654impl Display for UpdateExpression {
655    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656        match self {
657            UpdateExpression::Reset => write!(f, "0"),
658            UpdateExpression::Inc(x) => write!(f, "+ {x}"),
659            UpdateExpression::Dec(x) => write!(f, "- {x}"),
660            UpdateExpression::Unchanged => write!(f, ""),
661        }
662    }
663}
664
665#[cfg(test)]
666mod test {
667
668    use crate::expressions::{ComparisonOp, IntegerExpression};
669    use builder::{GeneralThresholdAutomatonBuilder, RuleBuilder};
670
671    use super::*;
672    use crate::expressions::{BooleanExpression, IntegerOp, Location, Parameter, Variable};
673    use std::{collections::HashMap, vec};
674
675    #[test]
676    fn ta_has_decrements_or_resets() {
677        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
678            .with_locations(vec![
679                Location::new("loc1"),
680                Location::new("loc2"),
681                Location::new("loc3"),
682            ])
683            .unwrap()
684            .with_variable(Variable::new("var1"))
685            .unwrap()
686            .initialize()
687            .with_rules(vec![
688                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
689                    .with_guard(BooleanExpression::True)
690                    .build(),
691                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
692                    .with_guard(BooleanExpression::True)
693                    .with_actions(vec![Action::new_reset(Variable::new("var1"))])
694                    .build(),
695            ])
696            .unwrap()
697            .build();
698
699        assert!(ta.has_decrements_or_resets());
700        assert!(!ta.has_decrements());
701        assert!(ta.has_resets());
702
703        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
704            .with_locations(vec![
705                Location::new("loc1"),
706                Location::new("loc2"),
707                Location::new("loc3"),
708            ])
709            .unwrap()
710            .with_variable(Variable::new("var1"))
711            .unwrap()
712            .initialize()
713            .with_rules(vec![
714                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
715                    .with_guard(BooleanExpression::True)
716                    .build(),
717                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
718                    .with_guard(BooleanExpression::True)
719                    .with_actions(vec![
720                        Action::new(
721                            Variable::new("var1"),
722                            IntegerExpression::Atom(Variable::new("var1"))
723                                - IntegerExpression::Const(1),
724                        )
725                        .unwrap(),
726                    ])
727                    .build(),
728            ])
729            .unwrap()
730            .build();
731        assert!(ta.has_decrements_or_resets());
732
733        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
734            .with_locations(vec![
735                Location::new("loc1"),
736                Location::new("loc2"),
737                Location::new("loc3"),
738            ])
739            .unwrap()
740            .with_variable(Variable::new("var1"))
741            .unwrap()
742            .initialize()
743            .with_rules(vec![
744                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
745                    .with_guard(BooleanExpression::True)
746                    .build(),
747                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
748                    .with_guard(BooleanExpression::True)
749                    .with_actions(vec![
750                        Action::new(
751                            Variable::new("var1"),
752                            IntegerExpression::Const(1)
753                                + IntegerExpression::Atom(Variable::new("var1")),
754                        )
755                        .unwrap(),
756                    ])
757                    .build(),
758            ])
759            .unwrap()
760            .build();
761        assert!(!ta.has_decrements_or_resets());
762        assert!(!ta.has_decrements());
763        assert!(!ta.has_resets());
764    }
765
766    #[test]
767    fn test_ta_getters() {
768        let ta = GeneralThresholdAutomaton {
769            name: "test_ta1".to_string(),
770            parameters: HashSet::from([
771                Parameter::new("n"),
772                Parameter::new("t"),
773                Parameter::new("f"),
774            ]),
775            variables: HashSet::from([
776                Variable::new("var1"),
777                Variable::new("var2"),
778                Variable::new("var3"),
779            ]),
780            locations: HashSet::from([
781                Location::new("loc1"),
782                Location::new("loc2"),
783                Location::new("loc3"),
784            ]),
785            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
786                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
787                ComparisonOp::Eq,
788                Box::new(IntegerExpression::Const(1)),
789            )],
790            outgoing_rules: HashMap::from([
791                (
792                    Location::new("loc1"),
793                    vec![Rule {
794                        id: 0,
795                        source: Location::new("loc1"),
796                        target: Location::new("loc2"),
797                        guard: BooleanExpression::True,
798                        actions: vec![],
799                    }],
800                ),
801                (
802                    Location::new("loc2"),
803                    vec![Rule {
804                        id: 1,
805                        source: Location::new("loc2"),
806                        target: Location::new("loc3"),
807                        guard: BooleanVarConstraint::ComparisonExpression(
808                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
809                            ComparisonOp::Eq,
810                            Box::new(IntegerExpression::Const(1)),
811                        ) & BooleanVarConstraint::ComparisonExpression(
812                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
813                            ComparisonOp::Eq,
814                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
815                        ),
816                        actions: vec![
817                            Action {
818                                variable_to_update: Variable::new("var3"),
819                                update_expr: UpdateExpression::Reset,
820                            },
821                            Action {
822                                variable_to_update: Variable::new("var1"),
823                                update_expr: UpdateExpression::Inc(1),
824                            },
825                        ],
826                    }],
827                ),
828            ]),
829
830            initial_location_constraint: vec![
831                LocationConstraint::ComparisonExpression(
832                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
833                    ComparisonOp::Eq,
834                    Box::new(
835                        IntegerExpression::Param(Parameter::new("n"))
836                            - IntegerExpression::Param(Parameter::new("f")),
837                    ),
838                ) | LocationConstraint::ComparisonExpression(
839                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
840                    ComparisonOp::Eq,
841                    Box::new(IntegerExpression::Const(0)),
842                ),
843            ],
844            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
845                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
846                ComparisonOp::Gt,
847                Box::new(IntegerExpression::BinaryExpr(
848                    Box::new(IntegerExpression::Const(3)),
849                    IntegerOp::Mul,
850                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
851                )),
852            )],
853        };
854
855        assert_eq!(ta.name(), "test_ta1");
856
857        assert!(
858            [
859                Location::new("loc1"),
860                Location::new("loc2"),
861                Location::new("loc3")
862            ]
863            .iter()
864            .all(|loc| ta.locations().any(|ta_l| ta_l == loc))
865        );
866
867        assert!(
868            [
869                Variable::new("var1"),
870                Variable::new("var2"),
871                Variable::new("var3")
872            ]
873            .iter()
874            .all(|var| ta.variables().any(|ta_v| ta_v == var))
875        );
876
877        assert!(
878            [
879                Parameter::new("n"),
880                Parameter::new("t"),
881                Parameter::new("f")
882            ]
883            .iter()
884            .all(|param| ta.parameters().any(|ta_p| ta_p == param))
885        );
886
887        assert!(
888            [BooleanVarConstraint::ComparisonExpression(
889                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
890                ComparisonOp::Eq,
891                Box::new(IntegerExpression::Const(1)),
892            )]
893            .iter()
894            .all(|var_constr| ta
895                .initial_variable_conditions()
896                .any(|ta_vc| ta_vc == var_constr))
897        );
898
899        assert!(
900            [LocationConstraint::ComparisonExpression(
901                Box::new(IntegerExpression::Atom(Location::new("loc1"))),
902                ComparisonOp::Eq,
903                Box::new(
904                    IntegerExpression::Param(Parameter::new("n"))
905                        - IntegerExpression::Param(Parameter::new("f")),
906                ),
907            ) | LocationConstraint::ComparisonExpression(
908                Box::new(IntegerExpression::Atom(Location::new("loc2"))),
909                ComparisonOp::Eq,
910                Box::new(IntegerExpression::Const(0)),
911            )]
912            .iter()
913            .all(|loc_constr| ta
914                .initial_location_conditions()
915                .any(|ta_lc| ta_lc == loc_constr))
916        );
917
918        assert!(
919            [ParameterConstraint::ComparisonExpression(
920                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
921                ComparisonOp::Gt,
922                Box::new(IntegerExpression::BinaryExpr(
923                    Box::new(IntegerExpression::Const(3)),
924                    IntegerOp::Mul,
925                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
926                )),
927            )]
928            .iter()
929            .all(|param_constr| ta
930                .resilience_conditions()
931                .any(|ta_rc| ta_rc == param_constr))
932        );
933
934        assert!(
935            [
936                Rule {
937                    id: 0,
938                    source: Location::new("loc1"),
939                    target: Location::new("loc2"),
940                    guard: BooleanExpression::True,
941                    actions: vec![],
942                },
943                Rule {
944                    id: 1,
945                    source: Location::new("loc2"),
946                    target: Location::new("loc3"),
947                    guard: BooleanVarConstraint::ComparisonExpression(
948                        Box::new(IntegerExpression::Atom(Variable::new("var1"))),
949                        ComparisonOp::Eq,
950                        Box::new(IntegerExpression::Const(1)),
951                    ) & BooleanVarConstraint::ComparisonExpression(
952                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
953                        ComparisonOp::Eq,
954                        Box::new(IntegerExpression::Param(Parameter::new("n"))),
955                    ),
956                    actions: vec![
957                        Action {
958                            variable_to_update: Variable::new("var3"),
959                            update_expr: UpdateExpression::Reset,
960                        },
961                        Action {
962                            variable_to_update: Variable::new("var1"),
963                            update_expr: UpdateExpression::Inc(1),
964                        },
965                    ],
966                }
967            ]
968            .iter()
969            .all(|rule| ta.rules().any(|ta_r| ta_r == rule))
970        );
971
972        assert_eq!(ta.outgoing_rules(&Location::new("loc1")).count(), 1);
973        assert!(ta.outgoing_rules(&Location::new("loc1")).any(|r| *r
974            == Rule {
975                id: 0,
976                source: Location::new("loc1"),
977                target: Location::new("loc2"),
978                guard: BooleanExpression::True,
979                actions: vec![],
980            }));
981
982        assert_eq!(ta.resilience_conditions().count(), 1);
983        assert!(ta.resilience_conditions().any(|r| *r
984            == ParameterConstraint::ComparisonExpression(
985                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
986                ComparisonOp::Gt,
987                Box::new(IntegerExpression::BinaryExpr(
988                    Box::new(IntegerExpression::Const(3)),
989                    IntegerOp::Mul,
990                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
991                )),
992            )));
993
994        assert_eq!(ta.incoming_rules(&Location::new("loc2")).count(), 1);
995        assert!(ta.incoming_rules(&Location::new("loc2")).any(|r| *r
996            == Rule {
997                id: 0,
998                source: Location::new("loc1"),
999                target: Location::new("loc2"),
1000                guard: BooleanExpression::True,
1001                actions: vec![],
1002            }));
1003
1004        assert!(ta.is_declared(&Variable::new("var1")));
1005        assert!(ta.is_declared(&Variable::new("var2")));
1006        assert!(ta.is_declared(&Variable::new("var3")));
1007    }
1008
1009    #[test]
1010    fn test_ta_remove_rule() {
1011        let mut ta = GeneralThresholdAutomaton {
1012            name: "test_ta1".to_string(),
1013            parameters: HashSet::from([
1014                Parameter::new("n"),
1015                Parameter::new("t"),
1016                Parameter::new("f"),
1017            ]),
1018            variables: HashSet::from([
1019                Variable::new("var1"),
1020                Variable::new("var2"),
1021                Variable::new("var3"),
1022            ]),
1023            locations: HashSet::from([
1024                Location::new("loc1"),
1025                Location::new("loc2"),
1026                Location::new("loc3"),
1027            ]),
1028            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
1029                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1030                ComparisonOp::Eq,
1031                Box::new(IntegerExpression::Const(1)),
1032            )],
1033            outgoing_rules: HashMap::from([
1034                (
1035                    Location::new("loc1"),
1036                    vec![Rule {
1037                        id: 0,
1038                        source: Location::new("loc1"),
1039                        target: Location::new("loc2"),
1040                        guard: BooleanExpression::True,
1041                        actions: vec![],
1042                    }],
1043                ),
1044                (
1045                    Location::new("loc2"),
1046                    vec![Rule {
1047                        id: 1,
1048                        source: Location::new("loc2"),
1049                        target: Location::new("loc3"),
1050                        guard: BooleanVarConstraint::ComparisonExpression(
1051                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1052                            ComparisonOp::Eq,
1053                            Box::new(IntegerExpression::Const(1)),
1054                        ) & BooleanVarConstraint::ComparisonExpression(
1055                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1056                            ComparisonOp::Eq,
1057                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1058                        ),
1059                        actions: vec![
1060                            Action {
1061                                variable_to_update: Variable::new("var3"),
1062                                update_expr: UpdateExpression::Reset,
1063                            },
1064                            Action {
1065                                variable_to_update: Variable::new("var1"),
1066                                update_expr: UpdateExpression::Inc(1),
1067                            },
1068                        ],
1069                    }],
1070                ),
1071                (
1072                    Location::new("loc3"),
1073                    vec![Rule {
1074                        id: 3,
1075                        source: Location::new("loc3"),
1076                        target: Location::new("loc2"),
1077                        guard: BooleanExpression::True,
1078                        actions: vec![],
1079                    }],
1080                ),
1081            ]),
1082
1083            initial_location_constraint: vec![
1084                LocationConstraint::ComparisonExpression(
1085                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1086                    ComparisonOp::Eq,
1087                    Box::new(
1088                        IntegerExpression::Param(Parameter::new("n"))
1089                            - IntegerExpression::Param(Parameter::new("f")),
1090                    ),
1091                ) | LocationConstraint::ComparisonExpression(
1092                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1093                    ComparisonOp::Eq,
1094                    Box::new(IntegerExpression::Const(0)),
1095                ),
1096            ],
1097            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
1098                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1099                ComparisonOp::Gt,
1100                Box::new(IntegerExpression::BinaryExpr(
1101                    Box::new(IntegerExpression::Const(3)),
1102                    IntegerOp::Mul,
1103                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1104                )),
1105            )],
1106        };
1107
1108        let rule = Rule {
1109            id: 0,
1110            source: Location::new("loc1"),
1111            target: Location::new("loc2"),
1112            guard: BooleanExpression::True,
1113            actions: vec![],
1114        };
1115
1116        ta.retain_rules(|r| *r != rule);
1117
1118        let expected_ta = GeneralThresholdAutomaton {
1119            name: "test_ta1".to_string(),
1120            parameters: HashSet::from([
1121                Parameter::new("n"),
1122                Parameter::new("t"),
1123                Parameter::new("f"),
1124            ]),
1125            variables: HashSet::from([
1126                Variable::new("var1"),
1127                Variable::new("var2"),
1128                Variable::new("var3"),
1129            ]),
1130            locations: HashSet::from([
1131                Location::new("loc1"),
1132                Location::new("loc2"),
1133                Location::new("loc3"),
1134            ]),
1135            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
1136                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1137                ComparisonOp::Eq,
1138                Box::new(IntegerExpression::Const(1)),
1139            )],
1140            outgoing_rules: HashMap::from([
1141                (
1142                    Location::new("loc2"),
1143                    vec![Rule {
1144                        id: 1,
1145                        source: Location::new("loc2"),
1146                        target: Location::new("loc3"),
1147                        guard: BooleanVarConstraint::ComparisonExpression(
1148                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1149                            ComparisonOp::Eq,
1150                            Box::new(IntegerExpression::Const(1)),
1151                        ) & BooleanVarConstraint::ComparisonExpression(
1152                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1153                            ComparisonOp::Eq,
1154                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1155                        ),
1156                        actions: vec![
1157                            Action {
1158                                variable_to_update: Variable::new("var3"),
1159                                update_expr: UpdateExpression::Reset,
1160                            },
1161                            Action {
1162                                variable_to_update: Variable::new("var1"),
1163                                update_expr: UpdateExpression::Inc(1),
1164                            },
1165                        ],
1166                    }],
1167                ),
1168                (
1169                    Location::new("loc3"),
1170                    vec![Rule {
1171                        id: 3,
1172                        source: Location::new("loc3"),
1173                        target: Location::new("loc2"),
1174                        guard: BooleanExpression::True,
1175                        actions: vec![],
1176                    }],
1177                ),
1178            ]),
1179
1180            initial_location_constraint: vec![
1181                LocationConstraint::ComparisonExpression(
1182                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1183                    ComparisonOp::Eq,
1184                    Box::new(
1185                        IntegerExpression::Param(Parameter::new("n"))
1186                            - IntegerExpression::Param(Parameter::new("f")),
1187                    ),
1188                ) | LocationConstraint::ComparisonExpression(
1189                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1190                    ComparisonOp::Eq,
1191                    Box::new(IntegerExpression::Const(0)),
1192                ),
1193            ],
1194            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
1195                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1196                ComparisonOp::Gt,
1197                Box::new(IntegerExpression::BinaryExpr(
1198                    Box::new(IntegerExpression::Const(3)),
1199                    IntegerOp::Mul,
1200                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1201                )),
1202            )],
1203        };
1204
1205        assert_eq!(ta, expected_ta);
1206    }
1207
1208    #[test]
1209    fn test_ta_remove_location() {
1210        let mut ta = GeneralThresholdAutomaton {
1211            name: "test_ta1".to_string(),
1212            parameters: HashSet::from([
1213                Parameter::new("n"),
1214                Parameter::new("t"),
1215                Parameter::new("f"),
1216            ]),
1217            variables: HashSet::from([
1218                Variable::new("var1"),
1219                Variable::new("var2"),
1220                Variable::new("var3"),
1221            ]),
1222            locations: HashSet::from([
1223                Location::new("loc1"),
1224                Location::new("loc2"),
1225                Location::new("loc3"),
1226            ]),
1227            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
1228                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1229                ComparisonOp::Eq,
1230                Box::new(IntegerExpression::Const(1)),
1231            )],
1232            outgoing_rules: HashMap::from([
1233                (
1234                    Location::new("loc1"),
1235                    vec![Rule {
1236                        id: 0,
1237                        source: Location::new("loc1"),
1238                        target: Location::new("loc2"),
1239                        guard: BooleanExpression::True,
1240                        actions: vec![],
1241                    }],
1242                ),
1243                (
1244                    Location::new("loc2"),
1245                    vec![Rule {
1246                        id: 1,
1247                        source: Location::new("loc2"),
1248                        target: Location::new("loc3"),
1249                        guard: BooleanVarConstraint::ComparisonExpression(
1250                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1251                            ComparisonOp::Eq,
1252                            Box::new(IntegerExpression::Const(1)),
1253                        ) & BooleanVarConstraint::ComparisonExpression(
1254                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1255                            ComparisonOp::Eq,
1256                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1257                        ),
1258                        actions: vec![
1259                            Action {
1260                                variable_to_update: Variable::new("var3"),
1261                                update_expr: UpdateExpression::Reset,
1262                            },
1263                            Action {
1264                                variable_to_update: Variable::new("var1"),
1265                                update_expr: UpdateExpression::Inc(1),
1266                            },
1267                        ],
1268                    }],
1269                ),
1270                (
1271                    Location::new("loc3"),
1272                    vec![Rule {
1273                        id: 3,
1274                        source: Location::new("loc3"),
1275                        target: Location::new("loc2"),
1276                        guard: BooleanExpression::True,
1277                        actions: vec![],
1278                    }],
1279                ),
1280            ]),
1281
1282            initial_location_constraint: vec![
1283                LocationConstraint::ComparisonExpression(
1284                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1285                    ComparisonOp::Eq,
1286                    Box::new(
1287                        IntegerExpression::Param(Parameter::new("n"))
1288                            - IntegerExpression::Param(Parameter::new("f")),
1289                    ),
1290                ) | LocationConstraint::ComparisonExpression(
1291                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1292                    ComparisonOp::Eq,
1293                    Box::new(IntegerExpression::Const(0)),
1294                ),
1295            ],
1296            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
1297                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1298                ComparisonOp::Gt,
1299                Box::new(IntegerExpression::BinaryExpr(
1300                    Box::new(IntegerExpression::Const(3)),
1301                    IntegerOp::Mul,
1302                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1303                )),
1304            )],
1305        };
1306
1307        ta.remove_location_and_replace_with(&Location::new("loc3"), IntegerExpression::Const(0));
1308
1309        let expected_ta = GeneralThresholdAutomaton {
1310            name: "test_ta1".to_string(),
1311            parameters: HashSet::from([
1312                Parameter::new("n"),
1313                Parameter::new("t"),
1314                Parameter::new("f"),
1315            ]),
1316            variables: HashSet::from([
1317                Variable::new("var1"),
1318                Variable::new("var2"),
1319                Variable::new("var3"),
1320            ]),
1321            locations: HashSet::from([Location::new("loc1"), Location::new("loc2")]),
1322            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
1323                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1324                ComparisonOp::Eq,
1325                Box::new(IntegerExpression::Const(1)),
1326            )],
1327            outgoing_rules: HashMap::from([(
1328                Location::new("loc1"),
1329                vec![Rule {
1330                    id: 0,
1331                    source: Location::new("loc1"),
1332                    target: Location::new("loc2"),
1333                    guard: BooleanExpression::True,
1334                    actions: vec![],
1335                }],
1336            )]),
1337
1338            initial_location_constraint: vec![
1339                LocationConstraint::ComparisonExpression(
1340                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1341                    ComparisonOp::Eq,
1342                    Box::new(
1343                        IntegerExpression::Param(Parameter::new("n"))
1344                            - IntegerExpression::Param(Parameter::new("f")),
1345                    ),
1346                ) | LocationConstraint::ComparisonExpression(
1347                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1348                    ComparisonOp::Eq,
1349                    Box::new(IntegerExpression::Const(0)),
1350                ),
1351            ],
1352            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
1353                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1354                ComparisonOp::Gt,
1355                Box::new(IntegerExpression::BinaryExpr(
1356                    Box::new(IntegerExpression::Const(3)),
1357                    IntegerOp::Mul,
1358                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1359                )),
1360            )],
1361        };
1362
1363        assert_eq!(ta, expected_ta,);
1364    }
1365
1366    #[test]
1367    fn test_can_be_initial_location() {
1368        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1369            .with_locations(vec![
1370                Location::new("loc1"),
1371                Location::new("loc2"),
1372                Location::new("loc3"),
1373                Location::new("loc4"),
1374                Location::new("loc5"),
1375                Location::new("loc6"),
1376            ])
1377            .unwrap()
1378            .initialize()
1379            .with_initial_location_constraints(vec![
1380                LocationConstraint::ComparisonExpression(
1381                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1382                    ComparisonOp::Eq,
1383                    Box::new(IntegerExpression::Const(0)),
1384                ),
1385                LocationConstraint::ComparisonExpression(
1386                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1387                    ComparisonOp::Eq,
1388                    Box::new(IntegerExpression::Const(1) + -IntegerExpression::Const(1)),
1389                ),
1390                LocationConstraint::ComparisonExpression(
1391                    Box::new(IntegerExpression::Atom(Location::new("loc4"))),
1392                    ComparisonOp::Eq,
1393                    Box::new(IntegerExpression::Const(1) * IntegerExpression::Const(0)),
1394                ) | LocationConstraint::ComparisonExpression(
1395                    Box::new(IntegerExpression::Atom(Location::new("loc4"))),
1396                    ComparisonOp::Eq,
1397                    Box::new(IntegerExpression::Const(0)),
1398                ),
1399                LocationConstraint::ComparisonExpression(
1400                    Box::new(IntegerExpression::Atom(Location::new("loc5"))),
1401                    ComparisonOp::Eq,
1402                    Box::new(IntegerExpression::Const(0)),
1403                ) & LocationConstraint::ComparisonExpression(
1404                    Box::new(IntegerExpression::Atom(Location::new("loc5"))),
1405                    ComparisonOp::Eq,
1406                    Box::new(IntegerExpression::Const(1)),
1407                ),
1408                LocationConstraint::True,
1409                LocationConstraint::False,
1410                !LocationConstraint::ComparisonExpression(
1411                    Box::new(IntegerExpression::Atom(Location::new("loc6"))),
1412                    ComparisonOp::Eq,
1413                    Box::new(IntegerExpression::Const(0)),
1414                ),
1415                LocationConstraint::ComparisonExpression(
1416                    Box::new(IntegerExpression::Const(0)),
1417                    ComparisonOp::Eq,
1418                    Box::new(IntegerExpression::Const(0)),
1419                ),
1420            ])
1421            .unwrap()
1422            .build();
1423
1424        assert!(!ta.can_be_initial_location(&Location::new("loc1")));
1425        assert!(!ta.can_be_initial_location(&Location::new("loc2")));
1426        assert!(ta.can_be_initial_location(&Location::new("loc3")));
1427        assert!(!ta.can_be_initial_location(&Location::new("loc4")));
1428        assert!(!ta.can_be_initial_location(&Location::new("loc5")));
1429        assert!(ta.can_be_initial_location(&Location::new("loc6")));
1430    }
1431
1432    #[test]
1433    fn test_display_ta() {
1434        let ta = GeneralThresholdAutomaton {
1435            name: "test_ta1".to_string(),
1436            parameters: HashSet::from([
1437                Parameter::new("n"),
1438                Parameter::new("t"),
1439                Parameter::new("f"),
1440            ]),
1441            variables: HashSet::from([
1442                Variable::new("var1"),
1443                Variable::new("var2"),
1444                Variable::new("var3"),
1445            ]),
1446            locations: HashSet::from([
1447                Location::new("loc1"),
1448                Location::new("loc2"),
1449                Location::new("loc3"),
1450            ]),
1451            initial_variable_constraint: vec![BooleanVarConstraint::ComparisonExpression(
1452                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1453                ComparisonOp::Eq,
1454                Box::new(IntegerExpression::Const(1)),
1455            )],
1456            outgoing_rules: HashMap::from([
1457                (
1458                    Location::new("loc1"),
1459                    vec![Rule {
1460                        id: 0,
1461                        source: Location::new("loc1"),
1462                        target: Location::new("loc2"),
1463                        guard: BooleanExpression::True,
1464                        actions: vec![
1465                            Action::new(
1466                                Variable::new("var1"),
1467                                IntegerExpression::Atom(Variable::new("var1")),
1468                            )
1469                            .unwrap(),
1470                        ],
1471                    }],
1472                ),
1473                (
1474                    Location::new("loc2"),
1475                    vec![Rule {
1476                        id: 1,
1477                        source: Location::new("loc2"),
1478                        target: Location::new("loc3"),
1479                        guard: BooleanVarConstraint::ComparisonExpression(
1480                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1481                            ComparisonOp::Eq,
1482                            Box::new(IntegerExpression::Const(1)),
1483                        ) & BooleanVarConstraint::ComparisonExpression(
1484                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1485                            ComparisonOp::Eq,
1486                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1487                        ),
1488                        actions: vec![
1489                            Action {
1490                                variable_to_update: Variable::new("var3"),
1491                                update_expr: UpdateExpression::Reset,
1492                            },
1493                            Action {
1494                                variable_to_update: Variable::new("var1"),
1495                                update_expr: UpdateExpression::Inc(1),
1496                            },
1497                        ],
1498                    }],
1499                ),
1500            ]),
1501
1502            initial_location_constraint: vec![
1503                LocationConstraint::ComparisonExpression(
1504                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1505                    ComparisonOp::Eq,
1506                    Box::new(
1507                        IntegerExpression::Param(Parameter::new("n"))
1508                            - IntegerExpression::Param(Parameter::new("f")),
1509                    ),
1510                ) | LocationConstraint::ComparisonExpression(
1511                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1512                    ComparisonOp::Eq,
1513                    Box::new(IntegerExpression::Const(0)),
1514                ),
1515            ],
1516
1517            resilience_condition: vec![ParameterConstraint::ComparisonExpression(
1518                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1519                ComparisonOp::Gt,
1520                Box::new(IntegerExpression::BinaryExpr(
1521                    Box::new(IntegerExpression::Const(3)),
1522                    IntegerOp::Mul,
1523                    Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1524                )),
1525            )],
1526        };
1527        let expected_ta_string = "thresholdAutomaton test_ta1 {
1528    shared var1, var2, var3;
1529
1530    parameters f, n, t;
1531
1532    assumptions (1) {
1533        n > (3 * f);
1534    }
1535
1536    locations (3) {
1537        loc1:[0];
1538        loc2:[1];
1539        loc3:[2];
1540    }
1541
1542    inits (2) {
1543        (loc1 == (n - f) || loc2 == 0);
1544        var1 == 1;
1545    }
1546
1547    rules (2) {
1548        0: loc1 -> loc2
1549            when ( true )
1550            do { var1' == var1 };
1551        1: loc2 -> loc3
1552            when ( (var1 == 1 && var2 == n) )
1553            do { var3' == 0; var1' == var1 + 1 };
1554    }
1555}
1556";
1557
1558        assert_eq!(ta.to_bymc(), expected_ta_string);
1559        assert_eq!(ta.to_string(), expected_ta_string);
1560    }
1561
1562    #[test]
1563    fn test_rule_getters() {
1564        let rule = Rule {
1565            id: 0,
1566            source: Location::new("loc1"),
1567            target: Location::new("loc2"),
1568            guard: BooleanExpression::True,
1569            actions: vec![],
1570        };
1571
1572        assert_eq!(rule.id(), 0);
1573        assert_eq!(rule.source(), &Location::new("loc1"));
1574        assert_eq!(rule.target(), &Location::new("loc2"));
1575        assert_eq!(rule.guard(), &BooleanExpression::True);
1576        assert_eq!(rule.actions().cloned().collect::<Vec<Action>>(), vec![]);
1577
1578        assert_eq!(rule.guard().as_boolean_expr(), BooleanExpression::True);
1579    }
1580
1581    #[test]
1582    fn test_rule_has_decrements_or_resets() {
1583        let rule = Rule {
1584            id: 0,
1585            source: Location::new("loc1"),
1586            target: Location::new("loc2"),
1587            guard: BooleanExpression::True,
1588            actions: vec![],
1589        };
1590        assert!(!rule.has_resets());
1591        assert!(!rule.has_decrements());
1592
1593        let rule = Rule {
1594            id: 0,
1595            source: Location::new("loc1"),
1596            target: Location::new("loc2"),
1597            guard: BooleanExpression::True,
1598            actions: vec![Action::new_reset(Variable::new("var1"))],
1599        };
1600        assert!(rule.has_resets());
1601        assert!(!rule.has_decrements());
1602
1603        let rule = Rule {
1604            id: 0,
1605            source: Location::new("loc1"),
1606            target: Location::new("loc2"),
1607            guard: BooleanExpression::True,
1608            actions: vec![
1609                Action::new(
1610                    Variable::new("var1"),
1611                    IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1),
1612                )
1613                .unwrap(),
1614            ],
1615        };
1616        assert!(!rule.has_resets());
1617        assert!(rule.has_decrements());
1618
1619        let rule = Rule {
1620            id: 0,
1621            source: Location::new("loc1"),
1622            target: Location::new("loc2"),
1623            guard: BooleanExpression::True,
1624            actions: vec![
1625                Action::new(
1626                    Variable::new("var1"),
1627                    IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var1")),
1628                )
1629                .unwrap(),
1630                Action::new(
1631                    Variable::new("var2"),
1632                    IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var2")),
1633                )
1634                .unwrap(),
1635            ],
1636        };
1637        assert!(!rule.has_resets());
1638        assert!(!rule.has_decrements());
1639
1640        let rule = Rule {
1641            id: 0,
1642            source: Location::new("loc1"),
1643            target: Location::new("loc2"),
1644            guard: BooleanExpression::True,
1645            actions: vec![
1646                Action::new_reset(Variable::new("var1")),
1647                Action::new(
1648                    Variable::new("var1"),
1649                    IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1),
1650                )
1651                .unwrap(),
1652            ],
1653        };
1654        assert!(rule.has_resets());
1655        assert!(rule.has_decrements());
1656    }
1657
1658    #[test]
1659    fn test_action_getters() {
1660        let act = Action {
1661            variable_to_update: Variable::new("var1"),
1662            update_expr: UpdateExpression::Inc(1),
1663        };
1664
1665        assert_eq!(act.variable(), &Variable::new("var1"));
1666        assert_eq!(act.update(), &UpdateExpression::Inc(1));
1667    }
1668
1669    #[test]
1670    fn test_action_is_reset() {
1671        let act = Action {
1672            variable_to_update: Variable::new("var1"),
1673            update_expr: UpdateExpression::Reset,
1674        };
1675        assert!(act.is_reset());
1676
1677        let act = Action {
1678            variable_to_update: Variable::new("var1"),
1679            update_expr: UpdateExpression::Inc(1),
1680        };
1681        assert!(!act.is_reset());
1682
1683        let act = Action {
1684            variable_to_update: Variable::new("var1"),
1685            update_expr: UpdateExpression::Dec(1),
1686        };
1687        assert!(!act.is_reset());
1688
1689        let act = Action {
1690            variable_to_update: Variable::new("var1"),
1691            update_expr: UpdateExpression::Unchanged,
1692        };
1693        assert!(!act.is_reset());
1694
1695        let act = Action::new_reset(Variable::new("var1"));
1696        assert!(act.is_reset());
1697
1698        let act = Action::new(
1699            Variable::new("var1"),
1700            IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var1")),
1701        )
1702        .unwrap();
1703        assert!(!act.is_reset());
1704
1705        let act = Action::new(
1706            Variable::new("var1"),
1707            IntegerExpression::Const(5) + -IntegerExpression::Const(5),
1708        )
1709        .unwrap();
1710        assert!(act.is_reset());
1711    }
1712
1713    #[test]
1714    fn test_act_is_decrement() {
1715        let act = Action {
1716            variable_to_update: Variable::new("var1"),
1717            update_expr: UpdateExpression::Dec(1),
1718        };
1719        assert!(act.is_decrement());
1720
1721        let act = Action {
1722            variable_to_update: Variable::new("var1"),
1723            update_expr: UpdateExpression::Inc(1),
1724        };
1725        assert!(!act.is_decrement());
1726
1727        let act = Action {
1728            variable_to_update: Variable::new("var1"),
1729            update_expr: UpdateExpression::Reset,
1730        };
1731        assert!(!act.is_decrement());
1732
1733        let act = Action {
1734            variable_to_update: Variable::new("var1"),
1735            update_expr: UpdateExpression::Unchanged,
1736        };
1737        assert!(!act.is_decrement());
1738
1739        let act = Action::new(
1740            Variable::new("var1"),
1741            IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1),
1742        )
1743        .unwrap();
1744        assert!(act.is_decrement());
1745
1746        let act = Action::new(
1747            Variable::new("var1"),
1748            IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var1")),
1749        )
1750        .unwrap();
1751        assert!(!act.is_decrement());
1752
1753        let act = Action::new(
1754            Variable::new("var1"),
1755            -IntegerExpression::Const(5) + IntegerExpression::Atom(Variable::new("var1")),
1756        )
1757        .unwrap();
1758        assert!(act.is_decrement());
1759    }
1760
1761    #[test]
1762    fn test_act_is_unchanged() {
1763        let act = Action {
1764            variable_to_update: Variable::new("var1"),
1765            update_expr: UpdateExpression::Unchanged,
1766        };
1767        assert!(act.is_unchanged());
1768
1769        let act = Action {
1770            variable_to_update: Variable::new("var1"),
1771            update_expr: UpdateExpression::Inc(1),
1772        };
1773        assert!(!act.is_unchanged());
1774
1775        let act = Action {
1776            variable_to_update: Variable::new("var1"),
1777            update_expr: UpdateExpression::Dec(1),
1778        };
1779        assert!(!act.is_unchanged());
1780
1781        let act = Action {
1782            variable_to_update: Variable::new("var1"),
1783            update_expr: UpdateExpression::Reset,
1784        };
1785        assert!(!act.is_unchanged());
1786
1787        let act = Action::new(
1788            Variable::new("var1"),
1789            IntegerExpression::Atom(Variable::new("var1")),
1790        )
1791        .unwrap();
1792        assert!(act.is_unchanged());
1793
1794        let act = Action::new(
1795            Variable::new("var1"),
1796            IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var1")),
1797        )
1798        .unwrap();
1799        assert!(!act.is_unchanged());
1800
1801        let act = Action::new(
1802            Variable::new("var1"),
1803            -IntegerExpression::Const(5) + IntegerExpression::Atom(Variable::new("var1")),
1804        )
1805        .unwrap();
1806        assert!(!act.is_unchanged());
1807    }
1808
1809    #[test]
1810    fn test_act_is_increment() {
1811        let act = Action {
1812            variable_to_update: Variable::new("var1"),
1813            update_expr: UpdateExpression::Inc(1),
1814        };
1815        assert!(act.is_increment());
1816
1817        let act = Action {
1818            variable_to_update: Variable::new("var1"),
1819            update_expr: UpdateExpression::Dec(1),
1820        };
1821        assert!(!act.is_increment());
1822
1823        let act = Action {
1824            variable_to_update: Variable::new("var1"),
1825            update_expr: UpdateExpression::Reset,
1826        };
1827        assert!(!act.is_increment());
1828
1829        let act = Action {
1830            variable_to_update: Variable::new("var1"),
1831            update_expr: UpdateExpression::Unchanged,
1832        };
1833        assert!(!act.is_increment());
1834
1835        let act = Action::new(
1836            Variable::new("var1"),
1837            IntegerExpression::Const(1) + IntegerExpression::Atom(Variable::new("var1")),
1838        )
1839        .unwrap();
1840        assert!(act.is_increment());
1841
1842        let act = Action::new(
1843            Variable::new("var1"),
1844            IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1),
1845        )
1846        .unwrap();
1847        assert!(!act.is_increment());
1848
1849        let act = Action::new(
1850            Variable::new("var1"),
1851            IntegerExpression::Atom(Variable::new("var1")) + IntegerExpression::Const(5),
1852        )
1853        .unwrap();
1854        assert!(act.is_increment());
1855    }
1856
1857    #[test]
1858    fn test_update_expr_display() {
1859        let inc_expr = UpdateExpression::Inc(1);
1860        let dec_expr = UpdateExpression::Dec(1);
1861        let reset_expr = UpdateExpression::Reset;
1862        let unchanged_expr = UpdateExpression::Unchanged;
1863
1864        assert_eq!(inc_expr.to_string(), "+ 1");
1865        assert_eq!(dec_expr.to_string(), "- 1");
1866        assert_eq!(reset_expr.to_string(), "0");
1867        assert_eq!(unchanged_expr.to_string(), "");
1868    }
1869
1870    #[test]
1871    fn test_set_name() {
1872        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1873            .initialize()
1874            .build();
1875        ta.set_name("new_ta".to_string());
1876
1877        assert_eq!(ta.name(), "new_ta")
1878    }
1879
1880    #[test]
1881    fn test_add_rule() {
1882        let r = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1883            .with_guard(BooleanExpression::True)
1884            .build();
1885
1886        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1887            .with_locations(vec![Location::new("loc1"), Location::new("loc2")])
1888            .unwrap()
1889            .initialize()
1890            .with_initial_location_constraints(vec![])
1891            .unwrap()
1892            .build();
1893
1894        ta.add_rule(r);
1895
1896        assert!(ta.outgoing_rules.contains_key(&Location::new("loc1")));
1897        assert!(
1898            ta.outgoing_rules[&Location::new("loc1")]
1899                .iter()
1900                .any(|rule| rule.id == 0)
1901        );
1902    }
1903
1904    #[test]
1905    fn test_add_resilience_condition() {
1906        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1907            .with_locations(vec![Location::new("loc1"), Location::new("loc2")])
1908            .unwrap()
1909            .with_parameters(vec![
1910                Parameter::new("n"),
1911                Parameter::new("t"),
1912                Parameter::new("f"),
1913            ])
1914            .unwrap()
1915            .initialize()
1916            .with_initial_location_constraints(vec![])
1917            .unwrap()
1918            .build();
1919
1920        let condition = ParameterConstraint::ComparisonExpression(
1921            Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1922            ComparisonOp::Gt,
1923            Box::new(IntegerExpression::Const(0)),
1924        );
1925
1926        ta.add_resilience_conditions([condition.clone()]);
1927
1928        assert_eq!(ta.resilience_condition.len(), 1);
1929        assert_eq!(ta.resilience_condition[0], condition);
1930    }
1931
1932    #[test]
1933    fn test_add_initial_location_constraint() {
1934        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1935            .with_locations(vec![Location::new("loc1"), Location::new("loc2")])
1936            .unwrap()
1937            .initialize()
1938            .with_initial_location_constraints(vec![])
1939            .unwrap()
1940            .build();
1941
1942        let constraint = LocationConstraint::ComparisonExpression(
1943            Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1944            ComparisonOp::Eq,
1945            Box::new(IntegerExpression::Const(0)),
1946        );
1947
1948        ta.add_initial_location_constraints([constraint.clone()]);
1949
1950        assert_eq!(ta.initial_location_constraint.len(), 1);
1951        assert_eq!(ta.initial_location_constraint[0], constraint);
1952    }
1953
1954    #[test]
1955    fn test_add_initial_variable_constraint() {
1956        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1957            .with_locations(vec![Location::new("loc1"), Location::new("loc2")])
1958            .unwrap()
1959            .with_variables(vec![
1960                Variable::new("var1"),
1961                Variable::new("var2"),
1962                Variable::new("var3"),
1963            ])
1964            .unwrap()
1965            .initialize()
1966            .with_initial_variable_constraints(vec![])
1967            .unwrap()
1968            .build();
1969
1970        let constraint = BooleanVarConstraint::ComparisonExpression(
1971            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1972            ComparisonOp::Eq,
1973            Box::new(IntegerExpression::Const(0)),
1974        );
1975
1976        ta.add_initial_variable_constraints([constraint.clone()]);
1977
1978        assert_eq!(ta.initial_variable_constraint.len(), 1);
1979        assert_eq!(ta.initial_variable_constraint[0], constraint);
1980    }
1981
1982    #[test]
1983    fn test_retain_rule() {
1984        let r0 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1985            .with_guard(BooleanExpression::True)
1986            .build();
1987
1988        let r1 = Rule {
1989            id: 1,
1990            source: Location::new("loc2"),
1991            target: Location::new("loc1"),
1992            guard: BooleanExpression::True,
1993            actions: vec![],
1994        };
1995
1996        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1997            .with_locations(vec![Location::new("loc1"), Location::new("loc2")])
1998            .unwrap()
1999            .initialize()
2000            .with_rules([r0.clone(), r1.clone()])
2001            .unwrap()
2002            .build();
2003
2004        ta.retain_rules(|rule| *rule != r0);
2005
2006        assert_eq!(ta.rules().count(), 1);
2007        assert!(!ta.rules().any(|r| *r == r0));
2008        assert!(ta.rules().any(|r| *r == r1));
2009    }
2010
2011    #[test]
2012    fn test_transform_rules() {
2013        let r0 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
2014            .with_guard(BooleanExpression::True)
2015            .build();
2016
2017        let r1 = Rule {
2018            id: 1,
2019            source: Location::new("loc2"),
2020            target: Location::new("loc1"),
2021            guard: BooleanExpression::True,
2022            actions: vec![],
2023        };
2024
2025        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
2026            .with_locations(vec![
2027                Location::new("loc1"),
2028                Location::new("loc2"),
2029                Location::new("loc3"),
2030                Location::new("loc4"),
2031            ])
2032            .unwrap()
2033            .initialize()
2034            .with_rules([r0.clone(), r1.clone()])
2035            .unwrap()
2036            .build();
2037
2038        ta.transform_rules(|rule| {
2039            if rule.id == 0 {
2040                rule.source = Location::new("loc3");
2041                rule.target = Location::new("loc4");
2042                rule.id = 2;
2043            }
2044        });
2045
2046        let r2 = Rule {
2047            id: 2,
2048            source: Location::new("loc3"),
2049            target: Location::new("loc4"),
2050            guard: BooleanExpression::True,
2051            actions: vec![],
2052        };
2053
2054        assert_eq!(ta.rules().count(), 2);
2055        assert!(ta.rules().any(|r| *r == r1));
2056        assert!(ta.rules().any(|r| *r == r2));
2057        assert!(!ta.rules().any(|r| *r == r0));
2058    }
2059
2060    #[test]
2061    fn test_remove_location() {
2062        let r0 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
2063            .with_guard(BooleanExpression::True)
2064            .build();
2065
2066        let r1 = Rule {
2067            id: 1,
2068            source: Location::new("loc3"),
2069            target: Location::new("loc4"),
2070            guard: BooleanExpression::True,
2071            actions: vec![],
2072        };
2073
2074        let r2 = Rule {
2075            id: 2,
2076            source: Location::new("loc4"),
2077            target: Location::new("loc3"),
2078            guard: BooleanExpression::True,
2079            actions: vec![],
2080        };
2081
2082        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
2083            .with_locations(vec![
2084                Location::new("loc1"),
2085                Location::new("loc2"),
2086                Location::new("loc3"),
2087                Location::new("loc4"),
2088            ])
2089            .unwrap()
2090            .initialize()
2091            .with_rules([r0.clone(), r1.clone(), r2.clone()])
2092            .unwrap()
2093            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2094                Box::new(IntegerExpression::Atom(Location::new("loc3"))),
2095                ComparisonOp::Eq,
2096                Box::new(IntegerExpression::Const(0)),
2097            ))
2098            .unwrap()
2099            .build();
2100
2101        ta.remove_location_and_replace_with(&Location::new("loc3"), IntegerExpression::Const(0));
2102
2103        assert_eq!(ta.locations().count(), 3);
2104        assert!(!ta.locations().any(|l| *l == Location::new("loc3")));
2105
2106        assert_eq!(ta.rules().count(), 1);
2107        assert!(ta.rules().any(|r| *r == r0));
2108        assert!(!ta.rules().any(|r| *r == r1));
2109        assert!(!ta.rules().any(|r| *r == r2));
2110
2111        let expected_cond = BooleanExpression::ComparisonExpression(
2112            Box::new(IntegerExpression::Const(0)),
2113            ComparisonOp::Eq,
2114            Box::new(IntegerExpression::Const(0)),
2115        );
2116        assert_eq!(ta.initial_location_conditions().count(), 1);
2117        assert!(
2118            ta.initial_location_conditions()
2119                .any(|c| *c == expected_cond)
2120        );
2121    }
2122
2123    #[test]
2124    fn test_remove_variable() {
2125        let r0 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
2126            .with_guard(BooleanExpression::ComparisonExpression(
2127                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2128                ComparisonOp::Eq,
2129                Box::new(IntegerExpression::Const(0)),
2130            ))
2131            .build();
2132
2133        let r1 = Rule {
2134            id: 1,
2135            source: Location::new("loc3"),
2136            target: Location::new("loc4"),
2137            guard: BooleanExpression::True,
2138            actions: vec![
2139                Action::new(
2140                    Variable::new("var1"),
2141                    IntegerExpression::Atom(Variable::new("var1")) + IntegerExpression::Const(1),
2142                )
2143                .unwrap(),
2144                Action::new(
2145                    Variable::new("var2"),
2146                    IntegerExpression::Atom(Variable::new("var2")) + IntegerExpression::Const(1),
2147                )
2148                .unwrap(),
2149            ],
2150        };
2151
2152        let mut ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
2153            .with_locations(vec![
2154                Location::new("loc1"),
2155                Location::new("loc2"),
2156                Location::new("loc3"),
2157                Location::new("loc4"),
2158            ])
2159            .unwrap()
2160            .with_variables(vec![
2161                Variable::new("var1"),
2162                Variable::new("var2"),
2163                Variable::new("var3"),
2164            ])
2165            .unwrap()
2166            .initialize()
2167            .with_rules([r0.clone(), r1.clone()])
2168            .unwrap()
2169            .with_initial_variable_constraints([BooleanVarConstraint::ComparisonExpression(
2170                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2171                ComparisonOp::Eq,
2172                Box::new(IntegerExpression::Const(0)),
2173            )])
2174            .unwrap()
2175            .build();
2176
2177        ta.remove_variable_and_replace_with(&Variable::new("var1"), IntegerExpression::Const(0));
2178
2179        assert_eq!(ta.variables().count(), 2);
2180        assert!(!ta.variables().any(|v| *v == Variable::new("var1")));
2181
2182        let expected_r0 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
2183            .with_guard(BooleanExpression::ComparisonExpression(
2184                Box::new(IntegerExpression::Const(0)),
2185                ComparisonOp::Eq,
2186                Box::new(IntegerExpression::Const(0)),
2187            ))
2188            .build();
2189        let expected_r1 = Rule {
2190            id: 1,
2191            source: Location::new("loc3"),
2192            target: Location::new("loc4"),
2193            guard: BooleanExpression::True,
2194            actions: vec![
2195                Action::new(
2196                    Variable::new("var2"),
2197                    IntegerExpression::Atom(Variable::new("var2")) + IntegerExpression::Const(1),
2198                )
2199                .unwrap(),
2200            ],
2201        };
2202
2203        assert_eq!(ta.rules().count(), 2);
2204        assert!(ta.rules().any(|r| *r == expected_r0));
2205        assert!(ta.rules().any(|r| *r == expected_r1));
2206
2207        assert_eq!(ta.initial_variable_constraints().count(), 1);
2208        assert!(ta.initial_variable_constraints().any(|c| *c
2209            == BooleanVarConstraint::ComparisonExpression(
2210                Box::new(IntegerExpression::Const(0)),
2211                ComparisonOp::Eq,
2212                Box::new(IntegerExpression::Const(0)),
2213            )));
2214    }
2215}