Skip to main content

taco_threshold_automaton/lia_threshold_automaton/
properties.rs

1//! Implementation of functionality for [`LIAThresholdAutomaton`], [`LIARule`]
2//! and the guard types
3//!
4//! This module contains the structs and traits for the linear integer arithmetic
5//! threshold automaton representation.
6
7use std::collections::HashSet;
8
9use std::error::Error;
10use std::fmt::Display;
11
12use taco_display_utils::{
13    display_iterator_stable_order, indent_all, join_iterator, join_iterator_and_add_back,
14};
15
16use crate::expressions::fraction::Fraction;
17use crate::expressions::{
18    And, Atomic, BooleanConnective, BooleanExpression, IntegerExpression, Or, Parameter, Variable,
19};
20use crate::expressions::{IsDeclared, Location};
21use crate::general_threshold_automaton::{Action, GeneralThresholdAutomaton, Rule};
22use crate::lia_threshold_automaton::integer_thresholds::{
23    IntoNoDivBooleanExpr, Threshold, ThresholdConstraint, ThresholdConstraintOver, WeightedSum,
24};
25use crate::{
26    LocationConstraint, RuleDefinition, ThresholdAutomaton, VariableConstraint, impl_bitand,
27    impl_bitor,
28};
29
30use super::{
31    ComparisonConstraint, ComparisonConstraintCreationError, LIARule, LIAThresholdAutomaton,
32    LIATransformationError, LIAVariableConstraint, SingleAtomConstraint, SumAtomConstraint,
33    SumVarConstraintCreationError,
34};
35
36impl LIAThresholdAutomaton {
37    /// Get the maximum number of thresholds in a single rule of the threshold
38    /// automaton
39    pub fn get_max_number_of_thresholds_per_rule(&self) -> u32 {
40        self.rules
41            .values()
42            .flatten()
43            .map(|rule| rule.number_of_thresholds())
44            .max()
45            .unwrap_or(0)
46    }
47
48    /// Get a reference to the [`GeneralThresholdAutomaton`] this automaton has
49    /// been derived from
50    pub fn get_ta(&self) -> &GeneralThresholdAutomaton {
51        &self.ta
52    }
53
54    /// Check if the canonical threshold automaton contains a guard over as sum
55    /// of variables, i.e., some guard contains a [`SumAtomConstraint`]
56    pub fn has_sum_var_threshold_guard(&self) -> bool {
57        self.rules
58            .values()
59            .flatten()
60            .any(|r| r.guard().has_sum_var())
61    }
62
63    /// Check if the canonical threshold automaton has a comparison guard,i.e.,
64    /// some guard contains a [`ComparisonConstraint`]
65    pub fn has_comparison_guard(&self) -> bool {
66        self.rules
67            .values()
68            .flatten()
69            .any(|r| r.guard().has_comparison_guard())
70    }
71
72    /// Get all appearing [`SingleAtomConstraint`]s
73    ///
74    /// This function returns all appearing [`SingleAtomConstraint`]s in the
75    /// rules of a threshold automaton. The constraints are deduplicated.
76    pub fn get_single_var_constraints_rules(&self) -> HashSet<SingleAtomConstraint<Variable>> {
77        self.rules()
78            .flat_map(|r| r.guard().get_single_var_constraints())
79            .collect()
80    }
81
82    /// Get all appearing [`SumAtomConstraint`]s
83    ///
84    /// This function returns all appearing [`SumAtomConstraint`]s in the
85    /// rules of a threshold automaton. The constraints are deduplicated.
86    pub fn get_sum_var_constraints(&self) -> HashSet<SumAtomConstraint<Variable>> {
87        self.rules()
88            .flat_map(|r| r.guard().get_multi_variable_guards())
89            .collect()
90    }
91
92    /// Get all appearing [`ComparisonConstraint`]s
93    ///
94    /// This function returns all appearing [`ComparisonConstraint`]s in the
95    ///rules of a threshold automaton. The constraints are deduplicated.
96    pub fn get_comparison_guards(&self) -> HashSet<ComparisonConstraint<Variable>> {
97        self.rules()
98            .flat_map(|r| r.guard().get_comparison_guards())
99            .collect()
100    }
101
102    /// Get the number of distinct thresholds appearing in the definition of the
103    /// threshold automaton
104    pub fn get_thresholds(&self) -> HashSet<Threshold> {
105        self.rules()
106            .flat_map(|r| r.get_distinct_thresholds())
107            .collect()
108    }
109
110    /// Get the [`Rule`] this rule has been derived from
111    pub fn get_derived_rule(&self, r: &LIARule) -> Rule {
112        self.ta
113            .rules()
114            .find(|rule| {
115                rule.id() == r.id() && rule.source() == r.source() && rule.target() == r.target()
116            })
117            .expect("Could not find derived rule")
118            .clone()
119    }
120}
121
122impl ThresholdAutomaton for LIAThresholdAutomaton {
123    type Rule = LIARule;
124    type InitialVariableConstraintType = LIAVariableConstraint;
125
126    fn name(&self) -> &str {
127        self.ta.name()
128    }
129
130    fn variables(&self) -> impl Iterator<Item = &Variable> {
131        // TODO: remove as soon as proper ordering is implemented
132        self.ta
133            .variables()
134            .chain(self.additional_vars_for_sums.keys())
135    }
136
137    fn parameters(&self) -> impl Iterator<Item = &Parameter> {
138        self.ta.parameters()
139    }
140
141    fn locations(&self) -> impl Iterator<Item = &Location> {
142        self.ta.locations()
143    }
144
145    fn resilience_conditions(
146        &self,
147    ) -> impl Iterator<Item = &crate::expressions::BooleanExpression<Parameter>> {
148        self.ta.resilience_conditions()
149    }
150
151    fn can_be_initial_location(&self, location: &Location) -> bool {
152        self.ta.can_be_initial_location(location)
153    }
154
155    fn rules(&self) -> impl Iterator<Item = &Self::Rule> {
156        self.rules.values().flatten()
157    }
158
159    fn incoming_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
160        self.rules().filter(move |r| r.target() == location)
161    }
162
163    fn outgoing_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
164        self.rules.get(location).into_iter().flatten()
165    }
166
167    fn initial_location_constraints(&self) -> impl Iterator<Item = &LocationConstraint> {
168        self.ta.initial_location_conditions()
169    }
170
171    fn initial_variable_constraints(&self) -> impl Iterator<Item = &LIAVariableConstraint> {
172        self.init_variable_constr.iter()
173    }
174}
175
176// Here we decided to use the bymc format to display a threshold automaton,
177// since it is well documented and easy to read.
178impl Display for LIAThresholdAutomaton {
179    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        let variables = format!(
181            "shared {};",
182            display_iterator_stable_order(self.ta.variables())
183        );
184        let parameters = format!(
185            "parameters {};",
186            display_iterator_stable_order(self.ta.parameters())
187        );
188
189        let rc = format!(
190            "assumptions ({}) {{\n{}}}",
191            self.ta.resilience_conditions().count(),
192            indent_all(join_iterator_and_add_back(
193                self.ta.resilience_conditions(),
194                ";\n"
195            ))
196        );
197
198        let mut loc_str = String::new();
199        let mut locations = self.ta.locations().collect::<Vec<_>>();
200        locations.sort();
201        for (i, loc) in locations.into_iter().enumerate() {
202            loc_str += &format!("{loc}:[{i}];\n");
203        }
204        let locations = format!(
205            "locations ({}) {{\n{}}}",
206            self.ta.locations().count(),
207            indent_all(loc_str)
208        );
209
210        let initial_cond = format!(
211            "inits ({}) {{\n{}}}",
212            self.ta.initial_location_conditions().count() + self.init_variable_constr.len(),
213            indent_all(
214                join_iterator_and_add_back(self.ta.initial_location_conditions(), ";\n")
215                    + &join_iterator_and_add_back(
216                        self.init_variable_constr
217                            .iter()
218                            .map(|cstr| cstr.as_boolean_expr()),
219                        ";\n"
220                    )
221            )
222        );
223
224        let mut sorted_rules_by_id = self.rules.values().flatten().collect::<Vec<_>>();
225        sorted_rules_by_id.sort_by_key(|r| &r.id);
226        let rules = format!(
227            "rules ({}) {{\n{}}}",
228            self.rules.len(),
229            indent_all(join_iterator_and_add_back(
230                sorted_rules_by_id.into_iter(),
231                ";\n"
232            ))
233        );
234
235        let ta_body = format!(
236            "{variables}\n\n{parameters}\n\n{rc}\n\n{locations}\n\n{initial_cond}\n\n{rules}"
237        );
238
239        write!(
240            f,
241            "thresholdAutomaton {} {{\n{}\n}}\n",
242            self.ta.name(),
243            indent_all(ta_body)
244        )
245    }
246}
247
248impl IsDeclared<Parameter> for LIAThresholdAutomaton {
249    fn is_declared(&self, obj: &Parameter) -> bool {
250        self.ta.is_declared(obj)
251    }
252}
253
254impl IsDeclared<Variable> for LIAThresholdAutomaton {
255    fn is_declared(&self, obj: &Variable) -> bool {
256        self.ta.is_declared(obj)
257    }
258}
259
260impl IsDeclared<Location> for LIAThresholdAutomaton {
261    fn is_declared(&self, obj: &Location) -> bool {
262        self.ta.is_declared(obj)
263    }
264}
265
266impl LIARule {
267    /// Get all distinct thresholds appearing in the guard of the rule
268    fn get_distinct_thresholds(&self) -> HashSet<Threshold> {
269        self.guard.get_distinct_thresholds()
270    }
271
272    /// Get the number of thresholds appearing in the guard
273    pub fn number_of_thresholds(&self) -> u32 {
274        self.guard.count_number_of_thresholds()
275    }
276
277    /// Returns the number of updates in the rule
278    pub fn number_of_updates(&self) -> u32 {
279        self.actions.len() as u32
280    }
281}
282
283impl RuleDefinition for LIARule {
284    type Action = Action;
285
286    type Guard = LIAVariableConstraint;
287
288    fn id(&self) -> u32 {
289        self.id
290    }
291
292    fn source(&self) -> &Location {
293        &self.source
294    }
295
296    fn target(&self) -> &Location {
297        &self.target
298    }
299
300    fn guard(&self) -> &Self::Guard {
301        &self.guard
302    }
303
304    fn actions(&self) -> impl Iterator<Item = &Self::Action> {
305        self.actions.iter()
306    }
307}
308
309impl Display for LIARule {
310    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
311        let guard_string = self.guard.to_string();
312        let update_string = join_iterator(self.actions.iter(), "; ");
313
314        let rule_body = indent_all(format!("when ( {guard_string} )\ndo {{ {update_string} }}"));
315
316        write!(
317            f,
318            "{}: {} -> {}\n{}",
319            self.id, self.source, self.target, rule_body
320        )
321    }
322}
323
324impl LIAVariableConstraint {
325    /// Check whether constraint is constant true
326    ///
327    /// This function checks whether the constraint corresponds to `true`. It
328    /// will not do semantic analysis, i.e. a constraint of the form `0 = 0`
329    /// will not be considered as top.
330    ///
331    /// # Example
332    ///
333    /// ```
334    /// use taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint;
335    ///
336    /// assert!(LIAVariableConstraint::True.is_top());
337    /// assert!(!LIAVariableConstraint::False.is_top());
338    /// ```
339    pub fn is_top(&self) -> bool {
340        match self {
341            LIAVariableConstraint::False
342            | LIAVariableConstraint::ComparisonConstraint(_)
343            | LIAVariableConstraint::SingleVarConstraint(_)
344            | LIAVariableConstraint::SumVarConstraint(_) => false,
345            LIAVariableConstraint::BinaryGuard(lhs, BooleanConnective::And, rhs) => {
346                lhs.is_top() && rhs.is_top()
347            }
348            LIAVariableConstraint::BinaryGuard(lhs, BooleanConnective::Or, rhs) => {
349                lhs.is_top() || rhs.is_top()
350            }
351            LIAVariableConstraint::True => true,
352        }
353    }
354
355    /// Check whether constraint is constant false
356    ///
357    /// This function checks whether the constraint corresponds to `false`. It
358    /// will not do semantic analysis, i.e. a constraint of the form `0 = 1`
359    /// will not be considered as bottom.
360    ///
361    /// # Example
362    ///
363    /// ```
364    /// use taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint;
365    ///
366    /// assert!(LIAVariableConstraint::False.is_bot());
367    /// assert!(!LIAVariableConstraint::True.is_bot());
368    /// ```
369    pub fn is_bot(&self) -> bool {
370        match self {
371            LIAVariableConstraint::True
372            | LIAVariableConstraint::ComparisonConstraint(_)
373            | LIAVariableConstraint::SingleVarConstraint(_)
374            | LIAVariableConstraint::SumVarConstraint(_) => false,
375            LIAVariableConstraint::BinaryGuard(lhs, BooleanConnective::And, rhs) => {
376                lhs.is_bot() || rhs.is_bot()
377            }
378            LIAVariableConstraint::BinaryGuard(lhs, BooleanConnective::Or, rhs) => {
379                lhs.is_bot() && rhs.is_bot()
380            }
381            LIAVariableConstraint::False => true,
382        }
383    }
384
385    /// Get all [`Threshold`]s appearing in the constraint
386    fn get_distinct_thresholds(&self) -> HashSet<Threshold> {
387        match self {
388            LIAVariableConstraint::ComparisonConstraint(cg) => {
389                HashSet::from([cg.get_threshold().clone()])
390            }
391            LIAVariableConstraint::SingleVarConstraint(svg) => {
392                HashSet::from([svg.get_threshold().clone()])
393            }
394            LIAVariableConstraint::SumVarConstraint(mvg) => {
395                HashSet::from([mvg.get_threshold().clone()])
396            }
397            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
398                let mut l_thr = lhs.get_distinct_thresholds();
399                let r_thr = rhs.get_distinct_thresholds();
400                l_thr.extend(r_thr);
401                l_thr
402            }
403            LIAVariableConstraint::True | LIAVariableConstraint::False => HashSet::new(),
404        }
405    }
406
407    /// Count the number of thresholds appearing in the constraint
408    pub fn count_number_of_thresholds(&self) -> u32 {
409        match self {
410            LIAVariableConstraint::ComparisonConstraint(_)
411            | LIAVariableConstraint::SingleVarConstraint(_)
412            | LIAVariableConstraint::SumVarConstraint(_) => 1,
413            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
414                lhs.count_number_of_thresholds() + rhs.count_number_of_thresholds()
415            }
416            LIAVariableConstraint::True => 0,
417            LIAVariableConstraint::False => 0,
418        }
419    }
420
421    /// Check if the guard contains a [`SumAtomConstraint`] in its constraint
422    pub fn has_sum_var(&self) -> bool {
423        match self {
424            LIAVariableConstraint::SumVarConstraint(_) => true,
425            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
426                lhs.has_sum_var() || rhs.has_sum_var()
427            }
428            _ => false,
429        }
430    }
431
432    /// Check if the guard contains a  [`ComparisonConstraint`] in its
433    /// constraint
434    pub fn has_comparison_guard(&self) -> bool {
435        match self {
436            LIAVariableConstraint::ComparisonConstraint(_) => true,
437            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
438                lhs.has_comparison_guard() || rhs.has_comparison_guard()
439            }
440            _ => false,
441        }
442    }
443
444    /// Get all [`SingleAtomConstraint`]s appearing in the constraint
445    pub fn get_single_var_constraints(&self) -> Vec<SingleAtomConstraint<Variable>> {
446        match self {
447            LIAVariableConstraint::SingleVarConstraint(s) => vec![s.clone()],
448            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
449                let mut guards = lhs.get_single_var_constraints();
450                guards.extend(rhs.get_single_var_constraints());
451                guards
452            }
453            LIAVariableConstraint::SumVarConstraint(_)
454            | LIAVariableConstraint::ComparisonConstraint(_)
455            | LIAVariableConstraint::True
456            | LIAVariableConstraint::False => vec![],
457        }
458    }
459
460    /// Get all [`SumAtomConstraint`] appearing in the constraint
461    pub fn get_multi_variable_guards(&self) -> Vec<SumAtomConstraint<Variable>> {
462        match self {
463            LIAVariableConstraint::SumVarConstraint(m) => vec![m.clone()],
464            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
465                let mut guards = lhs.get_multi_variable_guards();
466                guards.extend(rhs.get_multi_variable_guards());
467                guards
468            }
469            LIAVariableConstraint::SingleVarConstraint(_)
470            | LIAVariableConstraint::ComparisonConstraint(_)
471            | LIAVariableConstraint::True
472            | LIAVariableConstraint::False => vec![],
473        }
474    }
475
476    /// Get all [`ComparisonConstraint`] appearing in the constraint
477    pub fn get_comparison_guards(&self) -> Vec<ComparisonConstraint<Variable>> {
478        match self {
479            LIAVariableConstraint::ComparisonConstraint(c) => vec![c.clone()],
480            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
481                let mut guards = lhs.get_comparison_guards();
482                guards.extend(rhs.get_comparison_guards());
483                guards
484            }
485            LIAVariableConstraint::SingleVarConstraint(_)
486            | LIAVariableConstraint::SumVarConstraint(_)
487            | LIAVariableConstraint::True
488            | LIAVariableConstraint::False => vec![],
489        }
490    }
491
492    /// Check if the constraint is an upper guard
493    pub fn is_upper_guard(&self) -> bool {
494        match self {
495            LIAVariableConstraint::ComparisonConstraint(c) => c.is_upper_guard(),
496            LIAVariableConstraint::SingleVarConstraint(s) => s.is_upper_guard(),
497            LIAVariableConstraint::SumVarConstraint(m) => m.is_upper_guard(),
498            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
499                lhs.is_upper_guard() || rhs.is_upper_guard()
500            }
501            LIAVariableConstraint::True | LIAVariableConstraint::False => false,
502        }
503    }
504
505    /// Check if the constraint is a lower guard
506    pub fn is_lower_guard(&self) -> bool {
507        match self {
508            LIAVariableConstraint::ComparisonConstraint(c) => c.is_lower_guard(),
509            LIAVariableConstraint::SingleVarConstraint(s) => s.is_lower_guard(),
510            LIAVariableConstraint::SumVarConstraint(m) => m.is_lower_guard(),
511            LIAVariableConstraint::BinaryGuard(lhs, _, rhs) => {
512                lhs.is_lower_guard() || rhs.is_lower_guard()
513            }
514            LIAVariableConstraint::True | LIAVariableConstraint::False => false,
515        }
516    }
517}
518
519impl And for LIAVariableConstraint {
520    fn and(self, other: Self) -> Self {
521        LIAVariableConstraint::BinaryGuard(Box::new(self), BooleanConnective::And, Box::new(other))
522    }
523}
524impl_bitand!(LIAVariableConstraint);
525
526impl Or for LIAVariableConstraint {
527    fn or(self, other: Self) -> Self {
528        LIAVariableConstraint::BinaryGuard(Box::new(self), BooleanConnective::Or, Box::new(other))
529    }
530}
531impl_bitor!(LIAVariableConstraint);
532
533impl VariableConstraint for LIAVariableConstraint {
534    fn as_boolean_expr(&self) -> crate::expressions::BooleanExpression<Variable> {
535        match self {
536            LIAVariableConstraint::ComparisonConstraint(guard) => guard.to_boolean_expr(),
537            LIAVariableConstraint::SingleVarConstraint(guard) => guard.to_boolean_expr(),
538            LIAVariableConstraint::SumVarConstraint(guard) => guard.to_boolean_expr(),
539            LIAVariableConstraint::BinaryGuard(lhs, boolean_connective, rhs) => {
540                let lhs = lhs.as_boolean_expr();
541                let rhs = rhs.as_boolean_expr();
542                BooleanExpression::BinaryExpression(
543                    Box::new(lhs),
544                    *boolean_connective,
545                    Box::new(rhs),
546                )
547            }
548            LIAVariableConstraint::True => BooleanExpression::True,
549            LIAVariableConstraint::False => BooleanExpression::False,
550        }
551    }
552}
553
554impl<T: Atomic> SingleAtomConstraint<T> {
555    /// Create a new single variable constraint
556    pub fn new(atom: T, thr: ThresholdConstraint) -> Self {
557        Self(ThresholdConstraintOver::new(atom, thr))
558    }
559
560    /// Check if the constraint is an upper guard
561    pub fn is_upper_guard(&self) -> bool {
562        self.0.is_upper_guard()
563    }
564
565    /// Check if the guard is a lower guard
566    pub fn is_lower_guard(&self) -> bool {
567        self.0.is_lower_guard()
568    }
569
570    /// Get the variable the constraint constrains
571    pub fn get_atom(&self) -> &T {
572        self.0.get_t()
573    }
574
575    /// Transform the constraint into a [`BooleanExpression`]
576    pub fn to_boolean_expr<S>(&self) -> BooleanExpression<S>
577    where
578        T: IntoNoDivBooleanExpr<S>,
579        IntegerExpression<S>: From<T>,
580        S: Atomic,
581    {
582        self.0.encode_to_boolean_expr()
583    }
584
585    /// Get the threshold of the guard
586    pub fn get_threshold(&self) -> &Threshold {
587        self.0.get_threshold()
588    }
589
590    /// Get the threshold constraint of the guard
591    pub fn get_threshold_constraint(&self) -> &ThresholdConstraint {
592        self.0.get_threshold_constraint()
593    }
594}
595
596impl<T: Atomic> Display for SingleAtomConstraint<T> {
597    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
598        write!(f, "{}", self.0)
599    }
600}
601
602impl<T: Atomic> SumAtomConstraint<T> {
603    /// Try to create a new [`SumAtomConstraint`]
604    ///
605    /// A [`SumAtomConstraint`] constraints a (weighted) sum of **multiple**
606    /// variables, where all variable weights are positive (or all negative, in
607    /// this case the constraint is transformed such that all variable are
608    /// positive). Any other form would result in a [`ComparisonConstraint`] or
609    /// if there is only a single variable a [`SingleAtomConstraint`].
610    ///
611    /// Returns an [`SumVarConstraintCreationError`] if the coefficients are
612    /// mixed or only a single variable is present.
613    pub fn try_new<F: Into<Fraction>, I: IntoIterator<Item = (T, F)> + Clone>(
614        atoms: I,
615        mut thr: ThresholdConstraint,
616    ) -> Result<Self, SumVarConstraintCreationError> {
617        let mut variables = WeightedSum::new(atoms);
618
619        if (&variables).into_iter().count() == 1 {
620            return Err(SumVarConstraintCreationError::IsSingleAtomConstraint);
621        }
622
623        if (&variables).into_iter().any(|(_, f)| f.is_negative())
624            && (&variables).into_iter().any(|(_, f)| !f.is_negative())
625        {
626            return Err(SumVarConstraintCreationError::IsComparisonConstraint);
627        }
628
629        if (&variables).into_iter().all(|(_, f)| f.is_negative()) {
630            variables.scale(-Fraction::from(1));
631            thr.scale(-Fraction::from(1));
632        }
633
634        let constr = ThresholdConstraintOver::new(variables, thr);
635
636        Ok(Self(constr))
637    }
638
639    /// Check if the guard is an upper guard
640    pub fn is_upper_guard(&self) -> bool {
641        self.0.is_upper_guard()
642    }
643
644    /// Check if the guard is a lower guard
645    pub fn is_lower_guard(&self) -> bool {
646        self.0.is_lower_guard()
647    }
648
649    /// Get the variable
650    pub fn get_atoms(&self) -> &WeightedSum<T> {
651        self.0.get_t()
652    }
653
654    /// Get boolean expression of the guard
655    pub fn to_boolean_expr<S>(&self) -> BooleanExpression<S>
656    where
657        T: IntoNoDivBooleanExpr<S>,
658        IntegerExpression<S>: From<T>,
659        S: Atomic,
660    {
661        self.0.encode_to_boolean_expr()
662    }
663
664    /// Get the threshold of the guard
665    pub fn get_threshold(&self) -> &Threshold {
666        self.0.get_threshold()
667    }
668
669    /// Get the threshold constraint of the guard
670    pub fn get_threshold_constraint(&self) -> &ThresholdConstraint {
671        self.0.get_threshold_constraint()
672    }
673}
674
675impl<T: Atomic> Display for SumAtomConstraint<T> {
676    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
677        write!(f, "{}", self.0)
678    }
679}
680
681impl Error for SumVarConstraintCreationError {}
682
683impl Display for SumVarConstraintCreationError {
684    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
685        match self {
686            SumVarConstraintCreationError::IsComparisonConstraint => {
687                write!(f, "Guard is a ComparisonGuard not a MultiVarThresholdGuard")
688            }
689            SumVarConstraintCreationError::IsSingleAtomConstraint => {
690                write!(
691                    f,
692                    "Guard is a SingleVarThresholdGuard not a MultiVarThresholdGuard"
693                )
694            }
695        }
696    }
697}
698
699impl<T: Atomic> ComparisonConstraint<T> {
700    /// Create a [`ComparisonConstraint`]
701    ///
702    /// A comparison constraint constrains a weighted sum over **multiple**
703    /// variables where at least one has a negative and at least one has a
704    /// positive coefficient.
705    /// In any other case the guard would be a [`SingleAtomConstraint`] or a
706    /// [`SumAtomConstraint`]. In this case a
707    /// [`ComparisonConstraintCreationError`] is returned.
708    pub fn try_new<I: IntoIterator<Item = (T, Fraction)> + Clone>(
709        atoms: I,
710        thr: ThresholdConstraint,
711    ) -> Result<Self, ComparisonConstraintCreationError> {
712        if atoms.clone().into_iter().all(|(_, f)| f.is_negative())
713            || atoms.clone().into_iter().all(|(_, f)| !f.is_negative())
714        {
715            return Err(ComparisonConstraintCreationError::IsSumVarConstraint);
716        }
717
718        let atoms = WeightedSum::new(atoms);
719        let symbolic_constraint = ThresholdConstraintOver::new(atoms, thr);
720
721        Ok(Self(symbolic_constraint))
722    }
723
724    /// Check if the guard is an upper guard
725    pub fn is_upper_guard(&self) -> bool {
726        self.0.is_upper_guard()
727    }
728
729    /// Check if the guard is a lower guard
730    pub fn is_lower_guard(&self) -> bool {
731        self.0.is_lower_guard()
732    }
733
734    /// Get the boolean expression representing the threshold
735    pub fn to_boolean_expr<S>(&self) -> BooleanExpression<S>
736    where
737        T: IntoNoDivBooleanExpr<S>,
738        IntegerExpression<S>: From<T>,
739        S: Atomic,
740    {
741        self.0.encode_to_boolean_expr()
742    }
743
744    /// Get the threshold of the constraint
745    pub fn get_threshold(&self) -> &Threshold {
746        self.0.get_threshold()
747    }
748
749    /// Get the threshold constraint of the constraint
750    pub fn get_threshold_constraint(&self) -> &ThresholdConstraint {
751        self.0.get_threshold_constraint()
752    }
753}
754
755impl<T: Atomic> Display for ComparisonConstraint<T> {
756    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
757        write!(f, "{}", self.0)?;
758
759        Ok(())
760    }
761}
762
763impl Error for ComparisonConstraintCreationError {}
764
765impl Display for ComparisonConstraintCreationError {
766    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
767        match self {
768            ComparisonConstraintCreationError::IsSumVarConstraint => {
769                write!(f, "Guard is a MultiVarThresholdGuard not a ComparisonGuard")
770            }
771        }
772    }
773}
774
775impl Display for LIAVariableConstraint {
776    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
777        match self {
778            LIAVariableConstraint::SumVarConstraint(g) => write!(f, "{g}"),
779            LIAVariableConstraint::BinaryGuard(lhs, op, rhs) => {
780                write!(f, "({lhs}) {op} ({rhs})")
781            }
782            LIAVariableConstraint::True => write!(f, "true"),
783            LIAVariableConstraint::False => write!(f, "false"),
784            LIAVariableConstraint::ComparisonConstraint(g) => write!(f, "{g}"),
785            LIAVariableConstraint::SingleVarConstraint(g) => write!(f, "{g}"),
786        }
787    }
788}
789
790impl Error for LIATransformationError {}
791
792impl Display for LIATransformationError {
793    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
794        match self {
795            LIATransformationError::GuardError(rule, guard, err) => write!(
796                f,
797                "Error while transforming guard {guard} of rule {rule}: {err}"
798            ),
799            LIATransformationError::InitialConstraintError(
800                boolean_expression,
801                guard_rewrite_error,
802            ) => {
803                write!(
804                    f,
805                    "Error while transforming initial constraint {boolean_expression} into linear arithmetic: {guard_rewrite_error}"
806                )
807            }
808        }
809    }
810}
811
812#[cfg(test)]
813mod tests {
814    use std::{
815        collections::{BTreeMap, HashMap, HashSet},
816        vec,
817    };
818
819    use crate::{
820        BooleanVarConstraint, LocationConstraint, ParameterConstraint, RuleDefinition,
821        ThresholdAutomaton, VariableConstraint,
822        expressions::{
823            BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, IntegerOp,
824            Location, Parameter, Variable, fraction::Fraction,
825        },
826        general_threshold_automaton::{
827            Action,
828            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
829        },
830        lia_threshold_automaton::{
831            ComparisonConstraint, ComparisonConstraintCreationError, ConstraintRewriteError,
832            LIARule, LIAThresholdAutomaton, LIATransformationError, LIAVariableConstraint,
833            SingleAtomConstraint, SumAtomConstraint, SumVarConstraintCreationError, WeightedSum,
834            integer_thresholds::{
835                Threshold, ThresholdCompOp, ThresholdConstraint, ThresholdConstraintOver,
836            },
837        },
838    };
839
840    #[test]
841    fn test_lia_threshold_automaton_getters() {
842        let rule = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build();
843
844        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
845            .with_parameters(vec![
846                Parameter::new("n"),
847                Parameter::new("t"),
848                Parameter::new("f"),
849            ])
850            .unwrap()
851            .with_variables(vec![
852                Variable::new("var1"),
853                Variable::new("var2"),
854                Variable::new("var3"),
855            ])
856            .unwrap()
857            .with_locations(vec![
858                Location::new("loc1"),
859                Location::new("loc2"),
860                Location::new("loc3"),
861            ])
862            .unwrap()
863            .initialize()
864            .with_rules(vec![
865                rule.clone(),
866                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
867                    .with_guard(
868                        BooleanVarConstraint::ComparisonExpression(
869                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
870                            ComparisonOp::Eq,
871                            Box::new(IntegerExpression::Const(1)),
872                        ) & BooleanVarConstraint::ComparisonExpression(
873                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
874                            ComparisonOp::Eq,
875                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
876                        ),
877                    )
878                    .with_action(Action::new_reset(Variable::new("var3")))
879                    .with_action(
880                        Action::new(
881                            Variable::new("var1"),
882                            IntegerExpression::Atom(Variable::new("var1"))
883                                + IntegerExpression::Const(1),
884                        )
885                        .unwrap(),
886                    )
887                    .build(),
888            ])
889            .unwrap()
890            .with_initial_location_constraint(
891                LocationConstraint::ComparisonExpression(
892                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
893                    ComparisonOp::Eq,
894                    Box::new(
895                        IntegerExpression::Param(Parameter::new("n"))
896                            - IntegerExpression::Param(Parameter::new("f")),
897                    ),
898                ) & LocationConstraint::ComparisonExpression(
899                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
900                    ComparisonOp::Eq,
901                    Box::new(IntegerExpression::Const(0)),
902                ),
903            )
904            .unwrap()
905            .with_initial_variable_constraint(
906                BooleanVarConstraint::ComparisonExpression(
907                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
908                    ComparisonOp::Eq,
909                    Box::new(IntegerExpression::Const(0)),
910                ) & BooleanVarConstraint::ComparisonExpression(
911                    Box::new(IntegerExpression::Atom(Variable::new("var2"))),
912                    ComparisonOp::Eq,
913                    Box::new(IntegerExpression::Const(0)),
914                ),
915            )
916            .unwrap()
917            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
918                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
919                ComparisonOp::Gt,
920                Box::new(
921                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
922                ),
923            ))
924            .unwrap()
925            .build();
926
927        let abstract_rule = LIARule {
928            id: 0,
929            source: Location::new("loc1"),
930            target: Location::new("loc2"),
931            guard: LIAVariableConstraint::True,
932            actions: vec![],
933        };
934
935        let lta = LIAThresholdAutomaton {
936            ta: ta.clone(),
937            rules: HashMap::from([
938                (Location::new("loc1"), vec![abstract_rule.clone()]),
939                (
940                    Location::new("loc2"),
941                    vec![LIARule {
942                        id: 1,
943                        source: Location::new("loc2"),
944                        target: Location::new("loc3"),
945                        guard: (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
946                            ThresholdConstraintOver::new(
947                                WeightedSum::new([(Variable::new("var1"), Fraction::from(1))]),
948                                ThresholdConstraint::new(
949                                    ThresholdCompOp::Lt,
950                                    Vec::<(Parameter, Fraction)>::new(),
951                                    2,
952                                ),
953                            ),
954                        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
955                            ThresholdConstraintOver::new(
956                                WeightedSum::new([(Variable::new("var1"), Fraction::from(1))]),
957                                ThresholdConstraint::new(
958                                    ThresholdCompOp::Geq,
959                                    Vec::<(Parameter, Fraction)>::new(),
960                                    1,
961                                ),
962                            ),
963                        ))) & (LIAVariableConstraint::SingleVarConstraint(
964                            SingleAtomConstraint(ThresholdConstraintOver::new(
965                                Variable::new("var2"),
966                                ThresholdConstraint::new(
967                                    ThresholdCompOp::Lt,
968                                    [(Parameter::new("n"), 1)],
969                                    1,
970                                ),
971                            )),
972                        ) & LIAVariableConstraint::SingleVarConstraint(
973                            SingleAtomConstraint(ThresholdConstraintOver::new(
974                                Variable::new("var2"),
975                                ThresholdConstraint::new(
976                                    ThresholdCompOp::Geq,
977                                    [(Parameter::new("n"), 1)],
978                                    0,
979                                ),
980                            )),
981                        )) | (LIAVariableConstraint::ComparisonConstraint(
982                            ComparisonConstraint(ThresholdConstraintOver::new(
983                                WeightedSum::new([
984                                    (Variable::new("var1"), Fraction::from(1)),
985                                    (Variable::new("var2"), -Fraction::from(1)),
986                                ]),
987                                ThresholdConstraint::new(
988                                    ThresholdCompOp::Lt,
989                                    Vec::<(Parameter, Fraction)>::new(),
990                                    2,
991                                ),
992                            )),
993                        ) & LIAVariableConstraint::ComparisonConstraint(
994                            ComparisonConstraint(ThresholdConstraintOver::new(
995                                WeightedSum::new([
996                                    (Variable::new("var1"), Fraction::from(1)),
997                                    (Variable::new("var2"), -Fraction::from(1)),
998                                ]),
999                                ThresholdConstraint::new(
1000                                    ThresholdCompOp::Geq,
1001                                    Vec::<(Parameter, Fraction)>::new(),
1002                                    1,
1003                                ),
1004                            )),
1005                        )),
1006                        actions: vec![
1007                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
1008                                .unwrap(),
1009                            Action::new(
1010                                Variable::new("var1"),
1011                                IntegerExpression::BinaryExpr(
1012                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1013                                    IntegerOp::Add,
1014                                    Box::new(IntegerExpression::Const(1)),
1015                                ),
1016                            )
1017                            .unwrap(),
1018                        ],
1019                    }],
1020                ),
1021            ]),
1022            init_variable_constr: vec![LIAVariableConstraint::BinaryGuard(
1023                Box::new(
1024                    (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
1025                        Variable::new("var1"),
1026                        ThresholdConstraint::new(
1027                            ThresholdCompOp::Leq,
1028                            Vec::<(Parameter, Fraction)>::new(),
1029                            0,
1030                        ),
1031                    ))) & LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
1032                        Variable::new("var1"),
1033                        ThresholdConstraint::new(
1034                            ThresholdCompOp::Geq,
1035                            Vec::<(Parameter, Fraction)>::new(),
1036                            0,
1037                        ),
1038                    )),
1039                ),
1040                BooleanConnective::And,
1041                Box::new(
1042                    LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
1043                        Variable::new("var2"),
1044                        ThresholdConstraint::new(
1045                            ThresholdCompOp::Leq,
1046                            Vec::<(Parameter, Fraction)>::new(),
1047                            0,
1048                        ),
1049                    )) & LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
1050                        Variable::new("var2"),
1051                        ThresholdConstraint::new(
1052                            ThresholdCompOp::Geq,
1053                            Vec::<(Parameter, Fraction)>::new(),
1054                            0,
1055                        ),
1056                    )),
1057                ),
1058            )],
1059            additional_vars_for_sums: HashMap::new(),
1060        };
1061
1062        assert_eq!(lta.get_ta(), &ta);
1063        assert_eq!(lta.get_max_number_of_thresholds_per_rule(), 6);
1064
1065        assert_eq!(lta.name(), "test_ta1");
1066        assert_eq!(lta.variables().count(), 3);
1067        assert!(lta.variables().all(|v| ta.variables().any(|tv| tv == v)));
1068
1069        assert_eq!(lta.parameters().count(), 3);
1070        assert!(lta.parameters().all(|p| ta.parameters().any(|tp| tp == p)));
1071
1072        assert_eq!(lta.locations().count(), 3);
1073        assert!(lta.locations().all(|l| ta.locations().any(|tl| tl == l)));
1074
1075        assert_eq!(lta.resilience_conditions().count(), 1);
1076        assert!(
1077            lta.resilience_conditions()
1078                .all(|rc| ta.resilience_conditions().any(|trc| trc == rc))
1079        );
1080
1081        assert!(lta.can_be_initial_location(&Location::new("loc1")));
1082        assert!(!lta.can_be_initial_location(&Location::new("loc2")));
1083
1084        assert!(lta.outgoing_rules(&Location::new("loc3")).next().is_none());
1085        assert_eq!(lta.outgoing_rules(&Location::new("loc2")).count(), 1);
1086        assert_eq!(lta.incoming_rules(&Location::new("loc2")).count(), 1);
1087        assert_eq!(lta.incoming_rules(&Location::new("loc1")).count(), 0);
1088
1089        let mut sorted_rules = lta.rules().cloned().collect::<Vec<_>>();
1090        sorted_rules.sort_by_key(|r| r.id());
1091
1092        assert_eq!(
1093            lta.get_thresholds(),
1094            HashSet::from([
1095                Threshold::new(Vec::<(Parameter, Fraction)>::new(), 1),
1096                Threshold::new(Vec::<(Parameter, Fraction)>::new(), 2),
1097                Threshold::new([(Parameter::new("n"), 1)], 0),
1098                Threshold::new([(Parameter::new("n"), 1)], 1),
1099            ])
1100        );
1101
1102        assert_eq!(
1103            sorted_rules,
1104            vec![
1105                LIARule {
1106                    id: 0,
1107                    source: Location::new("loc1"),
1108                    target: Location::new("loc2"),
1109                    guard: LIAVariableConstraint::True,
1110                    actions: vec![],
1111                },
1112                LIARule {
1113                    id: 1,
1114                    source: Location::new("loc2"),
1115                    target: Location::new("loc3"),
1116                    guard: (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1117                        ThresholdConstraintOver::new(
1118                            WeightedSum::new([(Variable::new("var1"), 1,)]),
1119                            ThresholdConstraint::new(
1120                                ThresholdCompOp::Lt,
1121                                Vec::<(Parameter, Fraction)>::new(),
1122                                2
1123                            ),
1124                        )
1125                    )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1126                        ThresholdConstraintOver::new(
1127                            WeightedSum::new([(Variable::new("var1"), 1,)]),
1128                            ThresholdConstraint::new(
1129                                ThresholdCompOp::Geq,
1130                                Vec::<(Parameter, Fraction)>::new(),
1131                                1
1132                            ),
1133                        )
1134                    ))) & (LIAVariableConstraint::SingleVarConstraint(
1135                        SingleAtomConstraint(ThresholdConstraintOver::new(
1136                            Variable::new("var2"),
1137                            ThresholdConstraint::new(
1138                                ThresholdCompOp::Lt,
1139                                [(Parameter::new("n"), 1)],
1140                                1
1141                            ),
1142                        ))
1143                    ) & LIAVariableConstraint::SingleVarConstraint(
1144                        SingleAtomConstraint(ThresholdConstraintOver::new(
1145                            Variable::new("var2"),
1146                            ThresholdConstraint::new(
1147                                ThresholdCompOp::Geq,
1148                                [(Parameter::new("n"), 1)],
1149                                0
1150                            ),
1151                        ))
1152                    )) | (LIAVariableConstraint::ComparisonConstraint(
1153                        ComparisonConstraint(ThresholdConstraintOver::new(
1154                            WeightedSum::new([
1155                                (Variable::new("var1"), Fraction::from(1)),
1156                                (Variable::new("var2"), -Fraction::from(1))
1157                            ]),
1158                            ThresholdConstraint::new(
1159                                ThresholdCompOp::Lt,
1160                                Vec::<(Parameter, Fraction)>::new(),
1161                                2,
1162                            ),
1163                        ))
1164                    ) & LIAVariableConstraint::ComparisonConstraint(
1165                        ComparisonConstraint(ThresholdConstraintOver::new(
1166                            WeightedSum::new([
1167                                (Variable::new("var1"), Fraction::from(1)),
1168                                (Variable::new("var2"), -Fraction::from(1))
1169                            ]),
1170                            ThresholdConstraint::new(
1171                                ThresholdCompOp::Geq,
1172                                Vec::<(Parameter, Fraction)>::new(),
1173                                1,
1174                            ),
1175                        ))
1176                    )),
1177                    actions: vec![
1178                        Action::new(Variable::new("var3"), IntegerExpression::Const(0)).unwrap(),
1179                        Action::new(
1180                            Variable::new("var1"),
1181                            IntegerExpression::BinaryExpr(
1182                                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1183                                IntegerOp::Add,
1184                                Box::new(IntegerExpression::Const(1)),
1185                            ),
1186                        )
1187                        .unwrap(),
1188                    ],
1189                }
1190            ]
1191        );
1192
1193        assert_eq!(lta.get_derived_rule(&abstract_rule), rule);
1194
1195        assert_eq!(lta.initial_location_constraints().count(), 1);
1196        assert!(
1197            lta.initial_location_constraints()
1198                .any(|ilc| ta.initial_location_constraints().any(|tilc| tilc == ilc))
1199        );
1200
1201        assert_eq!(lta.initial_variable_constraints().count(), 1);
1202        assert!(lta.initial_variable_constraints().any(|ivc| {
1203            ta.initial_variable_constraints()
1204                .map(|c| LIAVariableConstraint::try_from(c.remove_boolean_negations()).unwrap())
1205                .any(|tivc| tivc == *ivc)
1206        }));
1207    }
1208
1209    #[test]
1210    fn test_lia_get_single_var_threshold() {
1211        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1212            .with_parameters(vec![
1213                Parameter::new("n"),
1214                Parameter::new("t"),
1215                Parameter::new("f"),
1216            ])
1217            .unwrap()
1218            .with_variables(vec![
1219                Variable::new("var1"),
1220                Variable::new("var2"),
1221                Variable::new("var3"),
1222            ])
1223            .unwrap()
1224            .with_locations(vec![
1225                Location::new("loc1"),
1226                Location::new("loc2"),
1227                Location::new("loc3"),
1228            ])
1229            .unwrap()
1230            .initialize()
1231            .with_rules(vec![
1232                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
1233                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1234                    .with_guard(
1235                        BooleanVarConstraint::ComparisonExpression(
1236                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1237                            ComparisonOp::Eq,
1238                            Box::new(IntegerExpression::Const(1)),
1239                        ) & BooleanVarConstraint::ComparisonExpression(
1240                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1241                            ComparisonOp::Eq,
1242                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1243                        ),
1244                    )
1245                    .with_action(Action::new_reset(Variable::new("var3")))
1246                    .with_action(
1247                        Action::new(
1248                            Variable::new("var1"),
1249                            IntegerExpression::Atom(Variable::new("var1"))
1250                                + IntegerExpression::Const(1),
1251                        )
1252                        .unwrap(),
1253                    )
1254                    .build(),
1255            ])
1256            .unwrap()
1257            .with_initial_location_constraint(
1258                LocationConstraint::ComparisonExpression(
1259                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1260                    ComparisonOp::Eq,
1261                    Box::new(
1262                        IntegerExpression::Param(Parameter::new("n"))
1263                            - IntegerExpression::Param(Parameter::new("f")),
1264                    ),
1265                ) | LocationConstraint::ComparisonExpression(
1266                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1267                    ComparisonOp::Eq,
1268                    Box::new(IntegerExpression::Const(0)),
1269                ),
1270            )
1271            .unwrap()
1272            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
1273                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1274                ComparisonOp::Gt,
1275                Box::new(
1276                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
1277                ),
1278            ))
1279            .unwrap()
1280            .build();
1281
1282        let lta = LIAThresholdAutomaton {
1283            ta,
1284            rules: HashMap::from([
1285                (
1286                    Location::new("loc1"),
1287                    vec![LIARule {
1288                        id: 0,
1289                        source: Location::new("loc1"),
1290                        target: Location::new("loc2"),
1291                        guard: LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
1292                            ThresholdConstraintOver::new(
1293                                Variable::new("var1"),
1294                                ThresholdConstraint::new(
1295                                    ThresholdCompOp::Lt,
1296                                    Vec::<(Parameter, Fraction)>::new(),
1297                                    2,
1298                                ),
1299                            ),
1300                        )) & LIAVariableConstraint::SingleVarConstraint(
1301                            SingleAtomConstraint(ThresholdConstraintOver::new(
1302                                Variable::new("var1"),
1303                                ThresholdConstraint::new(
1304                                    ThresholdCompOp::Geq,
1305                                    Vec::<(Parameter, Fraction)>::new(),
1306                                    1,
1307                                ),
1308                            )),
1309                        ),
1310                        actions: vec![],
1311                    }],
1312                ),
1313                (
1314                    Location::new("loc2"),
1315                    vec![LIARule {
1316                        id: 1,
1317                        source: Location::new("loc2"),
1318                        target: Location::new("loc3"),
1319                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
1320                            ThresholdConstraintOver::new(
1321                                Variable::new("var1"),
1322                                ThresholdConstraint::new(
1323                                    ThresholdCompOp::Lt,
1324                                    Vec::<(Parameter, Fraction)>::new(),
1325                                    2,
1326                                ),
1327                            ),
1328                        )) & LIAVariableConstraint::SingleVarConstraint(
1329                            SingleAtomConstraint(ThresholdConstraintOver::new(
1330                                Variable::new("var1"),
1331                                ThresholdConstraint::new(
1332                                    ThresholdCompOp::Geq,
1333                                    Vec::<(Parameter, Fraction)>::new(),
1334                                    1,
1335                                ),
1336                            )),
1337                        )) & (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1338                            ThresholdConstraintOver::new(
1339                                WeightedSum::new(BTreeMap::from([
1340                                    (Variable::new("var2"), 1),
1341                                    (Variable::new("var1"), 1),
1342                                ])),
1343                                ThresholdConstraint::new(
1344                                    ThresholdCompOp::Lt,
1345                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1346                                    1,
1347                                ),
1348                            ),
1349                        )) & LIAVariableConstraint::SumVarConstraint(
1350                            SumAtomConstraint(ThresholdConstraintOver::new(
1351                                WeightedSum::new(BTreeMap::from([
1352                                    (Variable::new("var2"), 1),
1353                                    (Variable::new("var1"), 1),
1354                                ])),
1355                                ThresholdConstraint::new(
1356                                    ThresholdCompOp::Geq,
1357                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1358                                    0,
1359                                ),
1360                            )),
1361                        )) | (LIAVariableConstraint::ComparisonConstraint(
1362                            ComparisonConstraint(ThresholdConstraintOver::new(
1363                                WeightedSum::new(BTreeMap::from([
1364                                    (Variable::new("var3"), 1.into()),
1365                                    (Variable::new("var3"), -Fraction::from(1)),
1366                                ])),
1367                                ThresholdConstraint::new(
1368                                    ThresholdCompOp::Lt,
1369                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1370                                    1,
1371                                ),
1372                            )),
1373                        ) & LIAVariableConstraint::ComparisonConstraint(
1374                            ComparisonConstraint(ThresholdConstraintOver::new(
1375                                WeightedSum::new(BTreeMap::from([
1376                                    (Variable::new("var3"), 1.into()),
1377                                    (Variable::new("var3"), -Fraction::from(1)),
1378                                ])),
1379                                ThresholdConstraint::new(
1380                                    ThresholdCompOp::Geq,
1381                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1382                                    0,
1383                                ),
1384                            )),
1385                        )),
1386                        actions: vec![
1387                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
1388                                .unwrap(),
1389                            Action::new(
1390                                Variable::new("var1"),
1391                                IntegerExpression::BinaryExpr(
1392                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1393                                    IntegerOp::Add,
1394                                    Box::new(IntegerExpression::Const(1)),
1395                                ),
1396                            )
1397                            .unwrap(),
1398                        ],
1399                    }],
1400                ),
1401            ]),
1402            init_variable_constr: vec![],
1403            additional_vars_for_sums: HashMap::new(),
1404        };
1405
1406        let expected = HashSet::from([
1407            SingleAtomConstraint(ThresholdConstraintOver::new(
1408                Variable::new("var1"),
1409                ThresholdConstraint::new(
1410                    ThresholdCompOp::Lt,
1411                    Vec::<(Parameter, Fraction)>::new(),
1412                    2,
1413                ),
1414            )),
1415            SingleAtomConstraint(ThresholdConstraintOver::new(
1416                Variable::new("var1"),
1417                ThresholdConstraint::new(
1418                    ThresholdCompOp::Geq,
1419                    Vec::<(Parameter, Fraction)>::new(),
1420                    1,
1421                ),
1422            )),
1423        ]);
1424        assert_eq!(lta.get_single_var_constraints_rules(), expected)
1425    }
1426
1427    #[test]
1428    fn test_lia_ta_has_comparison_guard() {
1429        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1430            .with_parameters(vec![
1431                Parameter::new("n"),
1432                Parameter::new("t"),
1433                Parameter::new("f"),
1434            ])
1435            .unwrap()
1436            .with_variables(vec![
1437                Variable::new("var1"),
1438                Variable::new("var2"),
1439                Variable::new("var3"),
1440            ])
1441            .unwrap()
1442            .with_locations(vec![
1443                Location::new("loc1"),
1444                Location::new("loc2"),
1445                Location::new("loc3"),
1446            ])
1447            .unwrap()
1448            .initialize()
1449            .with_rules(vec![
1450                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
1451                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1452                    .with_guard(
1453                        BooleanVarConstraint::ComparisonExpression(
1454                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1455                            ComparisonOp::Eq,
1456                            Box::new(IntegerExpression::Const(1)),
1457                        ) & BooleanVarConstraint::ComparisonExpression(
1458                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1459                            ComparisonOp::Eq,
1460                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1461                        ),
1462                    )
1463                    .with_action(Action::new_reset(Variable::new("var3")))
1464                    .with_action(
1465                        Action::new(
1466                            Variable::new("var1"),
1467                            IntegerExpression::Atom(Variable::new("var1"))
1468                                + IntegerExpression::Const(1),
1469                        )
1470                        .unwrap(),
1471                    )
1472                    .build(),
1473            ])
1474            .unwrap()
1475            .with_initial_location_constraint(
1476                LocationConstraint::ComparisonExpression(
1477                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1478                    ComparisonOp::Eq,
1479                    Box::new(
1480                        IntegerExpression::Param(Parameter::new("n"))
1481                            - IntegerExpression::Param(Parameter::new("f")),
1482                    ),
1483                ) | LocationConstraint::ComparisonExpression(
1484                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1485                    ComparisonOp::Eq,
1486                    Box::new(IntegerExpression::Const(0)),
1487                ),
1488            )
1489            .unwrap()
1490            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
1491                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1492                ComparisonOp::Gt,
1493                Box::new(
1494                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
1495                ),
1496            ))
1497            .unwrap()
1498            .build();
1499
1500        let lta = LIAThresholdAutomaton {
1501            ta,
1502            rules: HashMap::from([
1503                (
1504                    Location::new("loc1"),
1505                    vec![LIARule {
1506                        id: 0,
1507                        source: Location::new("loc1"),
1508                        target: Location::new("loc2"),
1509                        guard: LIAVariableConstraint::True,
1510                        actions: vec![],
1511                    }],
1512                ),
1513                (
1514                    Location::new("loc2"),
1515                    vec![LIARule {
1516                        id: 1,
1517                        source: Location::new("loc2"),
1518                        target: Location::new("loc3"),
1519                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
1520                            ThresholdConstraintOver::new(
1521                                Variable::new("var1"),
1522                                ThresholdConstraint::new(
1523                                    ThresholdCompOp::Lt,
1524                                    Vec::<(Parameter, Fraction)>::new(),
1525                                    2,
1526                                ),
1527                            ),
1528                        )) & LIAVariableConstraint::SingleVarConstraint(
1529                            SingleAtomConstraint(ThresholdConstraintOver::new(
1530                                Variable::new("var1"),
1531                                ThresholdConstraint::new(
1532                                    ThresholdCompOp::Geq,
1533                                    Vec::<(Parameter, Fraction)>::new(),
1534                                    1,
1535                                ),
1536                            )),
1537                        )) & (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1538                            ThresholdConstraintOver::new(
1539                                WeightedSum::new(BTreeMap::from([
1540                                    (Variable::new("var2"), 1),
1541                                    (Variable::new("var1"), 1),
1542                                ])),
1543                                ThresholdConstraint::new(
1544                                    ThresholdCompOp::Lt,
1545                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1546                                    1,
1547                                ),
1548                            ),
1549                        )) & LIAVariableConstraint::SumVarConstraint(
1550                            SumAtomConstraint(ThresholdConstraintOver::new(
1551                                WeightedSum::new(BTreeMap::from([
1552                                    (Variable::new("var2"), 1),
1553                                    (Variable::new("var1"), 1),
1554                                ])),
1555                                ThresholdConstraint::new(
1556                                    ThresholdCompOp::Geq,
1557                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1558                                    0,
1559                                ),
1560                            )),
1561                        )) | (LIAVariableConstraint::ComparisonConstraint(
1562                            ComparisonConstraint(ThresholdConstraintOver::new(
1563                                WeightedSum::new(BTreeMap::from([
1564                                    (Variable::new("var3"), 1.into()),
1565                                    (Variable::new("var3"), -Fraction::from(1)),
1566                                ])),
1567                                ThresholdConstraint::new(
1568                                    ThresholdCompOp::Lt,
1569                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1570                                    1,
1571                                ),
1572                            )),
1573                        ) & LIAVariableConstraint::ComparisonConstraint(
1574                            ComparisonConstraint(ThresholdConstraintOver::new(
1575                                WeightedSum::new(BTreeMap::from([
1576                                    (Variable::new("var3"), 1.into()),
1577                                    (Variable::new("var3"), -Fraction::from(1)),
1578                                ])),
1579                                ThresholdConstraint::new(
1580                                    ThresholdCompOp::Geq,
1581                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1582                                    0,
1583                                ),
1584                            )),
1585                        )),
1586                        actions: vec![
1587                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
1588                                .unwrap(),
1589                            Action::new(
1590                                Variable::new("var1"),
1591                                IntegerExpression::BinaryExpr(
1592                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1593                                    IntegerOp::Add,
1594                                    Box::new(IntegerExpression::Const(1)),
1595                                ),
1596                            )
1597                            .unwrap(),
1598                        ],
1599                    }],
1600                ),
1601            ]),
1602            init_variable_constr: vec![],
1603            additional_vars_for_sums: HashMap::new(),
1604        };
1605
1606        assert!(lta.has_comparison_guard());
1607        let expected = HashSet::from([
1608            ComparisonConstraint(ThresholdConstraintOver::new(
1609                WeightedSum::new(BTreeMap::from([
1610                    (Variable::new("var3"), 1.into()),
1611                    (Variable::new("var3"), -Fraction::from(1)),
1612                ])),
1613                ThresholdConstraint::new(
1614                    ThresholdCompOp::Lt,
1615                    BTreeMap::from([(Parameter::new("t"), 3)]),
1616                    1,
1617                ),
1618            )),
1619            ComparisonConstraint(ThresholdConstraintOver::new(
1620                WeightedSum::new(BTreeMap::from([
1621                    (Variable::new("var3"), 1.into()),
1622                    (Variable::new("var3"), -Fraction::from(1)),
1623                ])),
1624                ThresholdConstraint::new(
1625                    ThresholdCompOp::Geq,
1626                    BTreeMap::from([(Parameter::new("t"), 3)]),
1627                    0,
1628                ),
1629            )),
1630        ]);
1631        assert_eq!(lta.get_comparison_guards(), expected);
1632
1633        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1634            .with_parameters(vec![
1635                Parameter::new("n"),
1636                Parameter::new("t"),
1637                Parameter::new("f"),
1638            ])
1639            .unwrap()
1640            .with_variables(vec![
1641                Variable::new("var1"),
1642                Variable::new("var2"),
1643                Variable::new("var3"),
1644            ])
1645            .unwrap()
1646            .with_locations(vec![
1647                Location::new("loc1"),
1648                Location::new("loc2"),
1649                Location::new("loc3"),
1650            ])
1651            .unwrap()
1652            .initialize()
1653            .with_rules(vec![
1654                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
1655                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1656                    .with_guard(
1657                        BooleanVarConstraint::ComparisonExpression(
1658                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1659                            ComparisonOp::Eq,
1660                            Box::new(IntegerExpression::Const(1)),
1661                        ) & BooleanVarConstraint::ComparisonExpression(
1662                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1663                            ComparisonOp::Eq,
1664                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1665                        ),
1666                    )
1667                    .with_action(Action::new_reset(Variable::new("var3")))
1668                    .with_action(
1669                        Action::new(
1670                            Variable::new("var1"),
1671                            IntegerExpression::Atom(Variable::new("var1"))
1672                                + IntegerExpression::Const(1),
1673                        )
1674                        .unwrap(),
1675                    )
1676                    .build(),
1677            ])
1678            .unwrap()
1679            .with_initial_location_constraint(
1680                LocationConstraint::ComparisonExpression(
1681                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1682                    ComparisonOp::Eq,
1683                    Box::new(
1684                        IntegerExpression::Param(Parameter::new("n"))
1685                            - IntegerExpression::Param(Parameter::new("f")),
1686                    ),
1687                ) | LocationConstraint::ComparisonExpression(
1688                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1689                    ComparisonOp::Eq,
1690                    Box::new(IntegerExpression::Const(0)),
1691                ),
1692            )
1693            .unwrap()
1694            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
1695                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1696                ComparisonOp::Gt,
1697                Box::new(
1698                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
1699                ),
1700            ))
1701            .unwrap()
1702            .build();
1703
1704        let lta = LIAThresholdAutomaton {
1705            ta,
1706            rules: HashMap::from([
1707                (
1708                    Location::new("loc1"),
1709                    vec![LIARule {
1710                        id: 0,
1711                        source: Location::new("loc1"),
1712                        target: Location::new("loc2"),
1713                        guard: LIAVariableConstraint::True,
1714                        actions: vec![],
1715                    }],
1716                ),
1717                (
1718                    Location::new("loc2"),
1719                    vec![LIARule {
1720                        id: 1,
1721                        source: Location::new("loc2"),
1722                        target: Location::new("loc3"),
1723                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
1724                            ThresholdConstraintOver::new(
1725                                Variable::new("var1"),
1726                                ThresholdConstraint::new(
1727                                    ThresholdCompOp::Lt,
1728                                    Vec::<(Parameter, Fraction)>::new(),
1729                                    2,
1730                                ),
1731                            ),
1732                        )) & LIAVariableConstraint::SingleVarConstraint(
1733                            SingleAtomConstraint(ThresholdConstraintOver::new(
1734                                Variable::new("var1"),
1735                                ThresholdConstraint::new(
1736                                    ThresholdCompOp::Geq,
1737                                    Vec::<(Parameter, Fraction)>::new(),
1738                                    1,
1739                                ),
1740                            )),
1741                        )) & (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1742                            ThresholdConstraintOver::new(
1743                                WeightedSum::new(BTreeMap::from([
1744                                    (Variable::new("var2"), 1),
1745                                    (Variable::new("var1"), 1),
1746                                ])),
1747                                ThresholdConstraint::new(
1748                                    ThresholdCompOp::Lt,
1749                                    [(Parameter::new("n"), 1)],
1750                                    1,
1751                                ),
1752                            ),
1753                        )) & LIAVariableConstraint::SumVarConstraint(
1754                            SumAtomConstraint(ThresholdConstraintOver::new(
1755                                WeightedSum::new(BTreeMap::from([
1756                                    (Variable::new("var2"), 1),
1757                                    (Variable::new("var1"), 1),
1758                                ])),
1759                                ThresholdConstraint::new(
1760                                    ThresholdCompOp::Geq,
1761                                    [(Parameter::new("n"), 1)],
1762                                    0,
1763                                ),
1764                            )),
1765                        )),
1766                        actions: vec![
1767                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
1768                                .unwrap(),
1769                            Action::new(
1770                                Variable::new("var1"),
1771                                IntegerExpression::BinaryExpr(
1772                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1773                                    IntegerOp::Add,
1774                                    Box::new(IntegerExpression::Const(1)),
1775                                ),
1776                            )
1777                            .unwrap(),
1778                        ],
1779                    }],
1780                ),
1781            ]),
1782            init_variable_constr: vec![],
1783            additional_vars_for_sums: HashMap::new(),
1784        };
1785
1786        assert!(!lta.has_comparison_guard());
1787        assert!(lta.get_comparison_guards().is_empty());
1788    }
1789
1790    #[test]
1791    fn test_lia_has_multi_var() {
1792        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1793            .with_parameters(vec![
1794                Parameter::new("n"),
1795                Parameter::new("t"),
1796                Parameter::new("f"),
1797            ])
1798            .unwrap()
1799            .with_variables(vec![
1800                Variable::new("var1"),
1801                Variable::new("var2"),
1802                Variable::new("var3"),
1803            ])
1804            .unwrap()
1805            .with_locations(vec![
1806                Location::new("loc1"),
1807                Location::new("loc2"),
1808                Location::new("loc3"),
1809            ])
1810            .unwrap()
1811            .initialize()
1812            .with_rules(vec![
1813                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
1814                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1815                    .with_guard(
1816                        BooleanVarConstraint::ComparisonExpression(
1817                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1818                            ComparisonOp::Eq,
1819                            Box::new(IntegerExpression::Const(1)),
1820                        ) & BooleanVarConstraint::ComparisonExpression(
1821                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1822                            ComparisonOp::Eq,
1823                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1824                        ),
1825                    )
1826                    .with_action(Action::new_reset(Variable::new("var3")))
1827                    .with_action(
1828                        Action::new(
1829                            Variable::new("var1"),
1830                            IntegerExpression::Atom(Variable::new("var1"))
1831                                + IntegerExpression::Const(1),
1832                        )
1833                        .unwrap(),
1834                    )
1835                    .build(),
1836            ])
1837            .unwrap()
1838            .with_initial_location_constraint(
1839                LocationConstraint::ComparisonExpression(
1840                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
1841                    ComparisonOp::Eq,
1842                    Box::new(
1843                        IntegerExpression::Param(Parameter::new("n"))
1844                            - IntegerExpression::Param(Parameter::new("f")),
1845                    ),
1846                ) | LocationConstraint::ComparisonExpression(
1847                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
1848                    ComparisonOp::Eq,
1849                    Box::new(IntegerExpression::Const(0)),
1850                ),
1851            )
1852            .unwrap()
1853            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
1854                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1855                ComparisonOp::Gt,
1856                Box::new(
1857                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
1858                ),
1859            ))
1860            .unwrap()
1861            .build();
1862
1863        let lta = LIAThresholdAutomaton {
1864            ta,
1865            rules: HashMap::from([
1866                (
1867                    Location::new("loc1"),
1868                    vec![LIARule {
1869                        id: 0,
1870                        source: Location::new("loc1"),
1871                        target: Location::new("loc2"),
1872                        guard: LIAVariableConstraint::True,
1873                        actions: vec![],
1874                    }],
1875                ),
1876                (
1877                    Location::new("loc2"),
1878                    vec![LIARule {
1879                        id: 1,
1880                        source: Location::new("loc2"),
1881                        target: Location::new("loc3"),
1882                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
1883                            ThresholdConstraintOver::new(
1884                                Variable::new("var1"),
1885                                ThresholdConstraint::new(
1886                                    ThresholdCompOp::Lt,
1887                                    Vec::<(Parameter, Fraction)>::new(),
1888                                    2,
1889                                ),
1890                            ),
1891                        )) & LIAVariableConstraint::SingleVarConstraint(
1892                            SingleAtomConstraint(ThresholdConstraintOver::new(
1893                                Variable::new("var1"),
1894                                ThresholdConstraint::new(
1895                                    ThresholdCompOp::Geq,
1896                                    Vec::<(Parameter, Fraction)>::new(),
1897                                    1,
1898                                ),
1899                            )),
1900                        )) & (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
1901                            ThresholdConstraintOver::new(
1902                                WeightedSum::new(BTreeMap::from([
1903                                    (Variable::new("var2"), 1),
1904                                    (Variable::new("var1"), 1),
1905                                ])),
1906                                ThresholdConstraint::new(
1907                                    ThresholdCompOp::Lt,
1908                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1909                                    1,
1910                                ),
1911                            ),
1912                        )) & LIAVariableConstraint::SumVarConstraint(
1913                            SumAtomConstraint(ThresholdConstraintOver::new(
1914                                WeightedSum::new(BTreeMap::from([
1915                                    (Variable::new("var2"), 1),
1916                                    (Variable::new("var1"), 1),
1917                                ])),
1918                                ThresholdConstraint::new(
1919                                    ThresholdCompOp::Geq,
1920                                    BTreeMap::from([(Parameter::new("n"), 1)]),
1921                                    0,
1922                                ),
1923                            )),
1924                        )) | (LIAVariableConstraint::ComparisonConstraint(
1925                            ComparisonConstraint(ThresholdConstraintOver::new(
1926                                WeightedSum::new(BTreeMap::from([
1927                                    (Variable::new("var3"), 1.into()),
1928                                    (Variable::new("var3"), -Fraction::from(1)),
1929                                ])),
1930                                ThresholdConstraint::new(
1931                                    ThresholdCompOp::Lt,
1932                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1933                                    1,
1934                                ),
1935                            )),
1936                        ) & LIAVariableConstraint::ComparisonConstraint(
1937                            ComparisonConstraint(ThresholdConstraintOver::new(
1938                                WeightedSum::new(BTreeMap::from([
1939                                    (Variable::new("var3"), 1.into()),
1940                                    (Variable::new("var3"), -Fraction::from(1)),
1941                                ])),
1942                                ThresholdConstraint::new(
1943                                    ThresholdCompOp::Geq,
1944                                    BTreeMap::from([(Parameter::new("t"), 3)]),
1945                                    0,
1946                                ),
1947                            )),
1948                        )),
1949                        actions: vec![
1950                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
1951                                .unwrap(),
1952                            Action::new(
1953                                Variable::new("var1"),
1954                                IntegerExpression::BinaryExpr(
1955                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1956                                    IntegerOp::Add,
1957                                    Box::new(IntegerExpression::Const(1)),
1958                                ),
1959                            )
1960                            .unwrap(),
1961                        ],
1962                    }],
1963                ),
1964            ]),
1965            init_variable_constr: vec![],
1966            additional_vars_for_sums: HashMap::new(),
1967        };
1968
1969        assert!(lta.has_sum_var_threshold_guard());
1970
1971        let expected = HashSet::from([
1972            SumAtomConstraint(ThresholdConstraintOver::new(
1973                WeightedSum::new(BTreeMap::from([
1974                    (Variable::new("var2"), 1),
1975                    (Variable::new("var1"), 1),
1976                ])),
1977                ThresholdConstraint::new(
1978                    ThresholdCompOp::Geq,
1979                    BTreeMap::from([(Parameter::new("n"), 1)]),
1980                    0,
1981                ),
1982            )),
1983            SumAtomConstraint(ThresholdConstraintOver::new(
1984                WeightedSum::new(BTreeMap::from([
1985                    (Variable::new("var2"), 1),
1986                    (Variable::new("var1"), 1),
1987                ])),
1988                ThresholdConstraint::new(
1989                    ThresholdCompOp::Lt,
1990                    BTreeMap::from([(Parameter::new("n"), 1)]),
1991                    1,
1992                ),
1993            )),
1994        ]);
1995        assert_eq!(lta.get_sum_var_constraints(), expected);
1996
1997        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
1998            .with_parameters(vec![
1999                Parameter::new("n"),
2000                Parameter::new("t"),
2001                Parameter::new("f"),
2002            ])
2003            .unwrap()
2004            .with_variables(vec![
2005                Variable::new("var1"),
2006                Variable::new("var2"),
2007                Variable::new("var3"),
2008            ])
2009            .unwrap()
2010            .with_locations(vec![
2011                Location::new("loc1"),
2012                Location::new("loc2"),
2013                Location::new("loc3"),
2014            ])
2015            .unwrap()
2016            .initialize()
2017            .with_rules(vec![
2018                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
2019                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
2020                    .with_guard(
2021                        BooleanVarConstraint::ComparisonExpression(
2022                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2023                            ComparisonOp::Eq,
2024                            Box::new(IntegerExpression::Const(1)),
2025                        ) & BooleanVarConstraint::ComparisonExpression(
2026                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2027                            ComparisonOp::Eq,
2028                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2029                        ),
2030                    )
2031                    .with_action(Action::new_reset(Variable::new("var3")))
2032                    .with_action(
2033                        Action::new(
2034                            Variable::new("var1"),
2035                            IntegerExpression::Atom(Variable::new("var1"))
2036                                + IntegerExpression::Const(1),
2037                        )
2038                        .unwrap(),
2039                    )
2040                    .build(),
2041            ])
2042            .unwrap()
2043            .with_initial_location_constraint(
2044                LocationConstraint::ComparisonExpression(
2045                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
2046                    ComparisonOp::Eq,
2047                    Box::new(
2048                        IntegerExpression::Param(Parameter::new("n"))
2049                            - IntegerExpression::Param(Parameter::new("f")),
2050                    ),
2051                ) | LocationConstraint::ComparisonExpression(
2052                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
2053                    ComparisonOp::Eq,
2054                    Box::new(IntegerExpression::Const(0)),
2055                ),
2056            )
2057            .unwrap()
2058            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
2059                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
2060                ComparisonOp::Gt,
2061                Box::new(
2062                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
2063                ),
2064            ))
2065            .unwrap()
2066            .build();
2067
2068        let lta = LIAThresholdAutomaton {
2069            ta,
2070            rules: HashMap::from([
2071                (
2072                    Location::new("loc1"),
2073                    vec![LIARule {
2074                        id: 0,
2075                        source: Location::new("loc1"),
2076                        target: Location::new("loc2"),
2077                        guard: LIAVariableConstraint::True,
2078                        actions: vec![],
2079                    }],
2080                ),
2081                (
2082                    Location::new("loc2"),
2083                    vec![LIARule {
2084                        id: 1,
2085                        source: Location::new("loc2"),
2086                        target: Location::new("loc3"),
2087                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
2088                            ThresholdConstraintOver::new(
2089                                Variable::new("var1"),
2090                                ThresholdConstraint::new(
2091                                    ThresholdCompOp::Lt,
2092                                    Vec::<(Parameter, Fraction)>::new(),
2093                                    2,
2094                                ),
2095                            ),
2096                        )) & LIAVariableConstraint::SingleVarConstraint(
2097                            SingleAtomConstraint(ThresholdConstraintOver::new(
2098                                Variable::new("var1"),
2099                                ThresholdConstraint::new(
2100                                    ThresholdCompOp::Geq,
2101                                    Vec::<(Parameter, Fraction)>::new(),
2102                                    1,
2103                                ),
2104                            )),
2105                        )) | (LIAVariableConstraint::ComparisonConstraint(
2106                            ComparisonConstraint(ThresholdConstraintOver::new(
2107                                WeightedSum::new(BTreeMap::from([
2108                                    (Variable::new("var3"), 1.into()),
2109                                    (Variable::new("var3"), -Fraction::from(1)),
2110                                ])),
2111                                ThresholdConstraint::new(
2112                                    ThresholdCompOp::Lt,
2113                                    BTreeMap::from([(Parameter::new("t"), 3)]),
2114                                    1,
2115                                ),
2116                            )),
2117                        ) & LIAVariableConstraint::ComparisonConstraint(
2118                            ComparisonConstraint(ThresholdConstraintOver::new(
2119                                WeightedSum::new(BTreeMap::from([
2120                                    (Variable::new("var3"), 1.into()),
2121                                    (Variable::new("var3"), -Fraction::from(1)),
2122                                ])),
2123                                ThresholdConstraint::new(
2124                                    ThresholdCompOp::Geq,
2125                                    BTreeMap::from([(Parameter::new("t"), 3)]),
2126                                    0,
2127                                ),
2128                            )),
2129                        )),
2130                        actions: vec![
2131                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
2132                                .unwrap(),
2133                            Action::new(
2134                                Variable::new("var1"),
2135                                IntegerExpression::BinaryExpr(
2136                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2137                                    IntegerOp::Add,
2138                                    Box::new(IntegerExpression::Const(1)),
2139                                ),
2140                            )
2141                            .unwrap(),
2142                        ],
2143                    }],
2144                ),
2145            ]),
2146            init_variable_constr: vec![],
2147            additional_vars_for_sums: HashMap::new(),
2148        };
2149
2150        assert!(!lta.has_sum_var_threshold_guard());
2151        assert!(lta.get_sum_var_constraints().is_empty());
2152    }
2153
2154    #[test]
2155    fn test_lia_display() {
2156        let ta = GeneralThresholdAutomatonBuilder::new("test_ta1")
2157            .with_parameters(vec![
2158                Parameter::new("n"),
2159                Parameter::new("t"),
2160                Parameter::new("f"),
2161            ])
2162            .unwrap()
2163            .with_variables(vec![
2164                Variable::new("var1"),
2165                Variable::new("var2"),
2166                Variable::new("var3"),
2167            ])
2168            .unwrap()
2169            .with_locations(vec![
2170                Location::new("loc1"),
2171                Location::new("loc2"),
2172                Location::new("loc3"),
2173            ])
2174            .unwrap()
2175            .initialize()
2176            .with_rules(vec![
2177                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
2178                RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
2179                    .with_guard(
2180                        BooleanVarConstraint::ComparisonExpression(
2181                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2182                            ComparisonOp::Eq,
2183                            Box::new(IntegerExpression::Const(1)),
2184                        ) & BooleanVarConstraint::ComparisonExpression(
2185                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2186                            ComparisonOp::Eq,
2187                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2188                        ),
2189                    )
2190                    .with_action(Action::new_reset(Variable::new("var3")))
2191                    .with_action(
2192                        Action::new(
2193                            Variable::new("var1"),
2194                            IntegerExpression::Atom(Variable::new("var1"))
2195                                + IntegerExpression::Const(1),
2196                        )
2197                        .unwrap(),
2198                    )
2199                    .build(),
2200            ])
2201            .unwrap()
2202            .with_initial_location_constraint(
2203                LocationConstraint::ComparisonExpression(
2204                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
2205                    ComparisonOp::Eq,
2206                    Box::new(
2207                        IntegerExpression::Param(Parameter::new("n"))
2208                            - IntegerExpression::Param(Parameter::new("f")),
2209                    ),
2210                ) | LocationConstraint::ComparisonExpression(
2211                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
2212                    ComparisonOp::Eq,
2213                    Box::new(IntegerExpression::Const(0)),
2214                ),
2215            )
2216            .unwrap()
2217            .with_initial_variable_constraint(BooleanVarConstraint::ComparisonExpression(
2218                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2219                ComparisonOp::Eq,
2220                Box::new(IntegerExpression::Const(1)),
2221            ))
2222            .unwrap()
2223            .with_resilience_condition(ParameterConstraint::ComparisonExpression(
2224                Box::new(IntegerExpression::Atom(Parameter::new("n"))),
2225                ComparisonOp::Gt,
2226                Box::new(
2227                    IntegerExpression::Const(3) * IntegerExpression::Atom(Parameter::new("f")),
2228                ),
2229            ))
2230            .unwrap()
2231            .build();
2232
2233        let lta = LIAThresholdAutomaton {
2234            ta,
2235            rules: HashMap::from([
2236                (
2237                    Location::new("loc1"),
2238                    vec![LIARule {
2239                        id: 0,
2240                        source: Location::new("loc1"),
2241                        target: Location::new("loc2"),
2242                        guard: LIAVariableConstraint::True,
2243                        actions: vec![],
2244                    }],
2245                ),
2246                (
2247                    Location::new("loc2"),
2248                    vec![LIARule {
2249                        id: 1,
2250                        source: Location::new("loc2"),
2251                        target: Location::new("loc3"),
2252                        guard: (LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
2253                            ThresholdConstraintOver::new(
2254                                Variable::new("var1"),
2255                                ThresholdConstraint::new(
2256                                    ThresholdCompOp::Lt,
2257                                    Vec::<(Parameter, Fraction)>::new(),
2258                                    2,
2259                                ),
2260                            ),
2261                        )) & LIAVariableConstraint::SingleVarConstraint(
2262                            SingleAtomConstraint(ThresholdConstraintOver::new(
2263                                Variable::new("var1"),
2264                                ThresholdConstraint::new(
2265                                    ThresholdCompOp::Geq,
2266                                    Vec::<(Parameter, Fraction)>::new(),
2267                                    1,
2268                                ),
2269                            )),
2270                        )) & (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2271                            ThresholdConstraintOver::new(
2272                                WeightedSum::new(BTreeMap::from([
2273                                    (Variable::new("var2"), 1),
2274                                    (Variable::new("var1"), 1),
2275                                ])),
2276                                ThresholdConstraint::new(
2277                                    ThresholdCompOp::Lt,
2278                                    BTreeMap::from([(Parameter::new("n"), 1)]),
2279                                    1,
2280                                ),
2281                            ),
2282                        )) & LIAVariableConstraint::SumVarConstraint(
2283                            SumAtomConstraint(ThresholdConstraintOver::new(
2284                                WeightedSum::new(BTreeMap::from([
2285                                    (Variable::new("var2"), 1),
2286                                    (Variable::new("var1"), 1),
2287                                ])),
2288                                ThresholdConstraint::new(
2289                                    ThresholdCompOp::Geq,
2290                                    BTreeMap::from([(Parameter::new("n"), 1)]),
2291                                    0,
2292                                ),
2293                            )),
2294                        )) | (LIAVariableConstraint::ComparisonConstraint(
2295                            ComparisonConstraint(ThresholdConstraintOver::new(
2296                                WeightedSum::new(BTreeMap::from([
2297                                    (Variable::new("var3"), 1.into()),
2298                                    (Variable::new("var3"), -Fraction::from(1)),
2299                                ])),
2300                                ThresholdConstraint::new(
2301                                    ThresholdCompOp::Lt,
2302                                    BTreeMap::from([(Parameter::new("t"), 3)]),
2303                                    1,
2304                                ),
2305                            )),
2306                        ) & LIAVariableConstraint::ComparisonConstraint(
2307                            ComparisonConstraint(ThresholdConstraintOver::new(
2308                                WeightedSum::new(BTreeMap::from([
2309                                    (Variable::new("var3"), 1.into()),
2310                                    (Variable::new("var3"), -Fraction::from(1)),
2311                                ])),
2312                                ThresholdConstraint::new(
2313                                    ThresholdCompOp::Geq,
2314                                    BTreeMap::from([(Parameter::new("t"), 3)]),
2315                                    0,
2316                                ),
2317                            )),
2318                        )),
2319
2320                        actions: vec![
2321                            Action::new(Variable::new("var3"), IntegerExpression::Const(0))
2322                                .unwrap(),
2323                            Action::new(
2324                                Variable::new("var1"),
2325                                IntegerExpression::BinaryExpr(
2326                                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2327                                    IntegerOp::Add,
2328                                    Box::new(IntegerExpression::Const(1)),
2329                                ),
2330                            )
2331                            .unwrap(),
2332                        ],
2333                    }],
2334                ),
2335            ]),
2336            init_variable_constr: vec![
2337                LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
2338                    ThresholdConstraintOver::new(
2339                        Variable::new("var1"),
2340                        ThresholdConstraint::new(
2341                            ThresholdCompOp::Lt,
2342                            Vec::<(Parameter, Fraction)>::new(),
2343                            Fraction::from(2),
2344                        ),
2345                    ),
2346                )),
2347                LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
2348                    ThresholdConstraintOver::new(
2349                        Variable::new("var1"),
2350                        ThresholdConstraint::new(
2351                            ThresholdCompOp::Geq,
2352                            Vec::<(Parameter, Fraction)>::new(),
2353                            Fraction::from(1),
2354                        ),
2355                    ),
2356                )),
2357            ],
2358            additional_vars_for_sums: HashMap::new(),
2359        };
2360
2361        let expected = "thresholdAutomaton test_ta1 {\n    shared var1, var2, var3;\n\n    parameters f, n, t;\n\n    assumptions (1) {\n        n > (3 * f);\n    }\n\n    locations (3) {\n        loc1:[0];\n        loc2:[1];\n        loc3:[2];\n    }\n\n    inits (3) {\n        (loc1 == (n - f) || loc2 == 0);\n        var1 < 2;\n        var1 >= 1;\n    }\n\n    rules (2) {\n        0: loc1 -> loc2\n            when ( true )\n            do {  };\n        1: loc2 -> loc3\n            when ( (((var1 < 2) && (var1 >= 1)) && ((var1 + var2 < n + 1) && (var1 + var2 >= n))) || ((-1 * var3 < 3 * t + 1) && (-1 * var3 >= 3 * t)) )\n            do { var3' == 0; var1' == var1 + 1 };\n    }\n}\n";
2362
2363        assert_eq!(lta.to_string(), expected)
2364    }
2365
2366    #[test]
2367    fn test_lia_rule_getters() {
2368        let rule = LIARule {
2369            id: 0,
2370            source: Location::new("loc1"),
2371            target: Location::new("loc2"),
2372            guard: LIAVariableConstraint::True,
2373            actions: vec![],
2374        };
2375
2376        assert_eq!(rule.id(), 0);
2377        assert_eq!(rule.source(), &Location::new("loc1"));
2378        assert_eq!(rule.target(), &Location::new("loc2"));
2379        assert_eq!(rule.guard(), &LIAVariableConstraint::True);
2380        assert_eq!(rule.actions().count(), 0);
2381        assert_eq!(rule.number_of_updates(), 0);
2382        assert!(!rule.has_resets());
2383        assert!(!rule.has_decrements());
2384
2385        let rule = LIARule {
2386            id: 0,
2387            source: Location::new("loc1"),
2388            target: Location::new("loc2"),
2389            guard: LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2390                ThresholdConstraintOver::new(
2391                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2392                    ThresholdConstraint::new(
2393                        ThresholdCompOp::Lt,
2394                        Vec::<(Parameter, Fraction)>::new(),
2395                        2,
2396                    ),
2397                ),
2398            )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2399                ThresholdConstraintOver::new(
2400                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2401                    ThresholdConstraint::new(
2402                        ThresholdCompOp::Geq,
2403                        Vec::<(Parameter, Fraction)>::new(),
2404                        1,
2405                    ),
2406                ),
2407            )),
2408            actions: vec![
2409                Action::new(
2410                    Variable::new("var1"),
2411                    IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1),
2412                )
2413                .unwrap(),
2414            ],
2415        };
2416
2417        assert_eq!(rule.id(), 0);
2418        assert_eq!(rule.source(), &Location::new("loc1"));
2419        assert_eq!(rule.target(), &Location::new("loc2"));
2420        assert_eq!(
2421            rule.guard(),
2422            &(LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2423                ThresholdConstraintOver::new(
2424                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2425                    ThresholdConstraint::new(
2426                        ThresholdCompOp::Lt,
2427                        Vec::<(Parameter, Fraction)>::new(),
2428                        2,
2429                    ),
2430                ),
2431            )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2432                ThresholdConstraintOver::new(
2433                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2434                    ThresholdConstraint::new(
2435                        ThresholdCompOp::Geq,
2436                        Vec::<(Parameter, Fraction)>::new(),
2437                        1,
2438                    ),
2439                ),
2440            ))),
2441        );
2442        assert_eq!(
2443            rule.actions().cloned().collect::<Vec<_>>(),
2444            vec![
2445                Action::new(
2446                    Variable::new("var1"),
2447                    IntegerExpression::Atom(Variable::new("var1")) - IntegerExpression::Const(1)
2448                )
2449                .unwrap()
2450            ]
2451        );
2452        assert_eq!(rule.number_of_updates(), 1);
2453        assert!(!rule.has_resets());
2454        assert!(rule.has_decrements());
2455
2456        let rule = LIARule {
2457            id: 2,
2458            source: Location::new("loc1"),
2459            target: Location::new("loc2"),
2460            guard: (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2461                ThresholdConstraintOver::new(
2462                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2463                    ThresholdConstraint::new(
2464                        ThresholdCompOp::Lt,
2465                        Vec::<(Parameter, Fraction)>::new(),
2466                        2,
2467                    ),
2468                ),
2469            )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2470                ThresholdConstraintOver::new(
2471                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2472                    ThresholdConstraint::new(
2473                        ThresholdCompOp::Geq,
2474                        Vec::<(Parameter, Fraction)>::new(),
2475                        1,
2476                    ),
2477                ),
2478            ))),
2479            actions: vec![Action::new(Variable::new("var1"), IntegerExpression::Const(0)).unwrap()],
2480        };
2481
2482        assert_eq!(rule.id(), 2);
2483        assert_eq!(rule.source(), &Location::new("loc1"));
2484        assert_eq!(rule.target(), &Location::new("loc2"));
2485        assert_eq!(
2486            rule.guard(),
2487            &(LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2488                ThresholdConstraintOver::new(
2489                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2490                    ThresholdConstraint::new(
2491                        ThresholdCompOp::Lt,
2492                        Vec::<(Parameter, Fraction)>::new(),
2493                        2,
2494                    ),
2495                ),
2496            )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2497                ThresholdConstraintOver::new(
2498                    WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2499                    ThresholdConstraint::new(
2500                        ThresholdCompOp::Geq,
2501                        Vec::<(Parameter, Fraction)>::new(),
2502                        1,
2503                    ),
2504                ),
2505            )))
2506        );
2507        assert_eq!(
2508            rule.actions().cloned().collect::<Vec<_>>(),
2509            vec![Action::new(Variable::new("var1"), IntegerExpression::Const(0)).unwrap()]
2510        );
2511        assert_eq!(rule.number_of_updates(), 1);
2512        assert!(rule.has_resets());
2513        assert!(!rule.has_decrements());
2514    }
2515
2516    #[test]
2517    fn test_count_number_of_thresholds() {
2518        let thr = LIAVariableConstraint::False;
2519        assert_eq!(thr.count_number_of_thresholds(), 0);
2520
2521        let thr = LIAVariableConstraint::True;
2522        assert_eq!(thr.count_number_of_thresholds(), 0);
2523
2524        let thr = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2525            ThresholdConstraintOver::new(
2526                WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2527                ThresholdConstraint::new(
2528                    ThresholdCompOp::Lt,
2529                    Vec::<(Parameter, Fraction)>::new(),
2530                    2,
2531                ),
2532            ),
2533        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2534            ThresholdConstraintOver::new(
2535                WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2536                ThresholdConstraint::new(
2537                    ThresholdCompOp::Geq,
2538                    Vec::<(Parameter, Fraction)>::new(),
2539                    1,
2540                ),
2541            ),
2542        ));
2543        assert_eq!(thr.count_number_of_thresholds(), 2);
2544
2545        let thr = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2546            ThresholdConstraintOver::new(
2547                WeightedSum::new(BTreeMap::from([
2548                    (Variable::new("var1"), 1),
2549                    (Variable::new("var2"), 2),
2550                ])),
2551                ThresholdConstraint::new(
2552                    ThresholdCompOp::Lt,
2553                    BTreeMap::from([(Parameter::new("n"), 3)]),
2554                    6,
2555                ),
2556            ),
2557        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2558            ThresholdConstraintOver::new(
2559                WeightedSum::new(BTreeMap::from([
2560                    (Variable::new("var1"), 1),
2561                    (Variable::new("var2"), 2),
2562                ])),
2563                ThresholdConstraint::new(
2564                    ThresholdCompOp::Geq,
2565                    BTreeMap::from([(Parameter::new("n"), 3)]),
2566                    5,
2567                ),
2568            ),
2569        ));
2570        assert_eq!(thr.count_number_of_thresholds(), 2);
2571
2572        let thr = (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2573            ThresholdConstraintOver::new(
2574                WeightedSum::new(BTreeMap::from([
2575                    (Variable::new("var1"), 1),
2576                    (Variable::new("var2"), 2),
2577                ])),
2578                ThresholdConstraint::new(
2579                    ThresholdCompOp::Lt,
2580                    BTreeMap::from([(Parameter::new("n"), 3)]),
2581                    1,
2582                ),
2583            ),
2584        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2585            ThresholdConstraintOver::new(
2586                WeightedSum::new(BTreeMap::from([
2587                    (Variable::new("var1"), 1),
2588                    (Variable::new("var2"), 2),
2589                ])),
2590                ThresholdConstraint::new(
2591                    ThresholdCompOp::Geq,
2592                    BTreeMap::from([(Parameter::new("n"), 3)]),
2593                    0,
2594                ),
2595            ),
2596        ))) | (LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2597            ThresholdConstraintOver::new(
2598                WeightedSum::new(BTreeMap::from([(Variable::new("var3"), 1)])),
2599                ThresholdConstraint::new(
2600                    ThresholdCompOp::Lt,
2601                    BTreeMap::from([(Parameter::new("m"), 3)]),
2602                    1,
2603                ),
2604            ),
2605        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2606            ThresholdConstraintOver::new(
2607                WeightedSum::new(BTreeMap::from([(Variable::new("var3"), 1)])),
2608                ThresholdConstraint::new(
2609                    ThresholdCompOp::Geq,
2610                    BTreeMap::from([(Parameter::new("m"), 3)]),
2611                    0,
2612                ),
2613            ),
2614        )));
2615        assert_eq!(thr.count_number_of_thresholds(), 4);
2616    }
2617
2618    #[test]
2619    fn test_bin_op_threshold_guard() {
2620        let thr = LIAVariableConstraint::False & LIAVariableConstraint::True;
2621        assert_eq!(
2622            thr,
2623            LIAVariableConstraint::BinaryGuard(
2624                Box::new(LIAVariableConstraint::False),
2625                BooleanConnective::And,
2626                Box::new(LIAVariableConstraint::True)
2627            )
2628        );
2629
2630        let thr = LIAVariableConstraint::False | LIAVariableConstraint::True;
2631        assert_eq!(
2632            thr,
2633            LIAVariableConstraint::BinaryGuard(
2634                Box::new(LIAVariableConstraint::False),
2635                BooleanConnective::Or,
2636                Box::new(LIAVariableConstraint::True)
2637            )
2638        );
2639    }
2640
2641    #[test]
2642    fn test_is_top() {
2643        assert!(LIAVariableConstraint::True.is_top());
2644        assert!(!LIAVariableConstraint::False.is_top());
2645
2646        // A conjunction is top only if both conjuncts are top
2647        let guard = LIAVariableConstraint::True & LIAVariableConstraint::True;
2648        assert!(guard.is_top());
2649
2650        let guard = LIAVariableConstraint::True & LIAVariableConstraint::False;
2651        assert!(!guard.is_top());
2652
2653        // A disjunction is top if at least one disjunct is top
2654        let guard = LIAVariableConstraint::False | LIAVariableConstraint::True;
2655        assert!(guard.is_top());
2656
2657        let guard = LIAVariableConstraint::False | LIAVariableConstraint::False;
2658        assert!(!guard.is_top());
2659
2660        // Propagation through nested binary guards
2661        let guard = (LIAVariableConstraint::True & LIAVariableConstraint::False)
2662            | LIAVariableConstraint::True;
2663        assert!(guard.is_top());
2664
2665        // Atomic constraints are never top (no semantic analysis)
2666        let guard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
2667            Variable::new("var1"),
2668            ThresholdConstraint::new(ThresholdCompOp::Geq, Vec::<(Parameter, Fraction)>::new(), 0),
2669        ));
2670        assert!(!guard.is_top());
2671    }
2672
2673    #[test]
2674    fn test_is_bot() {
2675        assert!(LIAVariableConstraint::False.is_bot());
2676        assert!(!LIAVariableConstraint::True.is_bot());
2677
2678        // A conjunction is bot if at least one conjunct is bot
2679        let guard = LIAVariableConstraint::False & LIAVariableConstraint::True;
2680        assert!(guard.is_bot());
2681
2682        let guard = LIAVariableConstraint::True & LIAVariableConstraint::True;
2683        assert!(!guard.is_bot());
2684
2685        // A disjunction is bot only if both disjuncts are bot
2686        let guard = LIAVariableConstraint::False | LIAVariableConstraint::False;
2687        assert!(guard.is_bot());
2688
2689        let guard = LIAVariableConstraint::False | LIAVariableConstraint::True;
2690        assert!(!guard.is_bot());
2691
2692        // Propagation through nested binary guards
2693        let guard = (LIAVariableConstraint::True | LIAVariableConstraint::False)
2694            & LIAVariableConstraint::False;
2695        assert!(guard.is_bot());
2696
2697        // Atomic constraints are never bot (no semantic analysis)
2698        let guard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
2699            Variable::new("var1"),
2700            ThresholdConstraint::new(ThresholdCompOp::Geq, Vec::<(Parameter, Fraction)>::new(), 0),
2701        ));
2702        assert!(!guard.is_bot());
2703    }
2704
2705    #[test]
2706    fn test_guards_to_boolean() {
2707        let guard = LIAVariableConstraint::True;
2708        assert_eq!(guard.as_boolean_expr(), BooleanExpression::True);
2709
2710        let guard = LIAVariableConstraint::False;
2711        assert_eq!(guard.as_boolean_expr(), BooleanExpression::False);
2712
2713        let guard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint(
2714            ThresholdConstraintOver::new(
2715                Variable::new("var1"),
2716                ThresholdConstraint::new(
2717                    ThresholdCompOp::Lt,
2718                    Vec::<(Parameter, Fraction)>::new(),
2719                    1,
2720                ),
2721            ),
2722        ));
2723        assert_eq!(
2724            guard.as_boolean_expr(),
2725            BooleanExpression::ComparisonExpression(
2726                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2727                ComparisonOp::Lt,
2728                Box::new(IntegerExpression::Const(1))
2729            )
2730        );
2731
2732        let guard = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2733            ThresholdConstraintOver::new(
2734                WeightedSum::new([(Variable::new("var1"), 1), (Variable::new("var2"), 2)]),
2735                ThresholdConstraint::new(
2736                    ThresholdCompOp::Lt,
2737                    BTreeMap::from([(Parameter::new("n"), 3)]),
2738                    5,
2739                ),
2740            ),
2741        ));
2742        assert_eq!(
2743            guard.as_boolean_expr(),
2744            BooleanExpression::ComparisonExpression(
2745                Box::new(IntegerExpression::BinaryExpr(
2746                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2747                    IntegerOp::Add,
2748                    Box::new(IntegerExpression::BinaryExpr(
2749                        Box::new(IntegerExpression::Const(2)),
2750                        IntegerOp::Mul,
2751                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2752                    ))
2753                )),
2754                ComparisonOp::Lt,
2755                Box::new(
2756                    IntegerExpression::BinaryExpr(
2757                        Box::new(IntegerExpression::Const(3)),
2758                        IntegerOp::Mul,
2759                        Box::new(IntegerExpression::Param(Parameter::new("n")))
2760                    ) + IntegerExpression::Const(5)
2761                )
2762            )
2763        );
2764
2765        let guard = LIAVariableConstraint::ComparisonConstraint(ComparisonConstraint(
2766            ThresholdConstraintOver::new(
2767                WeightedSum::new([
2768                    (Variable::new("var1"), 1.into()),
2769                    (Variable::new("var2"), Fraction::from(2)),
2770                ]),
2771                ThresholdConstraint::new(
2772                    ThresholdCompOp::Lt,
2773                    BTreeMap::from([(Parameter::new("n"), 3)]),
2774                    1,
2775                ),
2776            ),
2777        ));
2778        assert_eq!(
2779            guard.as_boolean_expr(),
2780            BooleanExpression::ComparisonExpression(
2781                Box::new(IntegerExpression::BinaryExpr(
2782                    Box::new(IntegerExpression::Atom(Variable::new("var1"))),
2783                    IntegerOp::Add,
2784                    Box::new(IntegerExpression::BinaryExpr(
2785                        Box::new(IntegerExpression::Const(2)),
2786                        IntegerOp::Mul,
2787                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
2788                    ))
2789                )),
2790                ComparisonOp::Lt,
2791                Box::new(
2792                    IntegerExpression::BinaryExpr(
2793                        Box::new(IntegerExpression::Const(3)),
2794                        IntegerOp::Mul,
2795                        Box::new(IntegerExpression::Param(Parameter::new("n")))
2796                    ) + IntegerExpression::Const(1)
2797                )
2798            )
2799        );
2800
2801        let guard = LIAVariableConstraint::BinaryGuard(
2802            Box::new(LIAVariableConstraint::True),
2803            BooleanConnective::And,
2804            Box::new(LIAVariableConstraint::False),
2805        );
2806        assert_eq!(
2807            guard.as_boolean_expr(),
2808            BooleanExpression::BinaryExpression(
2809                Box::new(BooleanExpression::True),
2810                BooleanConnective::And,
2811                Box::new(BooleanExpression::False)
2812            )
2813        );
2814
2815        let guard = LIAVariableConstraint::BinaryGuard(
2816            Box::new(LIAVariableConstraint::True),
2817            BooleanConnective::Or,
2818            Box::new(LIAVariableConstraint::False),
2819        );
2820        assert_eq!(
2821            guard.as_boolean_expr(),
2822            BooleanExpression::BinaryExpression(
2823                Box::new(BooleanExpression::True),
2824                BooleanConnective::Or,
2825                Box::new(BooleanExpression::False)
2826            )
2827        );
2828    }
2829
2830    #[test]
2831    fn test_threshold_guard_display() {
2832        let thr = LIAVariableConstraint::False;
2833        let expected = "false";
2834        assert_eq!(thr.to_string(), expected);
2835
2836        let thr = LIAVariableConstraint::True;
2837        let expected = "true";
2838        assert_eq!(thr.to_string(), expected);
2839
2840        let thr = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2841            ThresholdConstraintOver::new(
2842                WeightedSum::new(BTreeMap::from([(Variable::new("var1"), 1)])),
2843                ThresholdConstraint::new(
2844                    ThresholdCompOp::Lt,
2845                    Vec::<(Parameter, Fraction)>::new(),
2846                    1,
2847                ),
2848            ),
2849        ));
2850        let expected = "var1 < 1";
2851        assert_eq!(thr.to_string(), expected);
2852
2853        let thr = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2854            ThresholdConstraintOver::new(
2855                WeightedSum::new(BTreeMap::from([
2856                    (Variable::new("var1"), 1),
2857                    (Variable::new("var2"), 2),
2858                ])),
2859                ThresholdConstraint::new(
2860                    ThresholdCompOp::Lt,
2861                    BTreeMap::from([(Parameter::new("n"), 3)]),
2862                    5,
2863                ),
2864            ),
2865        ));
2866        let expected = "var1 + 2 * var2 < 3 * n + 5";
2867        assert_eq!(thr.to_string(), expected);
2868
2869        let thr = LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2870            ThresholdConstraintOver::new(
2871                WeightedSum::new(BTreeMap::from([
2872                    (Variable::new("var1"), 1),
2873                    (Variable::new("var2"), 2),
2874                ])),
2875                ThresholdConstraint::new(
2876                    ThresholdCompOp::Lt,
2877                    BTreeMap::from([(Parameter::new("n"), 3)]),
2878                    0,
2879                ),
2880            ),
2881        )) & LIAVariableConstraint::SumVarConstraint(SumAtomConstraint(
2882            ThresholdConstraintOver::new(
2883                WeightedSum::new(BTreeMap::from([(Variable::new("var3"), 1)])),
2884                ThresholdConstraint::new(
2885                    ThresholdCompOp::Lt,
2886                    BTreeMap::from([(Parameter::new("m"), 3), (Parameter::new("o"), 4)]),
2887                    0,
2888                ),
2889            ),
2890        ));
2891        let expected = "(var1 + 2 * var2 < 3 * n) && (var3 < 3 * m + 4 * o)";
2892        assert_eq!(thr.to_string(), expected);
2893    }
2894
2895    #[test]
2896    fn test_display_single_var_threshold_guard() {
2897        let guard = SingleAtomConstraint::new(
2898            Variable::new("var1"),
2899            ThresholdConstraint::new(ThresholdCompOp::Geq, Vec::<(Parameter, Fraction)>::new(), 5),
2900        );
2901        let expected = "var1 >= 5";
2902        assert_eq!(guard.to_string(), expected);
2903    }
2904
2905    #[test]
2906    fn test_new_multi_var_threshold() {
2907        let guard = SumAtomConstraint::try_new(
2908            [(Variable::new("var1"), 1), (Variable::new("var2"), 2)],
2909            ThresholdConstraint::new(
2910                ThresholdCompOp::Geq,
2911                BTreeMap::from([(Parameter::new("n"), 3)]),
2912                5,
2913            ),
2914        )
2915        .unwrap();
2916
2917        let expected = SumAtomConstraint(ThresholdConstraintOver::new(
2918            WeightedSum::new(BTreeMap::from([
2919                (Variable::new("var1"), 1),
2920                (Variable::new("var2"), 2),
2921            ])),
2922            ThresholdConstraint::new(ThresholdCompOp::Geq, [(Parameter::new("n"), 3)], 5),
2923        ));
2924
2925        assert_eq!(guard, expected);
2926
2927        let expected_thr = Threshold::new([(Parameter::new("n"), 3)], 5);
2928
2929        assert_eq!(guard.get_threshold(), &expected_thr);
2930        assert_eq!(
2931            guard.get_atoms(),
2932            &WeightedSum::new(BTreeMap::from([
2933                (Variable::new("var1"), 1),
2934                (Variable::new("var2"), 2),
2935            ]))
2936        );
2937    }
2938
2939    #[test]
2940    fn test_new_multi_var_threshold_all_vars_neg() {
2941        // -1 * var1 + -2 * var2 >=  3 * n + 5
2942        let guard = SumAtomConstraint::try_new(
2943            BTreeMap::from([
2944                (Variable::new("var1"), -Fraction::from(1)),
2945                (Variable::new("var2"), -Fraction::from(2)),
2946            ]),
2947            ThresholdConstraint::new(
2948                ThresholdCompOp::Geq,
2949                BTreeMap::from([(Parameter::new("n"), 3)]),
2950                5,
2951            ),
2952        )
2953        .unwrap();
2954
2955        // var1 + 2 * var2 =< -3 * n - 5
2956        let expected = SumAtomConstraint(ThresholdConstraintOver::new(
2957            WeightedSum::new(BTreeMap::from([
2958                (Variable::new("var1"), 1),
2959                (Variable::new("var2"), 2),
2960            ])),
2961            ThresholdConstraint::new(
2962                ThresholdCompOp::Leq,
2963                BTreeMap::from([(Parameter::new("n"), -Fraction::from(3))]),
2964                -Fraction::from(5),
2965            ),
2966        ));
2967
2968        assert_eq!(guard, expected);
2969    }
2970
2971    #[test]
2972    fn test_new_multi_var_threshold_err_single() {
2973        let guard = SumAtomConstraint::try_new(
2974            BTreeMap::from([(Variable::new("var1"), 1)]),
2975            ThresholdConstraint::new(
2976                ThresholdCompOp::Geq,
2977                BTreeMap::from([(Parameter::new("n"), 3)]),
2978                5,
2979            ),
2980        );
2981
2982        assert!(matches!(
2983            guard.clone(),
2984            Err(SumVarConstraintCreationError::IsSingleAtomConstraint)
2985        ));
2986        assert!(
2987            guard
2988                .unwrap_err()
2989                .to_string()
2990                .contains("SingleVarThresholdGuard")
2991        );
2992    }
2993
2994    #[test]
2995    fn test_new_multi_var_threshold_err_mixed() {
2996        let guard = SumAtomConstraint::try_new(
2997            BTreeMap::from([
2998                (Variable::new("var1"), 1.into()),
2999                (Variable::new("var2"), -Fraction::from(2)),
3000            ]),
3001            ThresholdConstraint::new(
3002                ThresholdCompOp::Geq,
3003                BTreeMap::from([(Parameter::new("n"), 3)]),
3004                5,
3005            ),
3006        );
3007
3008        assert!(matches!(
3009            guard.clone(),
3010            Err(SumVarConstraintCreationError::IsComparisonConstraint)
3011        ));
3012        assert!(guard.unwrap_err().to_string().contains("ComparisonGuard"));
3013    }
3014
3015    #[test]
3016    fn test_display_multi_var_threshold_guard() {
3017        let guard = SumAtomConstraint::try_new(
3018            BTreeMap::from([(Variable::new("var1"), 1), (Variable::new("var2"), 2)]),
3019            ThresholdConstraint::new(
3020                ThresholdCompOp::Geq,
3021                BTreeMap::from([(Parameter::new("n"), 3)]),
3022                5,
3023            ),
3024        )
3025        .unwrap();
3026        let expected = "var1 + 2 * var2 >= 3 * n + 5";
3027        assert_eq!(guard.to_string(), expected);
3028    }
3029
3030    #[test]
3031    fn test_new_comparison_guard() {
3032        let guard = ComparisonConstraint::try_new(
3033            BTreeMap::from([
3034                (Variable::new("var1"), 1.into()),
3035                (Variable::new("var2"), -Fraction::from(2)),
3036            ]),
3037            ThresholdConstraint::new(
3038                ThresholdCompOp::Geq,
3039                BTreeMap::from([(Parameter::new("n"), 3)]),
3040                5,
3041            ),
3042        )
3043        .unwrap();
3044
3045        let expected = ComparisonConstraint(ThresholdConstraintOver::new(
3046            WeightedSum::new(BTreeMap::from([
3047                (Variable::new("var1"), 1.into()),
3048                (Variable::new("var2"), -Fraction::from(2)),
3049            ])),
3050            ThresholdConstraint::new(
3051                ThresholdCompOp::Geq,
3052                BTreeMap::from([(Parameter::new("n"), 3)]),
3053                5,
3054            ),
3055        ));
3056
3057        assert_eq!(guard, expected);
3058    }
3059
3060    #[test]
3061    fn test_comparison_guard_get_threshold_constr() {
3062        let guard = ComparisonConstraint(ThresholdConstraintOver::new(
3063            WeightedSum::from([
3064                (Variable::new("var1"), 1.into()),
3065                (Variable::new("var2"), -Fraction::from(2)),
3066            ]),
3067            ThresholdConstraint::new(ThresholdCompOp::Geq, [(Parameter::new("n"), 3)], 5),
3068        ));
3069
3070        assert_eq!(
3071            guard.get_threshold_constraint(),
3072            &ThresholdConstraint::new(ThresholdCompOp::Geq, [(Parameter::new("n"), 3)], 5)
3073        )
3074    }
3075
3076    #[test]
3077    fn test_new_comparison_guard_err_multi() {
3078        let guard = ComparisonConstraint::try_new(
3079            BTreeMap::from([
3080                (Variable::new("var1"), 1.into()),
3081                (Variable::new("var2"), 2.into()),
3082            ]),
3083            ThresholdConstraint::new(
3084                ThresholdCompOp::Geq,
3085                BTreeMap::from([(Parameter::new("n"), 3)]),
3086                5,
3087            ),
3088        );
3089
3090        assert!(matches!(
3091            guard.clone(),
3092            Err(ComparisonConstraintCreationError::IsSumVarConstraint)
3093        ));
3094        assert!(
3095            guard
3096                .unwrap_err()
3097                .to_string()
3098                .contains("MultiVarThresholdGuard")
3099        );
3100    }
3101
3102    #[test]
3103    fn test_display_comparison_guard() {
3104        let guard = ComparisonConstraint::try_new(
3105            BTreeMap::from([
3106                (Variable::new("var1"), 1.into()),
3107                (Variable::new("var2"), -Fraction::from(2)),
3108            ]),
3109            ThresholdConstraint::new(
3110                ThresholdCompOp::Geq,
3111                BTreeMap::from([(Parameter::new("n"), 3)]),
3112                5,
3113            ),
3114        )
3115        .unwrap();
3116        let expected = "var1 + -2 * var2 >= 3 * n + 5";
3117        assert_eq!(guard.to_string(), expected);
3118    }
3119
3120    #[test]
3121    fn test_lower_and_upper_guard() {
3122        let lower_thr = ThresholdConstraint::new(
3123            ThresholdCompOp::Geq,
3124            BTreeMap::from([(Parameter::new("n"), 3)]),
3125            5,
3126        );
3127
3128        let upper_thr = ThresholdConstraint::new(
3129            ThresholdCompOp::Lt,
3130            BTreeMap::from([(Parameter::new("n"), 3)]),
3131            4,
3132        );
3133
3134        let guard = LIAVariableConstraint::ComparisonConstraint(
3135            ComparisonConstraint::try_new(
3136                BTreeMap::from([
3137                    (Variable::new("var1"), 1.into()),
3138                    (Variable::new("var2"), -Fraction::from(2)),
3139                ]),
3140                lower_thr.clone(),
3141            )
3142            .unwrap(),
3143        );
3144        assert!(guard.is_lower_guard());
3145        assert!(!guard.is_upper_guard());
3146
3147        let guard = LIAVariableConstraint::ComparisonConstraint(
3148            ComparisonConstraint::try_new(
3149                BTreeMap::from([
3150                    (Variable::new("var1"), 1.into()),
3151                    (Variable::new("var2"), -Fraction::from(2)),
3152                ]),
3153                upper_thr.clone(),
3154            )
3155            .unwrap(),
3156        );
3157        assert!(!guard.is_lower_guard());
3158        assert!(guard.is_upper_guard());
3159
3160        let guard = SingleAtomConstraint::new(Variable::new("var1"), lower_thr.clone());
3161        assert!(guard.is_lower_guard());
3162        assert!(!guard.is_upper_guard());
3163
3164        let guard = SingleAtomConstraint::new(Variable::new("var1"), upper_thr.clone());
3165        assert!(!guard.is_lower_guard());
3166        assert!(guard.is_upper_guard());
3167
3168        let guard = LIAVariableConstraint::SumVarConstraint(
3169            SumAtomConstraint::try_new(
3170                BTreeMap::from([
3171                    (Variable::new("var1"), 1.into()),
3172                    (Variable::new("var2"), Fraction::from(2)),
3173                ]),
3174                lower_thr.clone(),
3175            )
3176            .unwrap(),
3177        );
3178        assert!(guard.is_lower_guard());
3179        assert!(!guard.is_upper_guard());
3180
3181        let guard = LIAVariableConstraint::SumVarConstraint(
3182            SumAtomConstraint::try_new(
3183                BTreeMap::from([
3184                    (Variable::new("var1"), 1.into()),
3185                    (Variable::new("var2"), Fraction::from(2)),
3186                ]),
3187                upper_thr.clone(),
3188            )
3189            .unwrap(),
3190        );
3191        assert!(!guard.is_lower_guard());
3192        assert!(guard.is_upper_guard());
3193
3194        let guard = LIAVariableConstraint::True;
3195        assert!(!guard.is_lower_guard());
3196        assert!(!guard.is_upper_guard());
3197
3198        let guard = LIAVariableConstraint::False;
3199        assert!(!guard.is_lower_guard());
3200        assert!(!guard.is_upper_guard());
3201
3202        let guard = LIAVariableConstraint::BinaryGuard(
3203            Box::new(LIAVariableConstraint::True),
3204            BooleanConnective::And,
3205            Box::new(LIAVariableConstraint::False),
3206        );
3207        assert!(!guard.is_lower_guard());
3208        assert!(!guard.is_upper_guard());
3209
3210        let guard = LIAVariableConstraint::BinaryGuard(
3211            Box::new(LIAVariableConstraint::SingleVarConstraint(
3212                SingleAtomConstraint::new(Variable::new("var1"), lower_thr.clone()),
3213            )),
3214            BooleanConnective::Or,
3215            Box::new(LIAVariableConstraint::SingleVarConstraint(
3216                SingleAtomConstraint::new(Variable::new("var1"), upper_thr.clone()),
3217            )),
3218        );
3219        assert!(guard.is_lower_guard());
3220        assert!(guard.is_upper_guard());
3221    }
3222
3223    #[test]
3224    fn test_getters_single_var_thr() {
3225        let guard = SingleAtomConstraint::new(
3226            Variable::new("var1"),
3227            ThresholdConstraint::new(ThresholdCompOp::Geq, Vec::<(Parameter, Fraction)>::new(), 5),
3228        );
3229
3230        assert_eq!(guard.get_atom(), &Variable::new("var1"));
3231        assert_eq!(
3232            guard.get_threshold(),
3233            &Threshold::new(Vec::<(Parameter, Fraction)>::new(), 5)
3234        );
3235    }
3236
3237    #[test]
3238    fn test_getters_multi_var_thr() {
3239        let guard = SumAtomConstraint::try_new(
3240            BTreeMap::from([(Variable::new("var1"), 1), (Variable::new("var2"), 2)]),
3241            ThresholdConstraint::new(
3242                ThresholdCompOp::Geq,
3243                BTreeMap::from([(Parameter::new("n"), 3)]),
3244                5,
3245            ),
3246        )
3247        .unwrap();
3248
3249        assert_eq!(
3250            guard.get_atoms(),
3251            &WeightedSum::new(BTreeMap::from([
3252                (Variable::new("var1"), 1),
3253                (Variable::new("var2"), 2),
3254            ]))
3255        );
3256        assert_eq!(
3257            guard.get_threshold(),
3258            &Threshold::new(BTreeMap::from([(Parameter::new("n"), 3)]), 5,)
3259        );
3260    }
3261
3262    #[test]
3263    fn test_display_lia_transformation_error() {
3264        let error = LIATransformationError::GuardError(
3265            Box::new(RuleBuilder::new(0, Location::new("l1"), Location::new("l2")).build()),
3266            Box::new(BooleanExpression::False),
3267            Box::new(ConstraintRewriteError::NotLinearArithmetic),
3268        );
3269        let expected = "Error while transforming guard false of rule 0: l1 -> l2\n    when ( true )\n    do {  }: Non linear integer arithmetic expression detected. Threshold automata only allow for linear arithmetic formula in their guards.";
3270
3271        assert_eq!(error.to_string(), expected);
3272
3273        let error = LIATransformationError::InitialConstraintError(
3274            Box::new(BooleanExpression::False),
3275            Box::new(ConstraintRewriteError::NotLinearArithmetic),
3276        );
3277        let expected = "Error while transforming initial constraint false into linear arithmetic: Non linear integer arithmetic expression detected. Threshold automata only allow for linear arithmetic formula in their guards.";
3278        assert_eq!(error.to_string(), expected);
3279    }
3280}