taco_interval_ta/
lib.rs

1//! Threshold Automaton with applied interval abstraction
2//!
3//! This crate defines `IntervalThresholdAutomaton` which is a type representing
4//! a threshold automaton to which interval abstraction has been applied. Guards
5//! are abstracted to sets of symbolic intervals, depending on the interval
6//! order that is used for the automaton.
7//!
8//! This crate also provides the types for defining and working with intervals
9//! and implements an ordering mechanism for intervals.
10
11use builder::{IntervalTABuilder, static_interval_order::StaticIntervalOrder};
12use interval::{Interval, IntervalOrderError, IntervalOrderFor};
13use log::warn;
14use taco_display_utils::{
15    display_iterator_stable_order, indent_all, join_iterator, join_iterator_and_add_back,
16};
17use taco_model_checker::{ModelCheckerContext, TATrait, TargetSpec};
18use taco_smt_encoder::ProvidesSMTSolverBuilder;
19use taco_threshold_automaton::expressions::{BooleanConnective, IsDeclared};
20use taco_threshold_automaton::general_threshold_automaton::{
21    Action, GeneralThresholdAutomaton, Rule, UpdateExpression,
22};
23use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::WeightedSum;
24use taco_threshold_automaton::lia_threshold_automaton::{
25    LIARule, LIATransformationError, LIAVariableConstraint,
26};
27
28use taco_threshold_automaton::{
29    ActionDefinition, LocationConstraint, RuleDefinition, ThresholdAutomaton, VariableConstraint,
30};
31use taco_threshold_automaton::{
32    expressions::{BooleanExpression, Location, Parameter, Variable},
33    lia_threshold_automaton::LIAThresholdAutomaton,
34};
35
36use std::collections::HashSet;
37use std::error;
38use std::{
39    collections::HashMap,
40    fmt::{self, Debug, Display},
41    hash,
42};
43
44pub mod builder;
45pub mod interval;
46
47/// Threshold automaton with guards abstracted to sets of intervals
48///
49/// A threshold automaton where a specific order on intervals is defined and
50/// the guards are abstracted to sets of intervals in which they are enabled.
51#[derive(Debug, Clone)]
52pub struct IntervalThresholdAutomaton {
53    /// Underlying linear integer arithmetic threshold automaton
54    lia_ta: LIAThresholdAutomaton,
55    /// Initial variable constraints as intervals
56    initial_variable_constraints: Vec<IntervalConstraint>,
57    /// Abstract rules of the threshold automaton
58    rules: Vec<IntervalTARule>,
59    /// Current interval order
60    order: StaticIntervalOrder,
61    /// Order expression
62    pub order_expr: Vec<BooleanExpression<Parameter>>,
63}
64
65impl IntervalThresholdAutomaton {
66    /// Get the initial interval for variable `var`
67    pub fn get_initial_interval<'a>(&'a self, var: &Variable) -> Vec<&'a Interval> {
68        let intervals_for_var = self
69            .order
70            .get_all_intervals_of_t(var)
71            .unwrap_or_else(|_| panic!("Failed to get intervals of variable {}", var))
72            .iter()
73            .collect();
74
75        self.initial_variable_constraints
76            .iter()
77            .fold(intervals_for_var, |acc, constr| {
78                constr.get_enabled_intervals(var, acc)
79            })
80    }
81
82    /// Get the zero interval for variable `var`
83    pub fn get_zero_interval<'a>(&'a self, var: &Variable) -> &'a Interval {
84        self.order
85            .get_zero_interval(var)
86            .unwrap_or_else(|_| panic!("Failed to get zero interval for variable {}", var))
87    }
88
89    /// Get intervals for variable `var`
90    pub fn get_intervals(&self, var: &Variable) -> &Vec<Interval> {
91        self.order
92            .get_all_intervals_of_t(var)
93            .unwrap_or_else(|_| panic!("Failed to get intervals for variable {var}"))
94    }
95
96    /// Get the previous interval of `i` for variable `var`
97    ///
98    /// Returns `None` if `i` is the first interval
99    pub fn get_previous_interval(&self, var: &Variable, i: &Interval) -> Option<&Interval> {
100        self.order
101            .get_previous_interval(var, i)
102            .unwrap_or_else(|_| panic!("Failed to get previous interval for variable {var}"))
103    }
104
105    /// Get the next interval of `i` for variable `var`
106    ///
107    /// Returns `None` if `i` is the open interval
108    pub fn get_next_interval(&self, var: &Variable, i: &Interval) -> Option<&Interval> {
109        self.order
110            .get_next_interval(var, i)
111            .unwrap_or_else(|_| panic!("Failed to get next interval for variable {var}"))
112    }
113
114    /// Get the corresponding concrete rule for a given abstract rule
115    pub fn get_derived_rule(&self, abstract_rule: &IntervalTARule) -> &Rule {
116        self.lia_ta
117            .get_ta()
118            .rules()
119            .find(|rule| {
120                rule.id() == abstract_rule.id()
121                    && rule.source() == abstract_rule.source()
122                    && rule.target() == abstract_rule.target()
123            })
124            .unwrap()
125    }
126
127    /// Translate a [LIAVariableConstraint] into an [IntervalConstraint] in the
128    /// interval order of the automaton
129    ///
130    /// This function will return an error if the constraint contains a
131    /// threshold that was not present when the interval order was constructed
132    pub fn get_interval_constraint(
133        &self,
134        constr: &LIAVariableConstraint,
135    ) -> Result<IntervalConstraint, IntervalConstraintConstructionError> {
136        IntervalConstraint::from_lia_constr(constr, &self.order)
137    }
138
139    /// Get the intervals in which the interval constraint is enabled
140    pub fn get_enabled_intervals<'a>(
141        &'a self,
142        constr: &'a IntervalConstraint,
143        var: &Variable,
144    ) -> impl Iterator<Item = &'a Interval> {
145        // TODO: adjust when multivar
146
147        constr
148            .get_enabled_intervals(var, self.get_intervals(var).iter().collect())
149            .into_iter()
150    }
151
152    /// Get all variables for which the interval constraint actually
153    /// constrains the interval a variable can be in
154    pub fn get_variables_constrained(&self, constr: &IntervalConstraint) -> Vec<&Variable> {
155        self.variables()
156            .filter(|var| {
157                self.get_enabled_intervals(constr, var)
158                    .collect::<HashSet<_>>()
159                    != self.get_intervals(var).iter().collect::<HashSet<_>>()
160            })
161            .collect()
162    }
163
164    /// Get the underlying threshold automaton
165    pub fn get_ta(&self) -> &GeneralThresholdAutomaton {
166        self.lia_ta.get_ta()
167    }
168
169    /// Get the interval order for a specific variable
170    ///
171    /// Returns all intervals for variable `var` in the interval order of the
172    /// automaton
173    pub fn get_ordered_intervals(
174        &self,
175        var: &Variable,
176    ) -> Result<&Vec<Interval>, IntervalOrderError<Variable>> {
177        let intervals = self
178            .order
179            .single_var_order()
180            .get(var)
181            .ok_or_else(|| IntervalOrderError::VariableNotFound { var: var.clone() })?;
182        Ok(intervals)
183    }
184
185    /// TODO: Remove when interval order is ready
186    pub fn get_helper_vars_for_sumvars(&self) -> &HashMap<Variable, WeightedSum<Variable>> {
187        self.lia_ta.get_replacement_vars_for_sumvars()
188    }
189}
190
191impl ThresholdAutomaton for IntervalThresholdAutomaton {
192    type Rule = IntervalTARule;
193    type InitialVariableConstraintType = IntervalConstraint;
194
195    fn name(&self) -> &str {
196        self.lia_ta.name()
197    }
198
199    fn parameters(&self) -> impl Iterator<Item = &Parameter> {
200        self.lia_ta.parameters()
201    }
202
203    fn resilience_conditions(&self) -> impl Iterator<Item = &BooleanExpression<Parameter>> {
204        self.lia_ta
205            .resilience_conditions()
206            .chain(self.order_expr.iter())
207    }
208
209    fn variables(&self) -> impl Iterator<Item = &Variable> {
210        self.lia_ta.variables()
211    }
212
213    fn locations(&self) -> impl Iterator<Item = &Location> {
214        self.lia_ta.locations()
215    }
216
217    fn can_be_initial_location(&self, location: &Location) -> bool {
218        self.lia_ta.can_be_initial_location(location)
219    }
220
221    fn rules(&self) -> impl Iterator<Item = &Self::Rule> {
222        self.rules.iter()
223    }
224
225    fn incoming_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
226        self.rules
227            .iter()
228            .filter(move |rule| rule.target() == location)
229    }
230
231    fn outgoing_rules(&self, location: &Location) -> impl Iterator<Item = &Self::Rule> {
232        self.rules
233            .iter()
234            .filter(move |rule| rule.source() == location)
235    }
236
237    fn initial_location_constraints(&self) -> impl Iterator<Item = &LocationConstraint> {
238        self.lia_ta.initial_location_constraints()
239    }
240
241    fn initial_variable_constraints(&self) -> impl Iterator<Item = &IntervalConstraint> {
242        self.initial_variable_constraints.iter()
243    }
244}
245
246/// Trait marking types that can be abstracted by intervals
247///
248/// This trait is implemented by types that have a set of intervals associated
249/// with them, e.g., `Variable` and `WeightedSum<Variable>` that appear in
250/// guards of the automaton.
251pub trait HasAssociatedIntervals: hash::Hash + Eq + Display + Debug + Clone {}
252
253/// Mark `Variable` as having associated intervals, as they appear in guards
254/// `SingleVariableGuard`s
255impl HasAssociatedIntervals for Variable {}
256
257/// Mark `WeightedSum<Variable>` as having associated intervals as they appear
258/// in guards `MultiVariableGuard`s
259impl HasAssociatedIntervals for WeightedSum<Variable> {}
260
261impl fmt::Display for IntervalThresholdAutomaton {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        let order = self.order.to_string();
264
265        let variables = format!(
266            "shared {};",
267            display_iterator_stable_order(self.variables())
268        );
269
270        let parameters = format!(
271            "parameters {};",
272            display_iterator_stable_order(self.parameters())
273        );
274
275        let rc = format!(
276            "assumptions ({}) {{\n{}}}",
277            self.resilience_conditions().count(),
278            indent_all(join_iterator_and_add_back(
279                self.resilience_conditions(),
280                ";\n"
281            ))
282        );
283
284        let mut location_str = String::new();
285        let mut locations = self.locations().collect::<Vec<_>>();
286        locations.sort();
287
288        for (i, loc) in locations.into_iter().enumerate() {
289            location_str += &format!("{loc}:[{i}];\n");
290        }
291        let locations = format!(
292            "locations ({}) {{\n{}}}",
293            self.locations().count(),
294            indent_all(location_str)
295        );
296
297        let initial_cond = format!(
298            "inits ({}) {{\n{}}}",
299            self.initial_location_constraints().count()
300                + self.initial_variable_constraints().count(),
301            indent_all(
302                join_iterator_and_add_back(self.initial_location_constraints(), ";\n")
303                    + &join_iterator_and_add_back(self.initial_variable_constraints(), ";\n")
304            )
305        );
306
307        let mut sorted_rules_by_id = self.rules().collect::<Vec<_>>();
308        sorted_rules_by_id.sort_by_key(|r| &r.id);
309        let rules = format!(
310            "rules ({}) {{\n{}}}",
311            self.rules().count(),
312            indent_all(join_iterator_and_add_back(
313                sorted_rules_by_id.into_iter(),
314                ";\n"
315            ))
316        );
317
318        let ta_body = format!(
319            "{order}\n\n{variables}\n\n{parameters}\n\n{rc}\n\n{locations}\n\n{initial_cond}\n\n{rules}"
320        );
321
322        write!(
323            f,
324            "thresholdAutomaton {} {{\n{}\n}}\n",
325            self.name(),
326            indent_all(ta_body)
327        )
328    }
329}
330
331impl IsDeclared<Parameter> for IntervalThresholdAutomaton {
332    fn is_declared(&self, obj: &Parameter) -> bool {
333        self.lia_ta.is_declared(obj)
334    }
335}
336
337impl IsDeclared<Location> for IntervalThresholdAutomaton {
338    fn is_declared(&self, obj: &Location) -> bool {
339        self.lia_ta.is_declared(obj)
340    }
341}
342
343impl IsDeclared<Variable> for IntervalThresholdAutomaton {
344    fn is_declared(&self, obj: &Variable) -> bool {
345        self.lia_ta.is_declared(obj)
346    }
347}
348
349impl<C: ModelCheckerContext + ProvidesSMTSolverBuilder, SC: TargetSpec> TATrait<C, SC>
350    for IntervalThresholdAutomaton
351{
352    type TransformationError = IntervalTATransformationError;
353
354    fn try_from_general_ta(
355        ta: GeneralThresholdAutomaton,
356        ctx: &C,
357        spec_ctx: &SC,
358    ) -> Result<Vec<Self>, Self::TransformationError> {
359        let solver_builder = ctx.get_solver_builder();
360
361        let lta = LIAThresholdAutomaton::try_from(ta);
362        if let Err(lta_err) = lta {
363            return Err(IntervalTATransformationError::LIATransformationError(
364                Box::new(lta_err),
365            ));
366        }
367
368        // TODO: remove this preprocessing when sum of variables is implemented
369        let lta = lta.unwrap().into_ta_without_sum_vars();
370
371        let target_constrs = spec_ctx
372            .get_variable_constraint()
373            .into_iter()
374            .cloned()
375            .collect();
376
377        let builder = IntervalTABuilder::new(lta, solver_builder, target_constrs);
378
379        let itas = builder.build()?;
380        Ok(itas.collect())
381    }
382}
383
384/// Interval abstracted rule
385///
386/// Rule of the abstract threshold automaton with an interval guard
387#[derive(Debug, Clone, Eq, PartialEq, Hash)]
388pub struct IntervalTARule {
389    /// Id assigned to the rule in the specification
390    id: u32,
391    /// Source location of the rule
392    source: Location,
393    /// Target location of the rule
394    target: Location,
395    /// Guard of the rule
396    guard: IntervalConstraint,
397    /// Effect of the rule
398    actions: Vec<IntervalTAAction>,
399}
400
401impl IntervalTARule {
402    /// Creates a new abstract Rule for testing purposes
403    pub fn new(
404        id: u32,
405        source: Location,
406        target: Location,
407        guard: IntervalConstraint,
408        effect: Vec<IntervalTAAction>,
409    ) -> IntervalTARule {
410        IntervalTARule {
411            id,
412            source,
413            target,
414            guard,
415            actions: effect,
416        }
417    }
418
419    /// Check if the rule updates the given variable
420    pub fn updates_variable(&self, var: &Variable) -> bool {
421        self.actions().any(|action| action.variable() == var)
422    }
423
424    /// Returns the guard of the rule
425    pub fn get_guard(&self) -> &IntervalConstraint {
426        &self.guard
427    }
428
429    /// Check whether the rule is enabled in the given state
430    pub fn is_enabled(
431        &self,
432        state: &IntervalState<Variable>,
433    ) -> Result<bool, IntervalOrderError<Variable>> {
434        self.guard.is_enabled(state)
435    }
436
437    /// Derive an interval rule from a rule of a linear integer arithmetic rule
438    /// given a fixed order
439    fn from_lia_rule<O: IntervalOrderFor<Variable> + IntervalOrderFor<WeightedSum<Variable>>>(
440        rule: &LIARule,
441        order: &O,
442    ) -> IntervalTARule {
443        let guard = IntervalConstraint::from_lia_constr(rule.guard(), order)
444            .expect("Failed to derive guard interval constraint");
445
446        let actions = rule.actions().map(|action| action.clone().into()).collect();
447
448        IntervalTARule::new(
449            rule.id(),
450            rule.source().clone(),
451            rule.target().clone(),
452            guard,
453            actions,
454        )
455    }
456}
457
458impl RuleDefinition for IntervalTARule {
459    type Action = IntervalTAAction;
460    type Guard = IntervalConstraint;
461
462    fn id(&self) -> u32 {
463        self.id
464    }
465
466    fn source(&self) -> &Location {
467        &self.source
468    }
469
470    fn target(&self) -> &Location {
471        &self.target
472    }
473
474    fn guard(&self) -> &Self::Guard {
475        &self.guard
476    }
477
478    fn actions(&self) -> impl Iterator<Item = &Self::Action> {
479        self.actions.iter()
480    }
481}
482
483impl Display for IntervalTARule {
484    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
485        let rule_body = indent_all(format!(
486            "when ( {} )\ndo {{ {} }}",
487            self.guard,
488            join_iterator(self.actions.iter(), "; ")
489        ));
490
491        write!(
492            f,
493            "{}: {} -> {}\n{}",
494            self.id, self.source, self.target, rule_body
495        )
496    }
497}
498
499impl Display for IntervalTAAction {
500    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
501        write!(f, "{} {}", self.variable, self.effect)
502    }
503}
504
505impl Display for IntervalActionEffect {
506    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
507        match self {
508            IntervalActionEffect::Inc(_i) => write!(f, "++"),
509            IntervalActionEffect::Dec(_i) => write!(f, "--"),
510            IntervalActionEffect::Reset => write!(f, "= 0"),
511        }
512    }
513}
514
515impl Display for IntervalConstraint {
516    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
517        match self {
518            IntervalConstraint::True => write!(f, "true"),
519            IntervalConstraint::False => write!(f, "false"),
520            IntervalConstraint::Conj(g1, g2) => write!(f, "{g1} && {g2}"),
521            IntervalConstraint::Disj(g1, g2) => write!(f, "{g1} || {g2}"),
522            IntervalConstraint::SingleVarIntervalConstr(var, intervals) => {
523                write!(
524                    f,
525                    "{} ∈ {{ {} }}",
526                    var,
527                    join_iterator(intervals.iter(), ", ")
528                )
529            }
530            IntervalConstraint::MultiVarIntervalConstr(weighted_sum, intervals) => {
531                write!(
532                    f,
533                    "{}  ∈ {{ {} }}",
534                    weighted_sum,
535                    join_iterator(intervals.iter(), ", ")
536                )
537            }
538        }
539    }
540}
541
542/// Action describing the effect of an action on an interval
543///
544/// The action updates the interval of a variable
545#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
546pub struct IntervalTAAction {
547    /// Variable to be updated
548    variable: Variable,
549    /// Effect of the action
550    effect: IntervalActionEffect,
551}
552
553impl IntervalTAAction {
554    /// Creates a new interval action
555    pub fn new(variable: Variable, effect: IntervalActionEffect) -> IntervalTAAction {
556        IntervalTAAction { variable, effect }
557    }
558
559    /// Returns the effect of the action
560    pub fn effect(&self) -> &IntervalActionEffect {
561        &self.effect
562    }
563}
564
565impl From<Action> for IntervalTAAction {
566    fn from(val: Action) -> Self {
567        let variable = val.variable().clone();
568        let effect = match val.update() {
569            UpdateExpression::Inc(i) => IntervalActionEffect::Inc(*i),
570            UpdateExpression::Dec(d) => IntervalActionEffect::Dec(*d),
571            UpdateExpression::Reset => IntervalActionEffect::Reset,
572            UpdateExpression::Unchanged => unreachable!("Should have been removed"),
573        };
574
575        IntervalTAAction::new(variable, effect)
576    }
577}
578
579/// Describes the effect of an action to the interval
580#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
581pub enum IntervalActionEffect {
582    /// Increment
583    Inc(u32),
584    /// Decrement
585    Dec(u32),
586    /// Reset to the zero interval
587    Reset,
588}
589
590impl ActionDefinition for IntervalTAAction {
591    fn variable(&self) -> &Variable {
592        &self.variable
593    }
594
595    fn is_unchanged(&self) -> bool {
596        false
597    }
598
599    fn is_reset(&self) -> bool {
600        matches!(self.effect, IntervalActionEffect::Reset)
601    }
602
603    fn is_increment(&self) -> bool {
604        matches!(self.effect, IntervalActionEffect::Inc(_))
605    }
606
607    fn is_decrement(&self) -> bool {
608        matches!(self.effect, IntervalActionEffect::Dec(_))
609    }
610}
611
612/// Interval guard constraining the interval of evaluation of a variable
613/// or a sum of variables
614#[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
615pub enum IntervalConstraint {
616    /// Guard is always enabled
617    True,
618    /// Guard is always disabled
619    False,
620    /// Conjunction of interval guard
621    Conj(Box<IntervalConstraint>, Box<IntervalConstraint>),
622    /// Disjunction of interval guard
623    Disj(Box<IntervalConstraint>, Box<IntervalConstraint>),
624    /// Interval guard over a single variable
625    ///
626    /// The guard is enabled if the variable is in one of the intervals.
627    SingleVarIntervalConstr(Variable, Vec<Interval>),
628    /// Interval guard over a sum of variables
629    ///
630    /// The guard is enabled if the sum of variables is in one of the intervals.
631    MultiVarIntervalConstr(WeightedSum<Variable>, Vec<Interval>),
632}
633
634/// Interval state for a type `T`
635///
636/// This struct assigns an interval to each element of type `T`. It can be used
637/// to track the abstract state, i.e., the current interval in which `T`
638/// currently used.
639///
640/// On this abstract state, one can check for example whether a guard is enabled.
641pub struct IntervalState<T: HasAssociatedIntervals> {
642    // map t to its current interval
643    t_to_interval: HashMap<T, Interval>,
644}
645
646impl IntervalConstraint {
647    /// Check if the guard is enabled in the given state
648    pub fn is_enabled(
649        &self,
650        state: &IntervalState<Variable>,
651    ) -> Result<bool, IntervalOrderError<Variable>> {
652        match self {
653            IntervalConstraint::True => Ok(true),
654            IntervalConstraint::False => Ok(false),
655            IntervalConstraint::Conj(g1, g2) => Ok(g1.is_enabled(state)? && g2.is_enabled(state)?),
656            IntervalConstraint::Disj(g1, g2) => Ok(g1.is_enabled(state)? || g2.is_enabled(state)?),
657            IntervalConstraint::SingleVarIntervalConstr(var, intervals) => {
658                let interval = state
659                    .t_to_interval
660                    .get(var)
661                    .ok_or_else(|| IntervalOrderError::VariableNotFound { var: var.clone() })?;
662
663                Ok(intervals.contains(interval))
664            }
665            IntervalConstraint::MultiVarIntervalConstr(_multivar, _intervals) => {
666                todo!()
667            }
668        }
669    }
670
671    /// Derive an interval guard from a guard of a linear integer arithmetic
672    /// guard under the current interval order
673    fn from_lia_constr<S: IntervalOrderFor<Variable> + IntervalOrderFor<WeightedSum<Variable>>>(
674        guard: &LIAVariableConstraint,
675        order: &S,
676    ) -> Result<Self, IntervalConstraintConstructionError> {
677        match guard {
678            LIAVariableConstraint::True => Ok(IntervalConstraint::True),
679            LIAVariableConstraint::False => Ok(IntervalConstraint::False),
680            LIAVariableConstraint::ComparisonConstraint(_) => {
681                unreachable!("Comparison guards not supported by this algorithm")
682            }
683            LIAVariableConstraint::SingleVarConstraint(g) => {
684                let enabled_intervals = order.compute_enabled_intervals_of_threshold(
685                    g.get_atom(),
686                    g.get_threshold_constraint(),
687                )?;
688
689                // simplify the expressions
690                if enabled_intervals.is_empty() {
691                    return Ok(IntervalConstraint::False);
692                }
693
694                // simplify the expressions
695                if order
696                    .get_all_intervals_of_t(g.get_atom())?
697                    .iter()
698                    .all(|i| enabled_intervals.contains(i))
699                {
700                    return Ok(IntervalConstraint::True);
701                }
702
703                Ok(IntervalConstraint::SingleVarIntervalConstr(
704                    g.get_atom().clone(),
705                    enabled_intervals,
706                ))
707            }
708            LIAVariableConstraint::SumVarConstraint(g) => {
709                Ok(IntervalConstraint::MultiVarIntervalConstr(
710                    g.get_atoms().clone(),
711                    order
712                        .compute_enabled_intervals_of_threshold(
713                            g.get_atoms(),
714                            g.get_threshold_constraint(),
715                        )
716                        .unwrap(),
717                ))
718            }
719            LIAVariableConstraint::BinaryGuard(lhs, con, rhs) => {
720                let lhs_guard = Self::from_lia_constr(lhs, order)?;
721                let rhs_guard = Self::from_lia_constr(rhs, order)?;
722
723                match con {
724                    BooleanConnective::And => {
725                        // simplify the expressions
726                        if lhs_guard == IntervalConstraint::True {
727                            return Ok(rhs_guard);
728                        }
729                        // simplify the expressions
730                        if rhs_guard == IntervalConstraint::True {
731                            return Ok(lhs_guard);
732                        }
733
734                        Ok(IntervalConstraint::Conj(
735                            Box::new(lhs_guard),
736                            Box::new(rhs_guard),
737                        ))
738                    }
739                    BooleanConnective::Or => {
740                        // simplify the expressions
741                        if lhs_guard == IntervalConstraint::False {
742                            return Ok(rhs_guard);
743                        }
744                        // simplify the expressions
745                        if rhs_guard == IntervalConstraint::False {
746                            return Ok(lhs_guard);
747                        }
748
749                        Ok(IntervalConstraint::Disj(
750                            Box::new(lhs_guard),
751                            Box::new(rhs_guard),
752                        ))
753                    }
754                }
755            }
756        }
757    }
758
759    /// Get the the set of intervals for which the constraint `self` is
760    /// satisfied for variable `var`
761    ///
762    /// This function requires `intervals_of_var` to be the set of all intervals
763    /// for `var` in the automaton. (Which can be obtained from the interval
764    /// order)
765    fn get_enabled_intervals<'a>(
766        &'a self,
767        var: &Variable,
768        mut intervals_of_var: Vec<&'a Interval>,
769    ) -> Vec<&'a Interval> {
770        match self {
771            IntervalConstraint::True => intervals_of_var,
772            IntervalConstraint::False => Vec::new(),
773            IntervalConstraint::Conj(lhs, rhs) => {
774                let lhs_intervals = lhs.get_enabled_intervals(var, intervals_of_var);
775                rhs.get_enabled_intervals(var, lhs_intervals)
776            }
777            IntervalConstraint::Disj(lhs, rhs) => {
778                let mut lhs_intervals = lhs.get_enabled_intervals(var, intervals_of_var.clone());
779                let mut rhs_intervals = rhs.get_enabled_intervals(var, intervals_of_var);
780
781                rhs_intervals.retain(|i| !lhs_intervals.contains(i));
782                lhs_intervals.extend(rhs_intervals);
783
784                lhs_intervals
785            }
786            IntervalConstraint::SingleVarIntervalConstr(variable, intervals) => {
787                if variable == var {
788                    intervals_of_var.retain(|i| intervals.contains(i));
789                }
790                intervals_of_var
791            }
792            IntervalConstraint::MultiVarIntervalConstr(weighted_sum, _intervals) => {
793                // FIXME: Implement the interval arithmetic
794                if weighted_sum.contains(var) {
795                    warn!(
796                        "Interval constraint over a sum of variables not fully implemented ! Tried to get constraints for variable {var}"
797                    );
798                }
799
800                intervals_of_var
801            }
802        }
803    }
804}
805
806#[derive(Debug, Clone, PartialEq)]
807/// Error that can occur during the construction of an [IntervalConstraint]
808pub enum IntervalConstraintConstructionError {
809    /// Error while attempting to translate a constraint over a single variable
810    VarError(IntervalOrderError<Variable>),
811    /// Error while attempting to translate a constraint over the sum of variables
812    SumVarError(IntervalOrderError<WeightedSum<Variable>>),
813}
814
815impl error::Error for IntervalConstraintConstructionError {}
816
817impl fmt::Display for IntervalConstraintConstructionError {
818    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
819        match self {
820            IntervalConstraintConstructionError::VarError(err) => {
821                write!(f, "Error while constructing an IntervalConstraint: {err}")
822            }
823            IntervalConstraintConstructionError::SumVarError(err) => {
824                write!(f, "Error while constructing an IntervalConstraint: {err}")
825            }
826        }
827    }
828}
829
830impl From<IntervalOrderError<Variable>> for IntervalConstraintConstructionError {
831    fn from(value: IntervalOrderError<Variable>) -> Self {
832        Self::VarError(value)
833    }
834}
835
836impl From<IntervalOrderError<WeightedSum<Variable>>> for IntervalConstraintConstructionError {
837    fn from(value: IntervalOrderError<WeightedSum<Variable>>) -> Self {
838        Self::SumVarError(value)
839    }
840}
841
842impl VariableConstraint for IntervalConstraint {
843    fn as_boolean_expr(&self) -> BooleanExpression<Variable> {
844        match self {
845            IntervalConstraint::True => BooleanExpression::True,
846            IntervalConstraint::False => BooleanExpression::False,
847            IntervalConstraint::Conj(lhs, rhs) => BooleanExpression::BinaryExpression(
848                Box::new(lhs.as_boolean_expr()),
849                BooleanConnective::And,
850                Box::new(rhs.as_boolean_expr()),
851            ),
852            IntervalConstraint::Disj(lhs, rhs) => BooleanExpression::BinaryExpression(
853                Box::new(lhs.as_boolean_expr()),
854                BooleanConnective::Or,
855                Box::new(rhs.as_boolean_expr()),
856            ),
857            IntervalConstraint::SingleVarIntervalConstr(var, intervals) => {
858                intervals.iter().fold(BooleanExpression::False, |acc, i| {
859                    let b_expr = i.encode_into_boolean_expr(var);
860
861                    BooleanExpression::BinaryExpression(
862                        Box::new(acc),
863                        BooleanConnective::Or,
864                        Box::new(b_expr),
865                    )
866                })
867            }
868            IntervalConstraint::MultiVarIntervalConstr(vars, intervals) => {
869                intervals.iter().fold(BooleanExpression::False, |acc, i| {
870                    let b_expr = i.encode_into_boolean_expr(vars);
871
872                    BooleanExpression::BinaryExpression(
873                        Box::new(acc),
874                        BooleanConnective::Or,
875                        Box::new(b_expr),
876                    )
877                })
878            }
879        }
880    }
881}
882
883/// Errors that can occur when trying to construct an interval automaton from a
884/// general automaton
885#[derive(Debug, PartialEq, Clone)]
886pub enum IntervalTATransformationError {
887    /// Error that occurred during while attempting to translate general ta into
888    /// the linear arithmetic form
889    LIATransformationError(Box<LIATransformationError>),
890    /// Comparison Guard found in Rule during transformation
891    ComparisonGuardFoundRule(Box<LIARule>),
892    /// Comparison Guard found in Initial Variable Constraint during transformation
893    ComparisonGuardFoundInitialVariableConstraint(Box<LIAVariableConstraint>),
894    /// Comparison Guard found as part of the interval target
895    ComparisonGuardFoundVariableTarget(Box<LIAVariableConstraint>),
896}
897
898impl fmt::Display for IntervalTATransformationError {
899    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
900        match self {
901            IntervalTATransformationError::LIATransformationError(e) => write!(
902                f,
903                "Failed to derive interval threshold automaton: Transformation to linear arithmetic automaton failed: Error: {e}"
904            ),
905            IntervalTATransformationError::ComparisonGuardFoundRule(rule) => write!(
906                f,
907                "Failed to derive interval threshold automaton: Found comparison guard which is unsupported. Rule: {rule}"
908            ),
909            IntervalTATransformationError::ComparisonGuardFoundInitialVariableConstraint(
910                constr,
911            ) => {
912                write!(
913                    f,
914                    "Failed to derive interval threshold automaton: Found comparison guard in initial variable constraint which is unsupported. Initial variable constraint: {constr}"
915                )
916            }
917            IntervalTATransformationError::ComparisonGuardFoundVariableTarget(constr) => {
918                write!(
919                    f,
920                    "Failed to derive interval threshold automaton: Found comparison guard as part of a interval target constraint: {constr}"
921                )
922            }
923        }
924    }
925}
926
927impl error::Error for IntervalTATransformationError {}
928
929#[cfg(test)]
930mod tests {
931    use std::collections::{BTreeMap, HashSet};
932
933    use interval::IntervalBoundary;
934    use taco_smt_encoder::SMTSolverBuilder;
935    use taco_threshold_automaton::{
936        expressions::{ComparisonOp, IntegerExpression, fraction::Fraction},
937        general_threshold_automaton::{
938            Action,
939            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
940        },
941        lia_threshold_automaton::{
942            SingleAtomConstraint, SumAtomConstraint,
943            integer_thresholds::{ThresholdCompOp, ThresholdConstraint},
944        },
945    };
946
947    use super::*;
948
949    #[test]
950    fn test_interval_threshold_automaton_getters() {
951        let var = Variable::new("x");
952        let i1 = Interval::new_constant(0, 1);
953        let i2 = Interval::new_constant(1, 3);
954        let i3 = Interval::new(
955            IntervalBoundary::from_const(3),
956            false,
957            IntervalBoundary::new_infty(),
958            true,
959        );
960
961        let rc = BooleanExpression::ComparisonExpression(
962            Box::new(IntegerExpression::Param(Parameter::new("n"))),
963            ComparisonOp::Gt,
964            Box::new(IntegerExpression::Const(3)),
965        );
966
967        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
968            .with_variable(var.clone())
969            .unwrap()
970            .with_locations([Location::new("l1"), Location::new("l2")])
971            .unwrap()
972            .with_parameter(Parameter::new("n"))
973            .unwrap()
974            .initialize()
975            .with_resilience_condition(rc.clone())
976            .unwrap()
977            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
978                Box::new(IntegerExpression::Atom(Location::new("l1"))),
979                ComparisonOp::Eq,
980                Box::new(IntegerExpression::Const(0)),
981            ))
982            .unwrap()
983            .with_rule(
984                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
985                    .with_guard(BooleanExpression::ComparisonExpression(
986                        Box::new(IntegerExpression::Atom(var.clone())),
987                        ComparisonOp::Gt,
988                        Box::new(IntegerExpression::Const(2)),
989                    ))
990                    .with_action(
991                        Action::new(
992                            var.clone(),
993                            IntegerExpression::Const(1) + IntegerExpression::Atom(var.clone()),
994                        )
995                        .unwrap(),
996                    )
997                    .build(),
998            )
999            .unwrap()
1000            .build();
1001
1002        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
1003
1004        let rule = IntervalTARule::new(
1005            0,
1006            Location::new("l1"),
1007            Location::new("l2"),
1008            IntervalConstraint::SingleVarIntervalConstr(var.clone(), vec![i3.clone()]),
1009            vec![IntervalTAAction::new(
1010                var.clone(),
1011                IntervalActionEffect::Inc(1),
1012            )],
1013        );
1014
1015        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
1016            .build()
1017            .unwrap();
1018        let interval_threshold_automaton = interval_tas.next().unwrap();
1019        assert!(interval_tas.next().is_none());
1020
1021        assert_eq!(interval_threshold_automaton.name(), "test_ta");
1022        assert_eq!(
1023            interval_threshold_automaton
1024                .parameters()
1025                .map(|p| p.to_string())
1026                .collect::<HashSet<_>>(),
1027            HashSet::from(["n".to_string()])
1028        );
1029        assert_eq!(
1030            interval_threshold_automaton
1031                .resilience_conditions()
1032                .collect::<Vec<_>>(),
1033            vec![
1034                &rc,
1035                &BooleanExpression::ComparisonExpression(
1036                    Box::new(IntegerExpression::Const(0)),
1037                    ComparisonOp::Lt,
1038                    Box::new(IntegerExpression::Const(1)),
1039                ),
1040                &BooleanExpression::ComparisonExpression(
1041                    Box::new(IntegerExpression::Const(1)),
1042                    ComparisonOp::Lt,
1043                    Box::new(IntegerExpression::Const(3)),
1044                )
1045            ]
1046        );
1047
1048        assert!(interval_threshold_automaton.is_declared(&Location::new("l1")));
1049        assert!(interval_threshold_automaton.is_declared(&Variable::new("x")));
1050        assert!(interval_threshold_automaton.is_declared(&Parameter::new("n")));
1051
1052        assert_eq!(
1053            interval_threshold_automaton
1054                .incoming_rules(&Location::new("l2"))
1055                .collect::<Vec<_>>(),
1056            vec![&rule]
1057        );
1058        assert_eq!(
1059            interval_threshold_automaton
1060                .outgoing_rules(&Location::new("l1"))
1061                .collect::<Vec<_>>(),
1062            vec![&rule]
1063        );
1064
1065        assert_eq!(
1066            interval_threshold_automaton.get_initial_interval(&var),
1067            vec![
1068                &Interval::zero(),
1069                &Interval::new(
1070                    IntervalBoundary::from_const(1),
1071                    false,
1072                    IntervalBoundary::from_const(3),
1073                    true
1074                ),
1075                &Interval::new(
1076                    IntervalBoundary::from_const(3),
1077                    false,
1078                    IntervalBoundary::new_infty(),
1079                    true
1080                )
1081            ]
1082        );
1083        assert_eq!(
1084            interval_threshold_automaton.get_zero_interval(&var),
1085            &Interval::zero()
1086        );
1087        assert_eq!(
1088            interval_threshold_automaton.get_intervals(&var),
1089            &vec![i1.clone(), i2.clone(), i3.clone()]
1090        );
1091        assert_eq!(
1092            interval_threshold_automaton.get_previous_interval(&var, &i1),
1093            None
1094        );
1095        assert_eq!(
1096            interval_threshold_automaton.get_previous_interval(&var, &i2),
1097            Some(&i1)
1098        );
1099        assert_eq!(
1100            interval_threshold_automaton.get_previous_interval(&var, &i3),
1101            Some(&i2)
1102        );
1103        assert_eq!(
1104            interval_threshold_automaton.get_next_interval(&var, &i1),
1105            Some(&i2)
1106        );
1107        assert_eq!(
1108            interval_threshold_automaton.get_next_interval(&var, &i2),
1109            Some(&i3)
1110        );
1111        assert_eq!(
1112            interval_threshold_automaton.get_next_interval(&var, &i3),
1113            None
1114        );
1115
1116        assert_eq!(
1117            interval_threshold_automaton
1118                .initial_location_constraints()
1119                .cloned()
1120                .collect::<Vec<_>>(),
1121            vec![BooleanExpression::ComparisonExpression(
1122                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1123                ComparisonOp::Eq,
1124                Box::new(IntegerExpression::Const(0)),
1125            )]
1126        );
1127    }
1128
1129    #[test]
1130    fn test_rule_getter() {
1131        let var = Variable::new("x");
1132        let i = Interval::new(
1133            IntervalBoundary::from_const(2),
1134            false,
1135            IntervalBoundary::new_infty(),
1136            true,
1137        );
1138        let rule = IntervalTARule::new(
1139            0,
1140            Location::new("l1"),
1141            Location::new("l2"),
1142            IntervalConstraint::SingleVarIntervalConstr(var.clone(), vec![i.clone()]),
1143            vec![],
1144        );
1145
1146        assert_eq!(rule.id(), 0);
1147        assert_eq!(rule.source(), &Location::new("l1"));
1148        assert_eq!(rule.target(), &Location::new("l2"));
1149        assert_eq!(
1150            rule.guard(),
1151            &IntervalConstraint::SingleVarIntervalConstr(var.clone(), vec![i.clone()])
1152        );
1153    }
1154
1155    #[test]
1156    fn test_display_abstract_rule() {
1157        let var = Variable::new("x");
1158        let i = Interval::new(
1159            IntervalBoundary::from_const(2),
1160            false,
1161            IntervalBoundary::new_infty(),
1162            true,
1163        );
1164        let rule = IntervalTARule::new(
1165            0,
1166            Location::new("l1"),
1167            Location::new("l2"),
1168            IntervalConstraint::SingleVarIntervalConstr(var.clone(), vec![i.clone()]),
1169            vec![IntervalTAAction::new(
1170                var.clone(),
1171                IntervalActionEffect::Reset,
1172            )],
1173        );
1174
1175        assert_eq!(
1176            rule.to_string(),
1177            "0: l1 -> l2\n    when ( x ∈ { [2, ∞[ } )\n    do { x = 0 }"
1178        );
1179
1180        let rule = IntervalTARule::new(
1181            1,
1182            Location::new("l1"),
1183            Location::new("l2"),
1184            IntervalConstraint::True,
1185            vec![IntervalTAAction::new(
1186                var.clone(),
1187                IntervalActionEffect::Inc(1),
1188            )],
1189        );
1190
1191        assert_eq!(
1192            rule.to_string(),
1193            "1: l1 -> l2\n    when ( true )\n    do { x ++ }"
1194        );
1195
1196        let var2 = Variable::new("y");
1197        let rule = IntervalTARule::new(
1198            3,
1199            Location::new("l1"),
1200            Location::new("l2"),
1201            IntervalConstraint::MultiVarIntervalConstr(
1202                WeightedSum::new(BTreeMap::from([(var.clone(), 1), (var2.clone(), 1)])),
1203                vec![i.clone()],
1204            ),
1205            vec![IntervalTAAction::new(
1206                var.clone(),
1207                IntervalActionEffect::Dec(2),
1208            )],
1209        );
1210
1211        assert_eq!(
1212            rule.to_string(),
1213            "3: l1 -> l2\n    when ( x + y  ∈ { [2, ∞[ } )\n    do { x -- }"
1214        );
1215    }
1216
1217    #[test]
1218    fn test_get_initial_intervals() {
1219        let var1 = Variable::new("x");
1220        let var2 = Variable::new("y");
1221
1222        let i4 = Interval::new(
1223            IntervalBoundary::from_const(1),
1224            false,
1225            IntervalBoundary::Infty,
1226            true,
1227        );
1228
1229        let rc = BooleanExpression::ComparisonExpression(
1230            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1231            ComparisonOp::Gt,
1232            Box::new(IntegerExpression::Const(3)),
1233        );
1234
1235        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1236            .with_variables([var1.clone(), var2.clone()])
1237            .unwrap()
1238            .with_locations([Location::new("l1"), Location::new("l2")])
1239            .unwrap()
1240            .with_parameter(Parameter::new("n"))
1241            .unwrap()
1242            .initialize()
1243            .with_resilience_condition(rc.clone())
1244            .unwrap()
1245            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
1246                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1247                ComparisonOp::Eq,
1248                Box::new(IntegerExpression::Const(0)),
1249            ))
1250            .unwrap()
1251            .with_initial_variable_constraints([BooleanExpression::ComparisonExpression(
1252                Box::new(IntegerExpression::Atom(var1.clone())),
1253                ComparisonOp::Eq,
1254                Box::new(IntegerExpression::Const(0)),
1255            )])
1256            .unwrap()
1257            .with_rule(
1258                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
1259                    .with_guard(BooleanExpression::ComparisonExpression(
1260                        Box::new(IntegerExpression::Atom(var1.clone())),
1261                        ComparisonOp::Gt,
1262                        Box::new(IntegerExpression::Const(2)),
1263                    ))
1264                    .with_action(
1265                        Action::new(
1266                            var1.clone(),
1267                            IntegerExpression::Const(1) + IntegerExpression::Atom(var1.clone()),
1268                        )
1269                        .unwrap(),
1270                    )
1271                    .build(),
1272            )
1273            .unwrap()
1274            .build();
1275
1276        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
1277
1278        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
1279            .build()
1280            .unwrap();
1281        let interval_threshold_automaton = interval_tas.next().unwrap();
1282        assert!(interval_tas.next().is_none());
1283
1284        assert_eq!(
1285            interval_threshold_automaton.get_initial_interval(&var1),
1286            vec![&Interval::zero(),]
1287        );
1288        assert_eq!(
1289            interval_threshold_automaton.get_initial_interval(&var2),
1290            vec![&Interval::zero(), &i4]
1291        );
1292    }
1293
1294    #[test]
1295    fn test_interval_action_getters() {
1296        let var = Variable::new("x");
1297        let action = IntervalTAAction::new(var.clone(), IntervalActionEffect::Inc(1));
1298
1299        assert_eq!(action.variable(), &var);
1300        assert!(!action.is_unchanged());
1301        assert!(!action.is_reset());
1302        assert!(action.is_increment());
1303        assert!(!action.is_decrement());
1304
1305        let action = IntervalTAAction::new(var.clone(), IntervalActionEffect::Reset);
1306
1307        assert_eq!(action.variable(), &var);
1308        assert!(!action.is_unchanged());
1309        assert!(action.is_reset());
1310        assert!(!action.is_increment());
1311        assert!(!action.is_decrement());
1312
1313        let action = IntervalTAAction::new(var.clone(), IntervalActionEffect::Dec(1));
1314
1315        assert_eq!(action.variable(), &var);
1316        assert!(!action.is_unchanged());
1317        assert!(!action.is_reset());
1318        assert!(!action.is_increment());
1319        assert!(action.is_decrement());
1320    }
1321
1322    #[test]
1323    fn test_interval_guard_is_enabled() {
1324        let i1 = Interval::new_constant(0, 1);
1325        let i2 = Interval::new_constant(2, 3);
1326        let i3 = Interval::new_constant(4, 5);
1327
1328        let var = Variable::new("x");
1329        let state = IntervalState {
1330            t_to_interval: HashMap::from([(var.clone(), i1.clone())]),
1331        };
1332
1333        let guard = IntervalConstraint::SingleVarIntervalConstr(
1334            var.clone(),
1335            Vec::from([i1.clone(), i2.clone()]),
1336        );
1337        assert!(guard.is_enabled(&state).unwrap());
1338
1339        let guard = IntervalConstraint::SingleVarIntervalConstr(
1340            var.clone(),
1341            Vec::from([i2.clone(), i3.clone()]),
1342        );
1343        assert!(!guard.is_enabled(&state).unwrap());
1344
1345        let guard = IntervalConstraint::Conj(
1346            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1347                var.clone(),
1348                Vec::from([i1.clone(), i3.clone()]),
1349            )),
1350            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1351                var.clone(),
1352                Vec::from([i1.clone(), i2.clone()]),
1353            )),
1354        );
1355        assert!(guard.is_enabled(&state).unwrap());
1356
1357        let guard = IntervalConstraint::Conj(
1358            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1359                var.clone(),
1360                Vec::from([i1.clone(), i2.clone()]),
1361            )),
1362            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1363                var.clone(),
1364                Vec::from([i3.clone()]),
1365            )),
1366        );
1367        assert!(!guard.is_enabled(&state).unwrap());
1368
1369        let guard = IntervalConstraint::Disj(
1370            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1371                var.clone(),
1372                Vec::from([i1.clone()]),
1373            )),
1374            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1375                var.clone(),
1376                Vec::from([i3.clone()]),
1377            )),
1378        );
1379        assert!(guard.is_enabled(&state).unwrap());
1380
1381        let guard = IntervalConstraint::Disj(
1382            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1383                var.clone(),
1384                Vec::from([i2.clone()]),
1385            )),
1386            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1387                var.clone(),
1388                Vec::from([i3.clone()]),
1389            )),
1390        );
1391        assert!(!guard.is_enabled(&state).unwrap());
1392
1393        let guard = IntervalConstraint::True;
1394        assert!(guard.is_enabled(&state).unwrap());
1395    }
1396
1397    #[test]
1398    fn test_interval_guard_is_enabled_error() {
1399        let i1 = Interval::new_constant(0, 1);
1400
1401        let state: IntervalState<Variable> = IntervalState {
1402            t_to_interval: HashMap::new(),
1403        };
1404
1405        let var = Variable::new("x");
1406        let guard =
1407            IntervalConstraint::SingleVarIntervalConstr(var.clone(), Vec::from([i1.clone()]));
1408
1409        let err = guard.is_enabled(&state);
1410        assert!(matches!(err, Err(IntervalOrderError::VariableNotFound { var: v }) if v == var));
1411    }
1412
1413    #[test]
1414    fn test_guard_to_boolean_expr() {
1415        let guard = IntervalConstraint::True;
1416        assert_eq!(guard.as_boolean_expr(), BooleanExpression::True);
1417
1418        let guard = IntervalConstraint::False;
1419        assert_eq!(guard.as_boolean_expr(), BooleanExpression::False);
1420
1421        let var = Variable::new("x");
1422        let i1 = Interval::new_constant(0, 1);
1423        let i2 = Interval::new_constant(1, 2);
1424
1425        let guard =
1426            IntervalConstraint::SingleVarIntervalConstr(var.clone(), vec![i1.clone(), i2.clone()]);
1427        assert_eq!(
1428            guard.as_boolean_expr(),
1429            BooleanExpression::BinaryExpression(
1430                Box::new(BooleanExpression::BinaryExpression(
1431                    Box::new(BooleanExpression::False),
1432                    BooleanConnective::Or,
1433                    Box::new(BooleanExpression::BinaryExpression(
1434                        Box::new(BooleanExpression::ComparisonExpression(
1435                            Box::new(IntegerExpression::Atom(var.clone())),
1436                            ComparisonOp::Geq,
1437                            Box::new(IntegerExpression::Const(0))
1438                        )),
1439                        BooleanConnective::And,
1440                        Box::new(BooleanExpression::ComparisonExpression(
1441                            Box::new(IntegerExpression::Atom(var.clone())),
1442                            ComparisonOp::Lt,
1443                            Box::new(IntegerExpression::Const(1))
1444                        ))
1445                    )),
1446                )),
1447                BooleanConnective::Or,
1448                Box::new(BooleanExpression::BinaryExpression(
1449                    Box::new(BooleanExpression::ComparisonExpression(
1450                        Box::new(IntegerExpression::Atom(var.clone())),
1451                        ComparisonOp::Geq,
1452                        Box::new(IntegerExpression::Const(1))
1453                    )),
1454                    BooleanConnective::And,
1455                    Box::new(BooleanExpression::ComparisonExpression(
1456                        Box::new(IntegerExpression::Atom(var.clone())),
1457                        ComparisonOp::Lt,
1458                        Box::new(IntegerExpression::Const(2))
1459                    ))
1460                ))
1461            )
1462        );
1463
1464        let guard = IntervalConstraint::MultiVarIntervalConstr(
1465            WeightedSum::new([(var.clone(), 1)]),
1466            vec![i1.clone(), i2.clone()],
1467        );
1468        assert_eq!(
1469            guard.as_boolean_expr(),
1470            BooleanExpression::BinaryExpression(
1471                Box::new(BooleanExpression::BinaryExpression(
1472                    Box::new(BooleanExpression::False),
1473                    BooleanConnective::Or,
1474                    Box::new(BooleanExpression::BinaryExpression(
1475                        Box::new(BooleanExpression::ComparisonExpression(
1476                            Box::new(IntegerExpression::Atom(var.clone())),
1477                            ComparisonOp::Geq,
1478                            Box::new(IntegerExpression::Const(0))
1479                        )),
1480                        BooleanConnective::And,
1481                        Box::new(BooleanExpression::ComparisonExpression(
1482                            Box::new(IntegerExpression::Atom(var.clone())),
1483                            ComparisonOp::Lt,
1484                            Box::new(IntegerExpression::Const(1))
1485                        ))
1486                    )),
1487                )),
1488                BooleanConnective::Or,
1489                Box::new(BooleanExpression::BinaryExpression(
1490                    Box::new(BooleanExpression::ComparisonExpression(
1491                        Box::new(IntegerExpression::Atom(var.clone())),
1492                        ComparisonOp::Geq,
1493                        Box::new(IntegerExpression::Const(1))
1494                    )),
1495                    BooleanConnective::And,
1496                    Box::new(BooleanExpression::ComparisonExpression(
1497                        Box::new(IntegerExpression::Atom(var.clone())),
1498                        ComparisonOp::Lt,
1499                        Box::new(IntegerExpression::Const(2))
1500                    ))
1501                ))
1502            )
1503        );
1504
1505        let guard = IntervalConstraint::Conj(
1506            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1507                var.clone(),
1508                vec![i1.clone()],
1509            )),
1510            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1511                var.clone(),
1512                vec![i2.clone()],
1513            )),
1514        );
1515        assert_eq!(
1516            guard.as_boolean_expr(),
1517            BooleanExpression::BinaryExpression(
1518                Box::new(BooleanExpression::BinaryExpression(
1519                    Box::new(BooleanExpression::False),
1520                    BooleanConnective::Or,
1521                    Box::new(BooleanExpression::BinaryExpression(
1522                        Box::new(BooleanExpression::ComparisonExpression(
1523                            Box::new(IntegerExpression::Atom(var.clone())),
1524                            ComparisonOp::Geq,
1525                            Box::new(IntegerExpression::Const(0))
1526                        )),
1527                        BooleanConnective::And,
1528                        Box::new(BooleanExpression::ComparisonExpression(
1529                            Box::new(IntegerExpression::Atom(var.clone())),
1530                            ComparisonOp::Lt,
1531                            Box::new(IntegerExpression::Const(1))
1532                        ))
1533                    )),
1534                )),
1535                BooleanConnective::And,
1536                Box::new(BooleanExpression::BinaryExpression(
1537                    Box::new(BooleanExpression::False),
1538                    BooleanConnective::Or,
1539                    Box::new(BooleanExpression::BinaryExpression(
1540                        Box::new(BooleanExpression::ComparisonExpression(
1541                            Box::new(IntegerExpression::Atom(var.clone())),
1542                            ComparisonOp::Geq,
1543                            Box::new(IntegerExpression::Const(1))
1544                        )),
1545                        BooleanConnective::And,
1546                        Box::new(BooleanExpression::ComparisonExpression(
1547                            Box::new(IntegerExpression::Atom(var.clone())),
1548                            ComparisonOp::Lt,
1549                            Box::new(IntegerExpression::Const(2))
1550                        ))
1551                    )),
1552                )),
1553            )
1554        );
1555
1556        let guard = IntervalConstraint::Disj(
1557            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1558                var.clone(),
1559                vec![i1.clone()],
1560            )),
1561            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1562                var.clone(),
1563                vec![i2.clone()],
1564            )),
1565        );
1566
1567        assert_eq!(
1568            guard.as_boolean_expr(),
1569            BooleanExpression::BinaryExpression(
1570                Box::new(BooleanExpression::BinaryExpression(
1571                    Box::new(BooleanExpression::False),
1572                    BooleanConnective::Or,
1573                    Box::new(BooleanExpression::BinaryExpression(
1574                        Box::new(BooleanExpression::ComparisonExpression(
1575                            Box::new(IntegerExpression::Atom(var.clone())),
1576                            ComparisonOp::Geq,
1577                            Box::new(IntegerExpression::Const(0))
1578                        )),
1579                        BooleanConnective::And,
1580                        Box::new(BooleanExpression::ComparisonExpression(
1581                            Box::new(IntegerExpression::Atom(var.clone())),
1582                            ComparisonOp::Lt,
1583                            Box::new(IntegerExpression::Const(1))
1584                        ))
1585                    )),
1586                )),
1587                BooleanConnective::Or,
1588                Box::new(BooleanExpression::BinaryExpression(
1589                    Box::new(BooleanExpression::False),
1590                    BooleanConnective::Or,
1591                    Box::new(BooleanExpression::BinaryExpression(
1592                        Box::new(BooleanExpression::ComparisonExpression(
1593                            Box::new(IntegerExpression::Atom(var.clone())),
1594                            ComparisonOp::Geq,
1595                            Box::new(IntegerExpression::Const(1))
1596                        )),
1597                        BooleanConnective::And,
1598                        Box::new(BooleanExpression::ComparisonExpression(
1599                            Box::new(IntegerExpression::Atom(var.clone())),
1600                            ComparisonOp::Lt,
1601                            Box::new(IntegerExpression::Const(2))
1602                        ))
1603                    )),
1604                )),
1605            )
1606        );
1607    }
1608
1609    #[test]
1610    fn test_display_interval_threshold_automaton() {
1611        let var = Variable::new("x");
1612
1613        let rc = BooleanExpression::ComparisonExpression(
1614            Box::new(IntegerExpression::Param(Parameter::new("n"))),
1615            ComparisonOp::Gt,
1616            Box::new(IntegerExpression::Const(3)),
1617        );
1618
1619        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1620            .with_variable(var.clone())
1621            .unwrap()
1622            .with_locations([Location::new("l1"), Location::new("l2")])
1623            .unwrap()
1624            .with_parameter(Parameter::new("n"))
1625            .unwrap()
1626            .initialize()
1627            .with_resilience_condition(rc.clone())
1628            .unwrap()
1629            .with_rule(
1630                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
1631                    .with_guard(BooleanExpression::ComparisonExpression(
1632                        Box::new(IntegerExpression::Atom(var.clone())),
1633                        ComparisonOp::Gt,
1634                        Box::new(IntegerExpression::Const(2)),
1635                    ))
1636                    .with_action(
1637                        Action::new(
1638                            var.clone(),
1639                            IntegerExpression::Const(1) + IntegerExpression::Atom(var.clone()),
1640                        )
1641                        .unwrap(),
1642                    )
1643                    .build(),
1644            )
1645            .unwrap()
1646            .build();
1647
1648        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
1649
1650        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
1651            .build()
1652            .unwrap();
1653        let interval_threshold_automaton = interval_tas.next().unwrap();
1654        assert!(interval_tas.next().is_none());
1655
1656        let expected = "thresholdAutomaton test_ta {\n    intervalOrder {\n        x: [0, 1[, [1, 3[, [3, ∞[;\n    }\n\n    shared x;\n\n    parameters n;\n\n    assumptions (3) {\n        n > 3;\n        0 < 1;\n        1 < 3;\n    }\n\n    locations (2) {\n        l1:[0];\n        l2:[1];\n    }\n\n    inits (0) {\n    }\n\n    rules (1) {\n        0: l1 -> l2\n            when ( x ∈ { [3, ∞[ } )\n            do { x ++ };\n    }\n}\n";
1657
1658        assert_eq!(interval_threshold_automaton.to_string(), expected);
1659    }
1660
1661    #[test]
1662    fn test_interval_constraint_from_lia() {
1663        let i0 = Interval::new_constant(0, 1);
1664        let i1 = Interval::new_constant(1, 2);
1665        let i2 = Interval::new(
1666            IntervalBoundary::from_const(2),
1667            false,
1668            IntervalBoundary::new_bound(
1669                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1670                Fraction::from(0),
1671            ),
1672            true,
1673        );
1674        let i3 = Interval::new(
1675            IntervalBoundary::new_bound(
1676                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1677                Fraction::from(0),
1678            ),
1679            true,
1680            IntervalBoundary::Infty,
1681            true,
1682        );
1683
1684        let order = StaticIntervalOrder::new(
1685            HashMap::from([(
1686                Variable::new("x"),
1687                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1688            )]),
1689            HashMap::from([(
1690                WeightedSum::new([
1691                    (Variable::new("x"), Fraction::from(1)),
1692                    (Variable::new("y"), Fraction::from(1)),
1693                ]),
1694                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1695            )]),
1696            HashMap::new(),
1697        );
1698
1699        // x > 2
1700        let lguard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
1701            Variable::new("x"),
1702            ThresholdConstraint::new(
1703                ThresholdCompOp::Geq,
1704                Vec::<(_, u32)>::new(),
1705                Fraction::from(2),
1706            ),
1707        ));
1708        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1709        let expected_guard = IntervalConstraint::SingleVarIntervalConstr(
1710            Variable::new("x"),
1711            vec![i2.clone(), i3.clone()],
1712        );
1713        // x ∈ {[2, ∞[}
1714        assert_eq!(
1715            got_guard, expected_guard,
1716            "Got {got_guard} \n Expected {expected_guard}"
1717        );
1718
1719        // x + y < 2
1720        let lguard = LIAVariableConstraint::SumVarConstraint(
1721            SumAtomConstraint::try_new(
1722                [
1723                    (Variable::new("x"), Fraction::from(1)),
1724                    (Variable::new("y"), Fraction::from(1)),
1725                ],
1726                ThresholdConstraint::new(
1727                    ThresholdCompOp::Lt,
1728                    Vec::<(_, u32)>::new(),
1729                    Fraction::from(2),
1730                ),
1731            )
1732            .unwrap(),
1733        );
1734        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1735        let expected_guard = IntervalConstraint::MultiVarIntervalConstr(
1736            WeightedSum::new([
1737                (Variable::new("x"), Fraction::from(1)),
1738                (Variable::new("y"), Fraction::from(1)),
1739            ]),
1740            vec![i0.clone(), i1.clone()],
1741        );
1742        // x + y ∈ {[0, 1[, [1, 2[}
1743        assert_eq!(
1744            got_guard, expected_guard,
1745            "Got {got_guard} \n Expected {expected_guard}"
1746        );
1747
1748        // x < 2 && x >= 1
1749        let lguard = LIAVariableConstraint::BinaryGuard(
1750            Box::new(LIAVariableConstraint::SingleVarConstraint(
1751                SingleAtomConstraint::new(
1752                    Variable::new("x"),
1753                    ThresholdConstraint::new(
1754                        ThresholdCompOp::Lt,
1755                        Vec::<(_, u32)>::new(),
1756                        Fraction::from(2),
1757                    ),
1758                ),
1759            )),
1760            BooleanConnective::And,
1761            Box::new(LIAVariableConstraint::SingleVarConstraint(
1762                SingleAtomConstraint::new(
1763                    Variable::new("x"),
1764                    ThresholdConstraint::new(
1765                        ThresholdCompOp::Geq,
1766                        Vec::<(_, u32)>::new(),
1767                        Fraction::from(1),
1768                    ),
1769                ),
1770            )),
1771        );
1772        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1773        let expected_guard = IntervalConstraint::Conj(
1774            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1775                Variable::new("x"),
1776                vec![i0.clone(), i1.clone()],
1777            )),
1778            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1779                Variable::new("x"),
1780                vec![i1.clone(), i2.clone(), i3.clone()],
1781            )),
1782        );
1783        // x ∈ {[0,1[, [1, 2[} && {[1,2[, [2, ∞[}
1784        assert_eq!(
1785            got_guard, expected_guard,
1786            "Got {got_guard} \n Expected {expected_guard}"
1787        );
1788
1789        // x < 2 || x >= 1
1790        let lguard = LIAVariableConstraint::BinaryGuard(
1791            Box::new(LIAVariableConstraint::SingleVarConstraint(
1792                SingleAtomConstraint::new(
1793                    Variable::new("x"),
1794                    ThresholdConstraint::new(
1795                        ThresholdCompOp::Lt,
1796                        Vec::<(_, u32)>::new(),
1797                        Fraction::from(2),
1798                    ),
1799                ),
1800            )),
1801            BooleanConnective::Or,
1802            Box::new(LIAVariableConstraint::SingleVarConstraint(
1803                SingleAtomConstraint::new(
1804                    Variable::new("x"),
1805                    ThresholdConstraint::new(
1806                        ThresholdCompOp::Geq,
1807                        Vec::<(_, u32)>::new(),
1808                        Fraction::from(1),
1809                    ),
1810                ),
1811            )),
1812        );
1813        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1814        let expected_guard = IntervalConstraint::Disj(
1815            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1816                Variable::new("x"),
1817                vec![i0.clone(), i1.clone()],
1818            )),
1819            Box::new(IntervalConstraint::SingleVarIntervalConstr(
1820                Variable::new("x"),
1821                vec![i1.clone(), i2.clone(), i3.clone()],
1822            )),
1823        );
1824        // x ∈ {[0,1[, [1, 2[} || {[1,2[, [2, ∞[}
1825        assert_eq!(
1826            got_guard, expected_guard,
1827            "Got {got_guard} \n Expected {expected_guard}"
1828        );
1829    }
1830
1831    #[test]
1832    fn test_from_lia_simplify_and() {
1833        let i0 = Interval::new_constant(0, 1);
1834        let i1 = Interval::new_constant(1, 2);
1835        let i2 = Interval::new(
1836            IntervalBoundary::from_const(2),
1837            false,
1838            IntervalBoundary::new_bound(
1839                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1840                Fraction::from(0),
1841            ),
1842            true,
1843        );
1844        let i3 = Interval::new(
1845            IntervalBoundary::new_bound(
1846                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1847                Fraction::from(0),
1848            ),
1849            true,
1850            IntervalBoundary::Infty,
1851            true,
1852        );
1853
1854        let order = StaticIntervalOrder::new(
1855            HashMap::from([(
1856                Variable::new("x"),
1857                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1858            )]),
1859            HashMap::from([(
1860                WeightedSum::new([
1861                    (Variable::new("x"), Fraction::from(1)),
1862                    (Variable::new("y"), Fraction::from(1)),
1863                ]),
1864                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1865            )]),
1866            HashMap::new(),
1867        );
1868
1869        // (x >= 2) && true
1870        let lguard = LIAVariableConstraint::BinaryGuard(
1871            Box::new(LIAVariableConstraint::SingleVarConstraint(
1872                SingleAtomConstraint::new(
1873                    Variable::new("x"),
1874                    ThresholdConstraint::new(
1875                        ThresholdCompOp::Geq,
1876                        Vec::<(_, u32)>::new(),
1877                        Fraction::from(2),
1878                    ),
1879                ),
1880            )),
1881            BooleanConnective::And,
1882            Box::new(LIAVariableConstraint::True),
1883        );
1884        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1885        let expected_guard = IntervalConstraint::SingleVarIntervalConstr(
1886            Variable::new("x"),
1887            vec![i2.clone(), i3.clone()],
1888        );
1889        // x ∈ {[2, ∞[}
1890        assert_eq!(
1891            got_guard, expected_guard,
1892            "Got {got_guard} \n Expected {expected_guard}"
1893        );
1894
1895        // true && (x >= 2)
1896        let lguard = LIAVariableConstraint::BinaryGuard(
1897            Box::new(LIAVariableConstraint::True),
1898            BooleanConnective::And,
1899            Box::new(LIAVariableConstraint::SingleVarConstraint(
1900                SingleAtomConstraint::new(
1901                    Variable::new("x"),
1902                    ThresholdConstraint::new(
1903                        ThresholdCompOp::Geq,
1904                        Vec::<(_, u32)>::new(),
1905                        Fraction::from(2),
1906                    ),
1907                ),
1908            )),
1909        );
1910        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1911        let expected_guard = IntervalConstraint::SingleVarIntervalConstr(
1912            Variable::new("x"),
1913            vec![i2.clone(), i3.clone()],
1914        );
1915        // x ∈ {[2, ∞[}
1916        assert_eq!(
1917            got_guard, expected_guard,
1918            "Got {got_guard} \n Expected {expected_guard}"
1919        );
1920    }
1921
1922    #[test]
1923    fn test_from_lia_simplify_or() {
1924        let i0 = Interval::new_constant(0, 1);
1925        let i1 = Interval::new_constant(1, 2);
1926        let i2 = Interval::new(
1927            IntervalBoundary::from_const(2),
1928            false,
1929            IntervalBoundary::new_bound(
1930                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1931                Fraction::from(0),
1932            ),
1933            true,
1934        );
1935        let i3 = Interval::new(
1936            IntervalBoundary::new_bound(
1937                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
1938                Fraction::from(0),
1939            ),
1940            true,
1941            IntervalBoundary::Infty,
1942            true,
1943        );
1944
1945        let order = StaticIntervalOrder::new(
1946            HashMap::from([(
1947                Variable::new("x"),
1948                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1949            )]),
1950            HashMap::from([(
1951                WeightedSum::new([
1952                    (Variable::new("x"), Fraction::from(1)),
1953                    (Variable::new("y"), Fraction::from(1)),
1954                ]),
1955                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
1956            )]),
1957            HashMap::new(),
1958        );
1959
1960        // (x >= 2) || false
1961        let lguard = LIAVariableConstraint::BinaryGuard(
1962            Box::new(LIAVariableConstraint::SingleVarConstraint(
1963                SingleAtomConstraint::new(
1964                    Variable::new("x"),
1965                    ThresholdConstraint::new(
1966                        ThresholdCompOp::Geq,
1967                        Vec::<(_, u32)>::new(),
1968                        Fraction::from(2),
1969                    ),
1970                ),
1971            )),
1972            BooleanConnective::Or,
1973            Box::new(LIAVariableConstraint::False),
1974        );
1975        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
1976        let expected_guard = IntervalConstraint::SingleVarIntervalConstr(
1977            Variable::new("x"),
1978            vec![i2.clone(), i3.clone()],
1979        );
1980        // x ∈ {[2, ∞[}
1981        assert_eq!(
1982            got_guard, expected_guard,
1983            "Got {got_guard} \n Expected {expected_guard}"
1984        );
1985
1986        // false || (x >= 2)
1987        let lguard = LIAVariableConstraint::BinaryGuard(
1988            Box::new(LIAVariableConstraint::False),
1989            BooleanConnective::Or,
1990            Box::new(LIAVariableConstraint::SingleVarConstraint(
1991                SingleAtomConstraint::new(
1992                    Variable::new("x"),
1993                    ThresholdConstraint::new(
1994                        ThresholdCompOp::Geq,
1995                        Vec::<(_, u32)>::new(),
1996                        Fraction::from(2),
1997                    ),
1998                ),
1999            )),
2000        );
2001        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
2002        let expected_guard = IntervalConstraint::SingleVarIntervalConstr(
2003            Variable::new("x"),
2004            vec![i2.clone(), i3.clone()],
2005        );
2006        // x ∈ {[2, ∞[}
2007        assert_eq!(
2008            got_guard, expected_guard,
2009            "Got {got_guard} \n Expected {expected_guard}"
2010        );
2011    }
2012
2013    #[test]
2014    fn test_from_lia_simplify_interval_all_or_none() {
2015        let i0 = Interval::new_constant(0, 1);
2016        let i1 = Interval::new_constant(1, 2);
2017        let i2 = Interval::new(
2018            IntervalBoundary::from_const(2),
2019            false,
2020            IntervalBoundary::new_bound(
2021                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
2022                Fraction::from(0),
2023            ),
2024            true,
2025        );
2026        let i3 = Interval::new(
2027            IntervalBoundary::new_bound(
2028                WeightedSum::new([(Parameter::new("n"), Fraction::from(1))]),
2029                Fraction::from(0),
2030            ),
2031            true,
2032            IntervalBoundary::Infty,
2033            true,
2034        );
2035
2036        let order = StaticIntervalOrder::new(
2037            HashMap::from([(
2038                Variable::new("x"),
2039                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
2040            )]),
2041            HashMap::from([(
2042                WeightedSum::new([
2043                    (Variable::new("x"), Fraction::from(1)),
2044                    (Variable::new("y"), Fraction::from(1)),
2045                ]),
2046                vec![i0.clone(), i1.clone(), i2.clone(), i3.clone()],
2047            )]),
2048            HashMap::new(),
2049        );
2050
2051        // x >= 0
2052        let lguard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
2053            Variable::new("x"),
2054            ThresholdConstraint::new(
2055                ThresholdCompOp::Geq,
2056                Vec::<(_, u32)>::new(),
2057                Fraction::from(0),
2058            ),
2059        ));
2060        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
2061        let expected_guard = IntervalConstraint::True;
2062        // true
2063        assert_eq!(
2064            got_guard, expected_guard,
2065            "Got {got_guard} \n Expected {expected_guard}"
2066        );
2067
2068        // x < 0
2069        let lguard = LIAVariableConstraint::SingleVarConstraint(SingleAtomConstraint::new(
2070            Variable::new("x"),
2071            ThresholdConstraint::new(
2072                ThresholdCompOp::Lt,
2073                Vec::<(_, u32)>::new(),
2074                Fraction::from(0),
2075            ),
2076        ));
2077        let got_guard = IntervalConstraint::from_lia_constr(&lguard, &order).unwrap();
2078        let expected_guard = IntervalConstraint::False;
2079        // true
2080        assert_eq!(
2081            got_guard, expected_guard,
2082            "Got {got_guard} \n Expected {expected_guard}"
2083        );
2084    }
2085
2086    #[test]
2087    fn test_get_interval_constr() {
2088        let var = Variable::new("x");
2089
2090        let rc = BooleanExpression::ComparisonExpression(
2091            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2092            ComparisonOp::Gt,
2093            Box::new(IntegerExpression::Const(3)),
2094        );
2095
2096        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
2097            .with_variables([var.clone(), Variable::new("y")])
2098            .unwrap()
2099            .with_locations([Location::new("l1"), Location::new("l2")])
2100            .unwrap()
2101            .with_parameter(Parameter::new("n"))
2102            .unwrap()
2103            .initialize()
2104            .with_resilience_condition(rc.clone())
2105            .unwrap()
2106            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2107                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2108                ComparisonOp::Eq,
2109                Box::new(IntegerExpression::Const(0)),
2110            ))
2111            .unwrap()
2112            .with_rule(
2113                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
2114                    .with_guard(BooleanExpression::ComparisonExpression(
2115                        Box::new(IntegerExpression::Atom(var.clone())),
2116                        ComparisonOp::Gt,
2117                        Box::new(IntegerExpression::Const(2)),
2118                    ))
2119                    .with_action(
2120                        Action::new(
2121                            var.clone(),
2122                            IntegerExpression::Const(1) + IntegerExpression::Atom(var.clone()),
2123                        )
2124                        .unwrap(),
2125                    )
2126                    .build(),
2127            )
2128            .unwrap()
2129            .build();
2130
2131        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
2132
2133        let lia_constr: LIAVariableConstraint = (&BooleanExpression::ComparisonExpression(
2134            Box::new(IntegerExpression::Atom(Variable::new("x"))),
2135            ComparisonOp::Eq,
2136            Box::new(IntegerExpression::Const(0)),
2137        ))
2138            .try_into()
2139            .unwrap();
2140
2141        let mut interval_tas = IntervalTABuilder::new(
2142            lia_ta,
2143            SMTSolverBuilder::default(),
2144            vec![lia_constr.clone()],
2145        )
2146        .build()
2147        .unwrap();
2148        let interval_ta = interval_tas.next().unwrap();
2149
2150        let expected_interval_constr = IntervalConstraint::SingleVarIntervalConstr(
2151            Variable::new("x"),
2152            vec![Interval::new_constant(0, 1)],
2153        );
2154
2155        assert_eq!(
2156            interval_ta.get_interval_constraint(&lia_constr).unwrap(),
2157            expected_interval_constr
2158        )
2159    }
2160
2161    #[test]
2162    fn test_get_interval_constr_unknown_var() {
2163        let var = Variable::new("x");
2164
2165        let rc = BooleanExpression::ComparisonExpression(
2166            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2167            ComparisonOp::Gt,
2168            Box::new(IntegerExpression::Const(3)),
2169        );
2170
2171        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
2172            .with_variables([var.clone(), Variable::new("y")])
2173            .unwrap()
2174            .with_locations([Location::new("l1"), Location::new("l2")])
2175            .unwrap()
2176            .with_parameter(Parameter::new("n"))
2177            .unwrap()
2178            .initialize()
2179            .with_resilience_condition(rc.clone())
2180            .unwrap()
2181            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2182                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2183                ComparisonOp::Eq,
2184                Box::new(IntegerExpression::Const(0)),
2185            ))
2186            .unwrap()
2187            .with_rule(
2188                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
2189                    .with_guard(BooleanExpression::ComparisonExpression(
2190                        Box::new(IntegerExpression::Atom(var.clone())),
2191                        ComparisonOp::Gt,
2192                        Box::new(IntegerExpression::Const(2)),
2193                    ))
2194                    .with_action(
2195                        Action::new(
2196                            var.clone(),
2197                            IntegerExpression::Const(1) + IntegerExpression::Atom(var.clone()),
2198                        )
2199                        .unwrap(),
2200                    )
2201                    .build(),
2202            )
2203            .unwrap()
2204            .build();
2205
2206        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
2207
2208        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
2209            .build()
2210            .unwrap();
2211        let interval_ta = interval_tas.next().unwrap();
2212
2213        let lia_constr: LIAVariableConstraint = (&BooleanExpression::ComparisonExpression(
2214            Box::new(IntegerExpression::Atom(Variable::new("unknown"))),
2215            ComparisonOp::Eq,
2216            Box::new(IntegerExpression::Const(0)),
2217        ))
2218            .try_into()
2219            .unwrap();
2220
2221        let got_interval_constr = interval_ta.get_interval_constraint(&lia_constr);
2222
2223        assert!(got_interval_constr.is_err());
2224        assert_eq!(
2225            got_interval_constr.unwrap_err(),
2226            IntervalConstraintConstructionError::VarError(IntervalOrderError::VariableNotFound {
2227                var: Variable::new("unknown")
2228            })
2229        )
2230    }
2231
2232    #[test]
2233    fn test_get_constrained_variables() {
2234        let var = Variable::new("x");
2235
2236        let rc = BooleanExpression::ComparisonExpression(
2237            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2238            ComparisonOp::Gt,
2239            Box::new(IntegerExpression::Const(3)),
2240        );
2241
2242        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
2243            .with_variables([var.clone(), Variable::new("y")])
2244            .unwrap()
2245            .with_locations([Location::new("l1"), Location::new("l2")])
2246            .unwrap()
2247            .with_parameter(Parameter::new("n"))
2248            .unwrap()
2249            .initialize()
2250            .with_resilience_condition(rc.clone())
2251            .unwrap()
2252            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2253                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2254                ComparisonOp::Eq,
2255                Box::new(IntegerExpression::Const(0)),
2256            ))
2257            .unwrap()
2258            .with_rule(
2259                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
2260                    .with_guard(BooleanExpression::ComparisonExpression(
2261                        Box::new(IntegerExpression::Atom(var.clone())),
2262                        ComparisonOp::Gt,
2263                        Box::new(IntegerExpression::Const(2)),
2264                    ))
2265                    .with_action(
2266                        Action::new(
2267                            var.clone(),
2268                            IntegerExpression::Const(1) + IntegerExpression::Atom(var.clone()),
2269                        )
2270                        .unwrap(),
2271                    )
2272                    .build(),
2273            )
2274            .unwrap()
2275            .build();
2276
2277        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
2278
2279        let lia_constr: LIAVariableConstraint = (&BooleanExpression::ComparisonExpression(
2280            Box::new(IntegerExpression::Atom(Variable::new("x"))),
2281            ComparisonOp::Eq,
2282            Box::new(IntegerExpression::Const(10)),
2283        ))
2284            .try_into()
2285            .unwrap();
2286
2287        let mut interval_tas = IntervalTABuilder::new(
2288            lia_ta,
2289            SMTSolverBuilder::default(),
2290            vec![lia_constr.clone()],
2291        )
2292        .build()
2293        .unwrap();
2294        let interval_ta = interval_tas.next().unwrap();
2295
2296        let interval_constr = interval_ta.get_interval_constraint(&lia_constr).unwrap();
2297
2298        assert_eq!(
2299            interval_ta.get_variables_constrained(&interval_constr),
2300            vec![&Variable::new("x")]
2301        )
2302    }
2303
2304    #[test]
2305    fn test_display_interval_constraint_construction_error() {
2306        let var_error: IntervalConstraintConstructionError = IntervalOrderError::VariableNotFound {
2307            var: Variable::new("x"),
2308        }
2309        .into();
2310        assert!(
2311            var_error
2312                .to_string()
2313                .contains("Error while constructing an IntervalConstraint:"),
2314        );
2315
2316        let sum_var_error: IntervalConstraintConstructionError =
2317            IntervalOrderError::VariableNotFound {
2318                var: WeightedSum::new([(Variable::new("x"), 1), (Variable::new("y"), 1)]),
2319            }
2320            .into();
2321        assert!(
2322            sum_var_error
2323                .to_string()
2324                .contains("Error while constructing an IntervalConstraint:"),
2325        );
2326    }
2327
2328    #[test]
2329    fn test_display_interval_ta_transformation_error() {
2330        let var = Variable::new("x");
2331        let rule = RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
2332            .with_guard(BooleanExpression::ComparisonExpression(
2333                Box::new(IntegerExpression::Atom(var.clone())),
2334                ComparisonOp::Gt,
2335                Box::new(IntegerExpression::Atom(Variable::new("y"))),
2336            ))
2337            .build()
2338            .try_into()
2339            .unwrap();
2340        let error = IntervalTATransformationError::ComparisonGuardFoundRule(Box::new(rule));
2341        assert!(
2342            error
2343                .to_string()
2344                .contains("Found comparison guard which is unsupported")
2345        );
2346
2347        let constr: LIAVariableConstraint = BooleanExpression::ComparisonExpression(
2348            Box::new(IntegerExpression::Atom(var.clone())),
2349            ComparisonOp::Gt,
2350            Box::new(IntegerExpression::Atom(Variable::new("y"))),
2351        )
2352        .try_into()
2353        .unwrap();
2354        let error = IntervalTATransformationError::ComparisonGuardFoundInitialVariableConstraint(
2355            Box::new(constr.clone()),
2356        );
2357        assert!(
2358            error
2359                .to_string()
2360                .contains("Found comparison guard in initial variable constraint")
2361        );
2362
2363        let error =
2364            IntervalTATransformationError::ComparisonGuardFoundVariableTarget(Box::new(constr));
2365        assert!(
2366            error
2367                .to_string()
2368                .contains("Found comparison guard as part of a interval target constraint")
2369        );
2370    }
2371}