taco_model_checker/
reachability_specification.rs

1//! Reachability specification for model checkers
2//!
3//! This module defines the types for reachability specifications in threshold
4//! automata. On a high-level, a reachability specification asks whether an
5//! upwards closed set of locations can be reached.
6//!
7//! In more detail, a reachability consists of two parts:
8//! - A constraint that narrows down the set of initial locations (we will also
9//!   name this part of the specification `init`).
10//! - A constraint specifying the set of configurations that should be
11//!   reached (we will also call this part of the constraint `target`).
12//!
13//! Such a constraint can be seen as an (E)LTL formula:
14//!     `(init) => <>(target)`
15//!
16//! This module contains the logic to transform an arbitrary ELTL constraint
17//! into a constraint of that form, assuming that it is a safety constraint
18//! (i.e., it does not contain a `[]`).
19//!
20//! A reachability specification allows to specify a set of locations
21//! with a number of processes that must be **at least** in that location, as
22//! well as a set of locations that should not contain any processes.
23//! Additionally, one can specify a constraint on the valuations of the shared
24//! variables.
25
26use std::{
27    collections::{HashMap, HashSet},
28    error,
29    fmt::{self},
30    ops::Not,
31};
32
33use log::{debug, error, warn};
34use taco_display_utils::join_iterator;
35use taco_smt_encoder::expression_encoding::{EncodeToSMT, SMTSolverError, SMTVariableContext};
36use taco_threshold_automaton::{
37    ModifiableThresholdAutomaton, VariableConstraint,
38    expressions::{
39        BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
40        fraction::Fraction,
41    },
42    lia_threshold_automaton::{
43        ConstraintRewriteError, LIAVariableConstraint, integer_thresholds::Threshold,
44    },
45};
46
47use crate::{
48    ModelCheckerContext, SpecificationTrait, TargetSpec,
49    eltl::{ELTLExpression, remove_negations::NonNegatedELTLExpression},
50};
51
52/// A named reachability Property
53#[derive(Clone, Debug, PartialEq)]
54pub struct ReachabilityProperty {
55    /// Name of the property
56    name: String,
57    /// Disjunctions over constraints of the form (_) && <>(_)
58    disj: Vec<Reachability>,
59}
60
61impl ReachabilityProperty {
62    /// Try to construct a new reachability property from an ELTLExpression
63    pub fn from_named_eltl<S: ToString>(
64        name: S,
65        expr: ELTLExpression,
66    ) -> Result<Self, ReachabilityTransformationError> {
67        let name = name.to_string();
68        let disj = Reachability::try_from_eltl(name.clone(), expr.not().into())?;
69
70        Ok(Self { name, disj })
71    }
72
73    /// Check whether this constraint contains a reachability constraint
74    ///
75    /// A reachability constraint requires at least one location to be empty.
76    pub fn contains_reachability_constraint(&self) -> bool {
77        self.disj
78            .iter()
79            .any(|c| c.contains_reachability_constraint())
80    }
81
82    /// Create the threshold automata that need to be checked
83    pub fn create_tas_to_check<T: ModifiableThresholdAutomaton>(
84        &self,
85        ta: &T,
86    ) -> impl Iterator<Item = (DisjunctionTargetConfig, T)> {
87        self.clone().disj.into_iter().map(move |reach| {
88            let mut ta = ta.clone();
89
90            ta.set_name(ta.name().to_string() + "-" + reach.target.name());
91
92            ta.add_initial_location_constraints(reach.precondition_loc);
93            ta.add_initial_variable_constraints(reach.precondition_var);
94            ta.add_resilience_conditions(reach.precondition_par);
95
96            (reach.target, ta)
97        })
98    }
99
100    /// Get the name of the specification
101    pub fn name(&self) -> &str {
102        &self.name
103    }
104
105    /// Create a new property
106    pub fn new<S: Into<String>, T: Into<Reachability>>(name: S, targets: T) -> Self {
107        let spec = targets.into();
108        Self {
109            name: name.into(),
110            disj: vec![spec],
111        }
112    }
113}
114
115impl<C: ModelCheckerContext> SpecificationTrait<C> for ReachabilityProperty {
116    type TransformationError = ReachabilityTransformationError;
117
118    fn try_from_eltl(
119        spec: impl Iterator<Item = (String, crate::eltl::ELTLExpression)>,
120        _ctx: &C,
121    ) -> Result<Vec<Self>, Self::TransformationError> {
122        let mut res_properties = Vec::new();
123
124        for (name, expr) in spec {
125            let res = Self::from_named_eltl(name.clone(), expr.clone());
126            if let Err(ReachabilityTransformationError::LivenessSpecification) = res {
127                warn!(
128                    "Specification '{name}' is a liveness specification, which are currently not supported by TACO. Skipping ..."
129                );
130                debug!("Classified property '{name}' as liveness. Expression: {expr}");
131                continue;
132            }
133
134            let res = res?;
135            res_properties.push(res);
136        }
137
138        Ok(res_properties)
139    }
140
141    type InternalSpecType = DisjunctionTargetConfig;
142
143    fn transform_threshold_automaton<
144        TA: taco_threshold_automaton::ThresholdAutomaton + ModifiableThresholdAutomaton,
145    >(
146        ta: TA,
147        specs: Vec<Self>,
148        _ctx: &C,
149    ) -> Vec<(Self::InternalSpecType, TA)> {
150        specs
151            .into_iter()
152            .flat_map(|spec| spec.create_tas_to_check(&ta).collect::<Vec<_>>())
153            .collect::<Vec<_>>()
154    }
155}
156
157/// Formula of the form `(preconditions) => <>(target)`
158///
159/// A reachability constraint specifies
160/// - a set of starting configurations
161/// - a set of target configurations
162///
163/// The set of starting configurations is a subset of the threshold automaton.
164/// This type allows these restriction to be arbitrary boolean conditions on
165/// parameters, variables and locations (mostly since they appear in benchmarks
166/// of the ByMC tool).
167///
168/// Target constraints are more restricted. We only allow for lower bounds on
169/// the number of processes, or specifying that a location should not be
170/// covered. These constraints match the formal definition of ELTL formulas in
171/// the related literature (e.g., see paper
172/// ["A Short Counterexample Property for Safety and Liveness Verification of Fault-Tolerant Distributed Algorithms"](https://arxiv.org/abs/1608.05327)
173/// by Konnov et al.).
174/// Other constraints, such as upper bounds, can be seen as pathological and
175/// decision procedures may be incomplete for these specifications.
176///
177///
178/// To handle these conditions, add them to the initial constraints of the
179/// threshold automaton, preferably before transforming the automaton in a
180/// necessary intermediate format.
181#[derive(Debug, Clone, PartialEq)]
182pub struct Reachability {
183    /// Conjunction of conditions on the parameter evaluation
184    precondition_par: Vec<BooleanExpression<Parameter>>,
185    /// Conjunction of conditions on the initial distribution of processes in the threshold
186    /// automaton
187    precondition_loc: Vec<BooleanExpression<Location>>,
188    /// Conjunction of conditions on the initial valuation of the shared variables
189    precondition_var: Vec<BooleanExpression<Variable>>,
190    /// Conditions on the configuration to reach
191    target: DisjunctionTargetConfig,
192}
193
194impl And for Reachability {
195    fn and(mut self, other: Reachability) -> Self {
196        self.precondition_par.reserve(other.precondition_par.len());
197        self.precondition_par.extend(other.precondition_par);
198
199        self.precondition_loc.reserve(other.precondition_loc.len());
200        self.precondition_loc.extend(other.precondition_loc);
201
202        self.precondition_var.reserve(other.precondition_var.len());
203        self.precondition_var.extend(other.precondition_var);
204
205        self.target = self.target.and(other.target);
206
207        self
208    }
209}
210
211impl Reachability {
212    /// Create a new reachability constraint that has no constraints
213    fn new_unconstrained(name: String) -> Self {
214        Reachability {
215            precondition_par: vec![],
216            precondition_loc: vec![],
217            precondition_var: vec![],
218            target: DisjunctionTargetConfig::new_empty(name),
219        }
220    }
221
222    /// Create a new reachability constraint with only a single parameter
223    /// constraint
224    fn new_with_parameter_constr(
225        name: String,
226        lhs: Box<IntegerExpression<Parameter>>,
227        op: ComparisonOp,
228        rhs: Box<IntegerExpression<Parameter>>,
229    ) -> Self {
230        Reachability {
231            precondition_par: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
232            precondition_loc: vec![],
233            precondition_var: vec![],
234            target: DisjunctionTargetConfig::new_empty(name),
235        }
236    }
237
238    /// Create a new reachability constraint with only a single initial location
239    /// constraint
240    fn new_with_initial_loc_constr(
241        name: String,
242        lhs: Box<IntegerExpression<Location>>,
243        op: ComparisonOp,
244        rhs: Box<IntegerExpression<Location>>,
245    ) -> Self {
246        Reachability {
247            precondition_par: vec![],
248            precondition_loc: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
249            precondition_var: vec![],
250            target: DisjunctionTargetConfig::new_empty(name),
251        }
252    }
253
254    /// Create a new reachability constraint with only a single initial variable
255    /// constraint
256    fn new_with_initial_var_constr(
257        name: String,
258        lhs: Box<IntegerExpression<Variable>>,
259        op: ComparisonOp,
260        rhs: Box<IntegerExpression<Variable>>,
261    ) -> Self {
262        Reachability {
263            precondition_par: vec![],
264            precondition_loc: vec![],
265            precondition_var: vec![BooleanExpression::ComparisonExpression(lhs, op, rhs)],
266            target: DisjunctionTargetConfig::new_empty(name),
267        }
268    }
269
270    /// Create a new reachability constraint with only an eventual constraint
271    fn new_with_eventual_reach(disj: DisjunctionTargetConfig) -> Self {
272        Reachability {
273            precondition_par: vec![],
274            precondition_loc: vec![],
275            precondition_var: vec![],
276            target: disj,
277        }
278    }
279
280    /// Check whether this constraint contains a reachability constraint
281    ///
282    /// A reachability constraint requires at least one location to be empty.
283    pub fn contains_reachability_constraint(&self) -> bool {
284        self.target.contains_reachability_constraint()
285    }
286
287    /// Get the target specification of the reachability specification
288    ///
289    /// A [`Reachability`] object is a specification of the form
290    /// `(preconditions) => <>(target)`
291    /// This function returns the target as a [`DisjunctionTargetConfig`]
292    pub fn get_target_conditions(&self) -> &DisjunctionTargetConfig {
293        &self.target
294    }
295
296    /// Try to construct a reachability constraint from a an ELTL formula
297    fn try_from_eltl(
298        name: String,
299        spec: NonNegatedELTLExpression,
300    ) -> Result<Vec<Self>, ReachabilityTransformationError> {
301        match spec {
302            NonNegatedELTLExpression::Globally(_expr) => {
303                Err(ReachabilityTransformationError::LivenessSpecification)
304            }
305            NonNegatedELTLExpression::Eventually(expr) => {
306                let inner = Self::parse_in_eventually(name, *expr)?;
307                Ok(vec![Self::new_with_eventual_reach(inner)])
308            }
309            NonNegatedELTLExpression::And(lhs, rhs) => {
310                let lhs = Self::try_from_eltl(name.clone(), *lhs)?;
311                let rhs = Self::try_from_eltl(name, *rhs)?;
312
313                let mut new_constrs = Vec::with_capacity(lhs.len() * rhs.len());
314                for constr in &lhs {
315                    for o_constr in &rhs {
316                        new_constrs.push(constr.clone().and(o_constr.clone()));
317                    }
318                }
319
320                Ok(new_constrs)
321            }
322            NonNegatedELTLExpression::Or(lhs, rhs) => {
323                let mut lhs = Reachability::try_from_eltl(name.clone(), *lhs)?;
324                let mut rhs = Reachability::try_from_eltl(name, *rhs)?;
325
326                lhs.reserve(rhs.len());
327                lhs.append(&mut rhs);
328                Ok(lhs)
329            }
330            NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
331                Ok(vec![Self::new_with_initial_loc_constr(name, lhs, op, rhs)])
332            }
333            NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
334                Ok(vec![Self::new_with_initial_var_constr(name, lhs, op, rhs)])
335            }
336            NonNegatedELTLExpression::ParameterExpr(lhs, op, rhs) => {
337                Ok(vec![Self::new_with_parameter_constr(name, lhs, op, rhs)])
338            }
339            NonNegatedELTLExpression::True => Ok(vec![Self::new_unconstrained(name)]),
340            NonNegatedELTLExpression::False => Ok(vec![]),
341        }
342    }
343
344    /// Try to parse the constraints behind an eventually operator into a
345    /// disjunction of upwards closed sets
346    fn parse_in_eventually(
347        name: String,
348        expr: NonNegatedELTLExpression,
349    ) -> Result<DisjunctionTargetConfig, ReachabilityTransformationError> {
350        match expr {
351            NonNegatedELTLExpression::Eventually(expr) => Self::parse_in_eventually(name, *expr),
352            NonNegatedELTLExpression::Or(lhs, rhs) => {
353                let lhs = Self::parse_in_eventually(name.clone(), *lhs)?;
354                let rhs = Self::parse_in_eventually(name, *rhs)?;
355
356                Ok(lhs.or(rhs))
357            }
358            NonNegatedELTLExpression::And(lhs, rhs) => {
359                let lhs = Self::parse_in_eventually(name.clone(), *lhs)?;
360                let rhs = Self::parse_in_eventually(name, *rhs)?;
361
362                Ok(lhs.and(rhs))
363            }
364            NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
365                let reach = TargetConfig::try_from_loc_constr((*lhs, op, *rhs))?;
366
367                Ok(DisjunctionTargetConfig::new_with_single_disjunct(
368                    name, reach,
369                ))
370            }
371            NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
372                let var = BooleanExpression::ComparisonExpression(lhs, op, rhs);
373                let loc = TargetConfig::new_with_var_constr(var)?;
374
375                Ok(DisjunctionTargetConfig::new_with_single_disjunct(name, loc))
376            }
377            NonNegatedELTLExpression::True => {
378                Ok(DisjunctionTargetConfig::new_with_single_disjunct(
379                    name,
380                    TargetConfig::new_unconstrained(),
381                ))
382            }
383            NonNegatedELTLExpression::False => Ok(DisjunctionTargetConfig::new_empty(name)),
384            NonNegatedELTLExpression::Globally(_expr) => {
385                Err(ReachabilityTransformationError::LivenessSpecification)
386            }
387            NonNegatedELTLExpression::ParameterExpr(_, _, _) => unreachable!(
388                "This case should have been filtered out when validating the specification"
389            ),
390        }
391    }
392}
393
394impl<D: Into<DisjunctionTargetConfig>> From<D> for Reachability {
395    fn from(value: D) -> Self {
396        let disj = value.into();
397        Reachability::new_with_eventual_reach(disj)
398    }
399}
400
401/// Disjunction over multiple target configurations
402///
403/// This type represents a disjunction of [`TargetConfig`]
404#[derive(Debug, Clone, PartialEq)]
405pub struct DisjunctionTargetConfig {
406    /// Name of the property this specification has been derived from
407    property_name: String,
408    /// Sets of target configurations
409    targets: Vec<TargetConfig>,
410}
411
412impl fmt::Display for DisjunctionTargetConfig {
413    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
414        write!(f, "{}", join_iterator(self.targets.iter(), " || "))
415    }
416}
417
418impl Or for DisjunctionTargetConfig {
419    fn or(mut self, other: Self) -> Self {
420        self.targets.reserve(other.targets.len());
421        self.targets.extend(other.targets);
422        self
423    }
424}
425
426impl And for DisjunctionTargetConfig {
427    fn and(self, other: DisjunctionTargetConfig) -> DisjunctionTargetConfig {
428        if self.targets.is_empty() {
429            return other;
430        }
431
432        if other.targets.is_empty() {
433            return self;
434        }
435
436        let mut constrs = Vec::with_capacity(self.targets.len() * other.targets.len());
437
438        for constr in self.targets {
439            for o_constr in other.targets.iter() {
440                constrs.push(constr.clone().and(o_constr.clone()));
441            }
442        }
443
444        debug_assert!(
445            self.property_name == other.property_name,
446            "Combined target configurations from different properties"
447        );
448
449        Self {
450            property_name: self.property_name,
451            targets: constrs,
452        }
453    }
454}
455
456impl TargetSpec for DisjunctionTargetConfig {
457    fn get_locations_in_target(&self) -> impl IntoIterator<Item = &Location> {
458        self.get_locations_appearing()
459    }
460
461    fn get_variable_constraint(
462        &self,
463    ) -> impl IntoIterator<
464        Item = &taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint,
465    > {
466        self.targets.iter().map(|t| t.get_variable_constraint())
467    }
468}
469
470impl DisjunctionTargetConfig {
471    /// Create a new disjunction without any disjuncts
472    fn new_empty(name: String) -> Self {
473        Self {
474            property_name: name,
475            targets: Vec::new(),
476        }
477    }
478
479    /// Create a disjunction only containing a single disjunct
480    fn new_with_single_disjunct(name: String, u: TargetConfig) -> Self {
481        Self {
482            property_name: name,
483            targets: vec![u],
484        }
485    }
486
487    /// Create a disjunction from the given disjuncts
488    pub fn new_from_targets<I: IntoIterator<Item = TargetConfig>>(name: String, disj: I) -> Self {
489        Self {
490            property_name: name,
491            targets: disj.into_iter().collect(),
492        }
493    }
494
495    /// Check whether the disjunction contains a reachability constraint
496    ///
497    /// A reachability constraint requires at least one location to be empty.
498    pub fn contains_reachability_constraint(&self) -> bool {
499        self.targets.iter().any(|c| c.is_reachability_constraint())
500    }
501
502    /// Get the locations appearing in the specifications
503    pub fn get_locations_appearing(&self) -> HashSet<&Location> {
504        self.targets
505            .iter()
506            .map(|t| t.get_locations_appearing())
507            .fold(HashSet::new(), |mut acc, x| {
508                acc.reserve(x.len());
509                acc.extend(x);
510                acc
511            })
512    }
513
514    /// Get the name of the property this target has been derived from
515    pub fn name(&self) -> &str {
516        &self.property_name
517    }
518
519    /// Get the included target configurations
520    pub fn get_target_configs(&self) -> impl Iterator<Item = &TargetConfig> {
521        self.targets.iter()
522    }
523}
524
525impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
526    EncodeToSMT<DisjunctionTargetConfig, C> for DisjunctionTargetConfig
527{
528    fn encode_to_smt_with_ctx(
529        &self,
530        solver: &taco_smt_encoder::SMTSolver,
531        ctx: &C,
532    ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
533        if self.targets.is_empty() {
534            return Ok(solver.false_());
535        }
536
537        let disjuncts = self
538            .targets
539            .iter()
540            .map(|c| c.encode_to_smt_with_ctx(solver, ctx))
541            .collect::<Result<Vec<_>, SMTSolverError>>()?;
542
543        Ok(solver.or_many(disjuncts))
544    }
545}
546
547/// Upward closed set of configurations representing a target set of
548/// configurations
549///
550/// This type specifies an upwards closed set of configurations. It can be seen
551/// as a minimal basis specifying a set of configurations
552#[derive(Debug, Clone, PartialEq)]
553pub struct TargetConfig {
554    /// Map with location + number of processes at least in there
555    /// Set of locations where no process should be
556    lower_bounds: HashMap<Location, u32>,
557    /// Location should not be covered by any processes
558    locations_not_covered: HashSet<Location>,
559    /// conjunct of variable constraints
560    variable_constr: LIAVariableConstraint,
561}
562
563impl And for TargetConfig {
564    fn and(mut self, other: Self) -> Self {
565        self.lower_bounds.reserve(other.lower_bounds.len());
566        self.lower_bounds.extend(other.lower_bounds);
567
568        self.locations_not_covered
569            .reserve(other.locations_not_covered.len());
570        self.locations_not_covered
571            .extend(other.locations_not_covered);
572
573        self.variable_constr = if self.variable_constr == LIAVariableConstraint::True {
574            other.variable_constr
575        } else if other.variable_constr == LIAVariableConstraint::True {
576            self.variable_constr
577        } else {
578            self.variable_constr & other.variable_constr
579        };
580        self
581    }
582}
583
584impl TargetConfig {
585    /// Create a new specification object that contains all possible
586    /// configurations
587    fn new_unconstrained() -> Self {
588        TargetConfig {
589            lower_bounds: HashMap::new(),
590            locations_not_covered: HashSet::new(),
591            variable_constr: LIAVariableConstraint::True,
592        }
593    }
594
595    /// Create a new specification that contains all configurations where `loc`
596    /// is not covered by any processes
597    fn new_loc_not_covered(loc: Location) -> Self {
598        TargetConfig {
599            lower_bounds: HashMap::new(),
600            locations_not_covered: HashSet::from([loc]),
601            variable_constr: LIAVariableConstraint::True,
602        }
603    }
604
605    /// Create a new specification that requires all configurations in `cover`
606    /// to be covered by at least one process
607    pub fn new_cover<L: IntoIterator<Item = Location>>(
608        cover: L,
609    ) -> Result<Self, ReachabilityTransformationError> {
610        let locs = cover
611            .into_iter()
612            .map(|loc| (loc, 1))
613            .collect::<HashMap<_, _>>();
614
615        Self::new_reach_with_var_constr(locs, [], BooleanExpression::True)
616    }
617
618    /// Create a new specification that requires all configurations in `cover`
619    /// to be covered by at the given amount of processes
620    pub fn new_general_cover<L: IntoIterator<Item = (Location, u32)>>(
621        cover: L,
622    ) -> Result<Self, ReachabilityTransformationError> {
623        let locs = cover.into_iter().collect::<HashMap<_, _>>();
624
625        Self::new_reach_with_var_constr(locs, [], BooleanExpression::True)
626    }
627
628    /// Create a new specification that requires all configurations in `cover`
629    /// to be covered by at least one process and all locations in `uncover` to
630    /// be empty
631    pub fn new_reach<LI: IntoIterator<Item = Location>, LII: IntoIterator<Item = Location>>(
632        cover: LI,
633        uncover: LII,
634    ) -> Result<Self, ReachabilityTransformationError> {
635        let cover = cover
636            .into_iter()
637            .map(|loc| (loc, 1))
638            .collect::<HashMap<_, _>>();
639        let uncover = uncover.into_iter().collect::<HashSet<_>>();
640
641        Self::new_reach_with_var_constr(cover, uncover, BooleanExpression::True)
642    }
643
644    /// Create a new specification that requires all configurations in `cover`
645    /// to be covered by at least the specified amount of processes and all
646    /// locations in `uncover` to be empty
647    pub fn new_general_reach<
648        U: IntoIterator<Item = Location>,
649        C: IntoIterator<Item = (Location, u32)>,
650    >(
651        cover: C,
652        uncover: U,
653    ) -> Result<Self, ReachabilityTransformationError> {
654        let cover = cover.into_iter().collect::<HashMap<_, _>>();
655        let uncover = uncover.into_iter().collect::<HashSet<_>>();
656
657        Self::new_reach_with_var_constr(cover, uncover, BooleanExpression::True)
658    }
659
660    /// Get all locations that appear in the target specification
661    pub fn get_locations_appearing(&self) -> HashSet<&Location> {
662        self.lower_bounds
663            .keys()
664            .chain(self.locations_not_covered.iter())
665            .collect()
666    }
667
668    /// Get the variable constraint appearing in the target configuration
669    pub fn get_variable_constraint(&self) -> &LIAVariableConstraint {
670        &self.variable_constr
671    }
672
673    /// Create a new target configuration constraint
674    pub fn new_reach_with_var_constr<
675        LC: Into<HashMap<Location, u32>>,
676        LU: Into<HashSet<Location>>,
677    >(
678        locs: LC,
679        uncover: LU,
680        var_constr: BooleanExpression<Variable>,
681    ) -> Result<Self, ReachabilityTransformationError> {
682        let lia_constr = (&var_constr).try_into().map_err(|err| {
683            ReachabilityTransformationError::VariableConstraintNotLinearArithmetic(err, var_constr)
684        })?;
685
686        Ok(TargetConfig {
687            lower_bounds: locs.into(),
688            locations_not_covered: uncover.into(),
689            variable_constr: lia_constr,
690        })
691    }
692
693    /// Create a new specification contains all configurations where
694    /// `var_constr` holds
695    fn new_with_var_constr(
696        var_constr: BooleanExpression<Variable>,
697    ) -> Result<Self, ReachabilityTransformationError> {
698        let lia_constr = (&var_constr).try_into().map_err(|err| {
699            ReachabilityTransformationError::VariableConstraintNotLinearArithmetic(err, var_constr)
700        })?;
701
702        Ok(TargetConfig {
703            lower_bounds: HashMap::new(),
704            locations_not_covered: HashSet::new(),
705            variable_constr: lia_constr,
706        })
707    }
708
709    /// Check whether this constraint is a reachability constraint
710    ///
711    /// A reachability constraint requires at least one location to be empty.
712    fn is_reachability_constraint(&self) -> bool {
713        !self.locations_not_covered.is_empty()
714    }
715
716    /// Get iterator over the locations that should be covered by processes with
717    /// the least amount of processes
718    pub fn get_locations_to_cover(&self) -> impl Iterator<Item = (&Location, &u32)> {
719        self.lower_bounds.iter()
720    }
721
722    /// Locations that should not be covered
723    pub fn get_locations_to_uncover(&self) -> impl Iterator<Item = &Location> {
724        self.locations_not_covered.iter()
725    }
726
727    /// Create a [`DisjunctionTargetConfig`] with the given name from the single
728    /// target location
729    pub fn into_disjunct_with_name<S: ToString>(self, name: S) -> DisjunctionTargetConfig {
730        DisjunctionTargetConfig::new_with_single_disjunct(name.to_string(), self)
731    }
732
733    /// Attempt to parse an [`DisjunctionTargetConfig`] from a location
734    /// constraint
735    ///
736    /// This function can be used to attempt to extract an upwards closed set of
737    /// configurations from a single boolean constraint over location variables
738    ///
739    /// This function returns an error if the specification does not correspond
740    /// to an upwards closed set of configurations. See
741    fn try_from_loc_constr(
742        mut constr: LocConstraint,
743    ) -> Result<Self, ReachabilityTransformationError> {
744        let single_loc = Threshold::from_integer_comp_expr(constr.0.clone(), constr.2.clone());
745
746        // Handle errors that might occur when attempting to convert into a
747        // linear arithmetic
748        if let Err(err) = single_loc {
749            let err =
750                UpwardsClosedSetExtractionError::parse_from_constraint_rewrite_err(err, constr);
751            return Err(
752                ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
753            );
754        }
755        let (locs, mut thr) = single_loc.unwrap();
756
757        if locs.len() != 1 {
758            let err = UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(
759                Box::new(constr),
760            );
761
762            return Err(
763                ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
764            );
765        }
766
767        // Thresholds including parameters unsupported in reachable constr
768        if !thr.is_constant() {
769            let err =
770                UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(constr));
771
772            return Err(
773                ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err),
774            );
775        }
776
777        let (loc, scale) = locs.into_iter().next().unwrap();
778
779        // TODO: refactor this, the logic is actually already in threshold constraint
780        if scale.is_negative() {
781            thr.scale(-Fraction::from(1));
782            constr.1 = constr.1.get_swap_side();
783        }
784
785        let c = thr.get_const().unwrap();
786
787        match constr.1 {
788            ComparisonOp::Gt | ComparisonOp::Geq => {
789                // unconstrained
790                if c.is_negative() {
791                    return Ok(Self::new_unconstrained());
792                }
793
794                let c = if let Ok(c_i64) = i64::try_from(c) {
795                    // conversion should be safe now, but check u32 bounds
796                    let mut c = match u32::try_from(c_i64) {
797                        Ok(val) => val,
798                        Err(_) => {
799                            // Value out of bounds for u32, notify the user or handle error
800                            error!(
801                                "Threshold constant {} is out of bounds for u32 (must be between 0 and {}). Truncating to u32::MAX.",
802                                c_i64,
803                                u32::MAX
804                            );
805                            u32::MAX
806                        }
807                    };
808                    // in case of `>` need to add 1
809                    if constr.1 == ComparisonOp::Gt {
810                        if c < u32::MAX {
811                            c += 1
812                        } else {
813                            error!(
814                                "Fraction value for lower bound is out of range: {}. Returning unconstrained specification.",
815                                constr.1
816                            );
817                            return Ok(Self::new_unconstrained());
818                        }
819                    }
820
821                    c
822                } else {
823                    // TODO: not quite clear whether this is what we want
824                    // It might be safer to notify the user
825                    let num = c.numerator() as f64;
826                    let den = c.denominator() as f64;
827
828                    // take the ceil
829                    let lower_bound = (num / den).ceil();
830                    lower_bound as u32
831                };
832
833                return Self::new_reach_with_var_constr([(loc, c)], [], BooleanExpression::True);
834            }
835            ComparisonOp::Eq | ComparisonOp::Leq => {
836                if c == Fraction::from(0) {
837                    return Ok(Self::new_loc_not_covered(loc));
838                }
839            }
840            ComparisonOp::Neq => {
841                if c == Fraction::from(0) {
842                    return Self::new_cover([loc]);
843                }
844            }
845            ComparisonOp::Lt => {
846                if c == Fraction::from(1) {
847                    return Ok(Self::new_loc_not_covered(loc));
848                }
849            }
850        }
851
852        let err =
853            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(constr));
854        Err(ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(err))
855    }
856}
857
858impl<C: SMTVariableContext<Location> + SMTVariableContext<Variable> + SMTVariableContext<Parameter>>
859    EncodeToSMT<TargetConfig, C> for TargetConfig
860{
861    fn encode_to_smt_with_ctx(
862        &self,
863        solver: &taco_smt_encoder::SMTSolver,
864        ctx: &C,
865    ) -> Result<taco_smt_encoder::SMTExpr, SMTSolverError> {
866        // lower bounds
867        let lower_bounds = self
868            .lower_bounds
869            .iter()
870            .map(|(loc, n)| {
871                let loc = ctx.get_expr_for(loc)?;
872                let n = solver.numeral(*n);
873
874                Ok(solver.gte(loc, n))
875            })
876            .collect::<Result<Vec<_>, SMTSolverError>>()?;
877
878        // locations that should not be covered
879        let loc_uncover = self
880            .locations_not_covered
881            .iter()
882            .map(|loc| {
883                let loc = ctx.get_expr_for(loc)?;
884                let zero = solver.numeral(0);
885
886                Ok(solver.eq(loc, zero))
887            })
888            .collect::<Result<Vec<_>, SMTSolverError>>()?;
889
890        // constraint on variable evaluation
891        let var_constr = self
892            .variable_constr
893            .as_boolean_expr()
894            .encode_to_smt_with_ctx(solver, ctx)?;
895
896        let res = solver.and_many(
897            lower_bounds
898                .into_iter()
899                .chain(loc_uncover)
900                .chain([var_constr]),
901        );
902
903        Ok(res)
904    }
905}
906
907impl fmt::Display for TargetConfig {
908    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
909        let lower_bounds = join_iterator(
910            self.lower_bounds
911                .iter()
912                .map(|(loc, n)| format!("({loc} >= {n})")),
913            " && ",
914        );
915
916        let not_covered = join_iterator(
917            self.locations_not_covered
918                .iter()
919                .map(|loc| format!("({loc} == 0)")),
920            " && ",
921        );
922
923        write!(
924            f,
925            "{lower_bounds} && {not_covered} && {}",
926            self.variable_constr
927        )
928    }
929}
930
931/// Errors that can occur when attempting to extract an upwards closed set of
932/// configurations
933#[derive(Debug, Clone, PartialEq)]
934pub enum UpwardsClosedSetExtractionError {
935    /// Location constraint references multiple locations
936    ///
937    /// The set of such a constraint is not necessarily upwards closed
938    LocationConstraintOverMultipleLocs(Box<LocConstraint>),
939    /// Constraint for example contains multiplications between locations
940    ///
941    /// The set of configurations satisfying such a constraint is not necessarily
942    /// satisfied
943    LocationConstraintNonLinear(Box<LocConstraint>),
944    /// Locations constraint references single location but is not necessarily
945    /// upwards closed
946    LocationConstraintNotUpwardsClosed(Box<LocConstraint>),
947    /// Comparison with parameters does not correspond to an upwards closed set
948    /// of locations
949    LocationConstraintWithParameters(Box<LocConstraint>),
950}
951
952impl UpwardsClosedSetExtractionError {
953    /// Convert errors from a [`ConstraintRewriteError`] to an
954    /// [`UpwardsClosedSetExtractionError`]
955    pub fn parse_from_constraint_rewrite_err(
956        err: ConstraintRewriteError,
957        constr: LocConstraint,
958    ) -> Self {
959        match err {
960            ConstraintRewriteError::NotLinearArithmetic => {
961                UpwardsClosedSetExtractionError::LocationConstraintNonLinear(Box::new(constr))
962            }
963            ConstraintRewriteError::ParameterConstraint(_) => {
964                UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(constr))
965            }
966        }
967    }
968}
969
970impl fmt::Display for UpwardsClosedSetExtractionError {
971    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
972        match self {
973            UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(constr) => {
974                write!(
975                    f,
976                    "Location constraints referencing multiple locations unsupported in clauses behind temporal operators. Clause '{} {} {}' references more than one location",
977                    constr.0, constr.1, constr.2
978                )
979            }
980            UpwardsClosedSetExtractionError::LocationConstraintNonLinear(constr) => write!(
981                f,
982                "Location constraints containing non-linear arithmetic unsupported. Clause '{} {} {}' is not a linear arithmetic expression",
983                constr.0, constr.1, constr.2
984            ),
985            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(constr) => {
986                write!(
987                    f,
988                    "Locations constraint does not have an upwards closed set of error states. Such specifications are not supported. Clause '{} {} {}'",
989                    constr.0, constr.1, constr.2
990                )
991            }
992            UpwardsClosedSetExtractionError::LocationConstraintWithParameters(constr) => {
993                write!(
994                    f,
995                    "Locations constraint with parameters not supported behind temporal operator. Clause '{} {} {}'",
996                    constr.0, constr.1, constr.2
997                )
998            }
999        }
1000    }
1001}
1002
1003impl error::Error for UpwardsClosedSetExtractionError {}
1004
1005/// Error that can occur during a transformation into a reachability specification
1006#[derive(Clone, Debug, PartialEq)]
1007pub enum ReachabilityTransformationError {
1008    /// Specification is a liveness property
1009    LivenessSpecification,
1010    /// Specification contains no upwards closed target
1011    ReachableConfigurationsNotUpwardsClosed(UpwardsClosedSetExtractionError),
1012    /// Variable constraint not linear integer arithmetic
1013    VariableConstraintNotLinearArithmetic(ConstraintRewriteError, BooleanExpression<Variable>),
1014}
1015
1016impl fmt::Display for ReachabilityTransformationError {
1017    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
1018        match self {
1019            Self::LivenessSpecification => write!(
1020                f,
1021                "Property contains liveness specification that are not yet supported"
1022            ),
1023            Self::ReachableConfigurationsNotUpwardsClosed(err) => {
1024                write!(
1025                    f,
1026                    "Specification extraction failed because the set of error configurations is not upwards closed. Detailed error: {err}"
1027                )
1028            }
1029            Self::VariableConstraintNotLinearArithmetic(rewrite_err, bexpr) => write!(
1030                f,
1031                "Specification extraction failed because the variable constraint '{bexpr}'. Detailed error: {rewrite_err}"
1032            ),
1033        }
1034    }
1035}
1036
1037impl error::Error for ReachabilityTransformationError {}
1038
1039impl From<UpwardsClosedSetExtractionError> for ReachabilityTransformationError {
1040    fn from(value: UpwardsClosedSetExtractionError) -> Self {
1041        ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(value)
1042    }
1043}
1044
1045/// Alias for unconverted constraints over locations simplifying the definitions
1046/// in the remainder of the file
1047///
1048/// This type corresponds to an expression of the form
1049/// `BooleanExpression<Location>::ComparisonExpression(Box:.new(0), 1, Box::new(2))`
1050type LocConstraint = (
1051    IntegerExpression<Location>,
1052    ComparisonOp,
1053    IntegerExpression<Location>,
1054);
1055
1056/// Trait for anding two constraints
1057trait And {
1058    /// Get the conjunction of `self` and `other`
1059    fn and(self, other: Self) -> Self;
1060}
1061
1062/// Trait for oring two constraints
1063trait Or {
1064    /// Get the disjunction of `self` and `other`
1065    fn or(self, other: Self) -> Self;
1066}
1067
1068#[cfg(test)]
1069mod test {
1070    use std::collections::{HashMap, HashSet};
1071
1072    use taco_smt_encoder::{
1073        SMTSolverBuilder, SMTSolverContext,
1074        expression_encoding::{EncodeToSMT, SMTVariableContext, StaticSMTContext},
1075    };
1076    use taco_threshold_automaton::{
1077        BooleanVarConstraint,
1078        expressions::{
1079            BooleanExpression, ComparisonOp, IntegerExpression, IntegerOp, Location, Parameter,
1080            Variable,
1081        },
1082        general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder,
1083        lia_threshold_automaton::LIAVariableConstraint,
1084    };
1085
1086    use crate::{
1087        TargetSpec,
1088        eltl::ELTLExpression,
1089        reachability_specification::{
1090            DisjunctionTargetConfig, Reachability, ReachabilityProperty,
1091            ReachabilityTransformationError, TargetConfig, UpwardsClosedSetExtractionError,
1092        },
1093    };
1094
1095    #[test]
1096    fn test_target_config_new_cover() {
1097        let cover = TargetConfig::new_cover([
1098            Location::new("l1"),
1099            Location::new("l2"),
1100            Location::new("l3"),
1101        ])
1102        .unwrap();
1103
1104        let expected = TargetConfig {
1105            lower_bounds: HashMap::from([
1106                (Location::new("l1"), 1),
1107                (Location::new("l2"), 1),
1108                (Location::new("l3"), 1),
1109            ]),
1110            locations_not_covered: HashSet::new(),
1111            variable_constr: LIAVariableConstraint::True,
1112        };
1113
1114        assert_eq!(cover, expected);
1115    }
1116
1117    #[test]
1118    fn test_reach_property_new() {
1119        let cover = TargetConfig::new_cover([
1120            Location::new("l1"),
1121            Location::new("l2"),
1122            Location::new("l3"),
1123        ])
1124        .unwrap()
1125        .into_disjunct_with_name("test");
1126
1127        let reach_prop = ReachabilityProperty::new("test", cover);
1128
1129        let expected = ReachabilityProperty {
1130            name: "test".into(),
1131            disj: vec![Reachability {
1132                precondition_par: Vec::new(),
1133                precondition_loc: Vec::new(),
1134                precondition_var: Vec::new(),
1135                target: DisjunctionTargetConfig {
1136                    property_name: "test".into(),
1137                    targets: vec![TargetConfig {
1138                        lower_bounds: HashMap::from([
1139                            (Location::new("l1"), 1),
1140                            (Location::new("l2"), 1),
1141                            (Location::new("l3"), 1),
1142                        ]),
1143                        locations_not_covered: HashSet::new(),
1144                        variable_constr: LIAVariableConstraint::True,
1145                    }],
1146                },
1147            }],
1148        };
1149
1150        assert_eq!(reach_prop, expected);
1151    }
1152
1153    #[test]
1154    fn test_get_target_condition() {
1155        let cover = TargetConfig::new_cover([
1156            Location::new("l1"),
1157            Location::new("l2"),
1158            Location::new("l3"),
1159        ])
1160        .unwrap()
1161        .into_disjunct_with_name("test");
1162
1163        let reach: Reachability = cover.clone().into();
1164        assert_eq!(reach.get_target_conditions(), &cover);
1165    }
1166
1167    #[test]
1168    fn test_target_config_new_reach() {
1169        let reach = TargetConfig::new_reach(
1170            [
1171                Location::new("l1"),
1172                Location::new("l2"),
1173                Location::new("l3"),
1174            ],
1175            [
1176                Location::new("l4"),
1177                Location::new("l5"),
1178                Location::new("l6"),
1179            ],
1180        )
1181        .unwrap();
1182
1183        let expected = TargetConfig {
1184            lower_bounds: HashMap::from([
1185                (Location::new("l1"), 1),
1186                (Location::new("l2"), 1),
1187                (Location::new("l3"), 1),
1188            ]),
1189            locations_not_covered: HashSet::from([
1190                Location::new("l4"),
1191                Location::new("l5"),
1192                Location::new("l6"),
1193            ]),
1194            variable_constr: LIAVariableConstraint::True,
1195        };
1196
1197        assert_eq!(reach, expected);
1198    }
1199
1200    #[test]
1201    fn test_target_config_new_reach_with_var_constr() {
1202        let reach = TargetConfig::new_reach_with_var_constr(
1203            [
1204                (Location::new("l1"), 1),
1205                (Location::new("l2"), 2),
1206                (Location::new("l3"), 3),
1207            ],
1208            [
1209                Location::new("l4"),
1210                Location::new("l5"),
1211                Location::new("l6"),
1212            ],
1213            BooleanVarConstraint::ComparisonExpression(
1214                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1215                ComparisonOp::Eq,
1216                Box::new(IntegerExpression::Const(1)),
1217            ),
1218        )
1219        .unwrap();
1220
1221        let expected = TargetConfig {
1222            lower_bounds: HashMap::from([
1223                (Location::new("l1"), 1),
1224                (Location::new("l2"), 2),
1225                (Location::new("l3"), 3),
1226            ]),
1227            locations_not_covered: HashSet::from([
1228                Location::new("l4"),
1229                Location::new("l5"),
1230                Location::new("l6"),
1231            ]),
1232            variable_constr: (&BooleanVarConstraint::ComparisonExpression(
1233                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1234                ComparisonOp::Eq,
1235                Box::new(IntegerExpression::Const(1)),
1236            ))
1237                .try_into()
1238                .unwrap(),
1239        };
1240
1241        assert_eq!(reach, expected);
1242    }
1243
1244    // This test tests a private method but this method is crucial for correctness
1245    #[test]
1246    fn test_target_config_try_loc_constr_translatable_cases() {
1247        // l == 0
1248        let test_expr = (
1249            IntegerExpression::Atom(Location::new("l")),
1250            ComparisonOp::Eq,
1251            IntegerExpression::Const(0),
1252        );
1253
1254        let got = TargetConfig::try_from_loc_constr(test_expr);
1255        assert!(got.is_ok());
1256
1257        let expected = TargetConfig {
1258            lower_bounds: HashMap::new(),
1259            locations_not_covered: HashSet::from([Location::new("l")]),
1260            variable_constr: LIAVariableConstraint::True,
1261        };
1262
1263        assert_eq!(got.unwrap(), expected);
1264
1265        // l != 0
1266        let test_expr = (
1267            IntegerExpression::Atom(Location::new("l")),
1268            ComparisonOp::Neq,
1269            IntegerExpression::Const(0),
1270        );
1271
1272        let got = TargetConfig::try_from_loc_constr(test_expr);
1273        assert!(got.is_ok());
1274
1275        let expected = TargetConfig {
1276            lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1277            locations_not_covered: HashSet::new(),
1278            variable_constr: LIAVariableConstraint::True,
1279        };
1280
1281        assert_eq!(got.unwrap(), expected);
1282
1283        // l > 0
1284        let test_expr = (
1285            IntegerExpression::Atom(Location::new("l")),
1286            ComparisonOp::Gt,
1287            IntegerExpression::Const(0),
1288        );
1289
1290        let got = TargetConfig::try_from_loc_constr(test_expr);
1291        assert!(got.is_ok());
1292
1293        let expected = TargetConfig {
1294            lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1295            locations_not_covered: HashSet::new(),
1296            variable_constr: LIAVariableConstraint::True,
1297        };
1298
1299        assert_eq!(got.unwrap(), expected);
1300
1301        // l > 42
1302        let test_expr = (
1303            IntegerExpression::Atom(Location::new("l")),
1304            ComparisonOp::Gt,
1305            IntegerExpression::Const(42),
1306        );
1307
1308        let got = TargetConfig::try_from_loc_constr(test_expr);
1309        assert!(got.is_ok());
1310
1311        let expected = TargetConfig {
1312            lower_bounds: HashMap::from([(Location::new("l"), 43)]),
1313            locations_not_covered: HashSet::new(),
1314            variable_constr: LIAVariableConstraint::True,
1315        };
1316
1317        assert_eq!(got.unwrap(), expected);
1318
1319        // l >= 42
1320        let test_expr = (
1321            IntegerExpression::Atom(Location::new("l")),
1322            ComparisonOp::Geq,
1323            IntegerExpression::Const(42),
1324        );
1325
1326        let got = TargetConfig::try_from_loc_constr(test_expr);
1327        assert!(got.is_ok());
1328
1329        let expected = TargetConfig {
1330            lower_bounds: HashMap::from([(Location::new("l"), 42)]),
1331            locations_not_covered: HashSet::new(),
1332            variable_constr: LIAVariableConstraint::True,
1333        };
1334
1335        assert_eq!(got.unwrap(), expected);
1336
1337        // l > 2/3
1338        let test_expr = (
1339            IntegerExpression::Atom(Location::new("l")),
1340            ComparisonOp::Gt,
1341            IntegerExpression::BinaryExpr(
1342                Box::new(IntegerExpression::Const(2)),
1343                IntegerOp::Div,
1344                Box::new(IntegerExpression::Const(3)),
1345            ),
1346        );
1347
1348        let got = TargetConfig::try_from_loc_constr(test_expr);
1349        assert!(got.is_ok());
1350
1351        let expected = TargetConfig {
1352            lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1353            locations_not_covered: HashSet::new(),
1354            variable_constr: LIAVariableConstraint::True,
1355        };
1356
1357        assert_eq!(got.unwrap(), expected);
1358
1359        // l >= 2/3
1360        let test_expr = (
1361            IntegerExpression::Atom(Location::new("l")),
1362            ComparisonOp::Gt,
1363            IntegerExpression::BinaryExpr(
1364                Box::new(IntegerExpression::Const(2)),
1365                IntegerOp::Div,
1366                Box::new(IntegerExpression::Const(3)),
1367            ),
1368        );
1369
1370        let got = TargetConfig::try_from_loc_constr(test_expr);
1371        assert!(got.is_ok());
1372
1373        let expected = TargetConfig {
1374            lower_bounds: HashMap::from([(Location::new("l"), 1)]),
1375            locations_not_covered: HashSet::new(),
1376            variable_constr: LIAVariableConstraint::True,
1377        };
1378
1379        assert_eq!(got.unwrap(), expected);
1380
1381        //  42 < l --> l > 42
1382        let test_expr = (
1383            IntegerExpression::Const(42),
1384            ComparisonOp::Lt,
1385            IntegerExpression::Atom(Location::new("l")),
1386        );
1387
1388        let got = TargetConfig::try_from_loc_constr(test_expr);
1389        assert!(got.is_ok(), "{:#?}", got);
1390
1391        let expected = TargetConfig {
1392            lower_bounds: HashMap::from([(Location::new("l"), 43)]),
1393            locations_not_covered: HashSet::new(),
1394            variable_constr: LIAVariableConstraint::True,
1395        };
1396
1397        assert_eq!(got.unwrap(), expected);
1398
1399        // l > 0 + 1 + 2
1400        let test_expr = (
1401            IntegerExpression::Atom(Location::new("l")),
1402            ComparisonOp::Gt,
1403            IntegerExpression::BinaryExpr(
1404                Box::new(IntegerExpression::Const(0)),
1405                IntegerOp::Add,
1406                Box::new(IntegerExpression::BinaryExpr(
1407                    Box::new(IntegerExpression::Const(1)),
1408                    IntegerOp::Add,
1409                    Box::new(IntegerExpression::Const(2)),
1410                )),
1411            ),
1412        );
1413
1414        let got = TargetConfig::try_from_loc_constr(test_expr);
1415        assert!(got.is_ok());
1416
1417        let expected = TargetConfig {
1418            lower_bounds: HashMap::from([(Location::new("l"), 4)]),
1419            locations_not_covered: HashSet::new(),
1420            variable_constr: LIAVariableConstraint::True,
1421        };
1422
1423        assert_eq!(got.unwrap(), expected);
1424
1425        // l < 1
1426        let test_expr = (
1427            IntegerExpression::Atom(Location::new("l")),
1428            ComparisonOp::Lt,
1429            IntegerExpression::Const(1),
1430        );
1431
1432        let got = TargetConfig::try_from_loc_constr(test_expr);
1433        assert!(got.is_ok());
1434
1435        let expected = TargetConfig {
1436            lower_bounds: HashMap::new(),
1437            locations_not_covered: HashSet::from([Location::new("l")]),
1438            variable_constr: LIAVariableConstraint::True,
1439        };
1440
1441        assert_eq!(got.unwrap(), expected);
1442
1443        // l <= 0
1444        let test_expr = (
1445            IntegerExpression::Atom(Location::new("l")),
1446            ComparisonOp::Leq,
1447            IntegerExpression::Const(0),
1448        );
1449
1450        let got = TargetConfig::try_from_loc_constr(test_expr);
1451        assert!(got.is_ok());
1452
1453        let expected = TargetConfig {
1454            lower_bounds: HashMap::new(),
1455            locations_not_covered: HashSet::from([Location::new("l")]),
1456            variable_constr: LIAVariableConstraint::True,
1457        };
1458
1459        assert_eq!(got.unwrap(), expected);
1460    }
1461
1462    // This test tests a private method but this method is crucial for correctness
1463    #[test]
1464    fn test_target_config_try_loc_constr_error_cases() {
1465        // l1 + l2 == 0
1466        let test_expr = (
1467            IntegerExpression::BinaryExpr(
1468                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1469                IntegerOp::Add,
1470                Box::new(IntegerExpression::Atom(Location::new("l2"))),
1471            ),
1472            ComparisonOp::Eq,
1473            IntegerExpression::Const(0),
1474        );
1475
1476        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1477        assert!(got.is_err(), "{:#?}", got);
1478
1479        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1480            UpwardsClosedSetExtractionError::LocationConstraintOverMultipleLocs(Box::new(
1481                test_expr,
1482            )),
1483        );
1484        assert_eq!(got.unwrap_err(), expected);
1485
1486        // l * l == 0
1487        let test_expr = (
1488            IntegerExpression::BinaryExpr(
1489                Box::new(IntegerExpression::Atom(Location::new("l"))),
1490                IntegerOp::Mul,
1491                Box::new(IntegerExpression::Atom(Location::new("l"))),
1492            ),
1493            ComparisonOp::Eq,
1494            IntegerExpression::Const(0),
1495        );
1496
1497        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1498        assert!(got.is_err());
1499
1500        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1501            UpwardsClosedSetExtractionError::LocationConstraintNonLinear(Box::new(test_expr)),
1502        );
1503        assert_eq!(got.unwrap_err(), expected);
1504
1505        // l + n == 0
1506        let test_expr = (
1507            IntegerExpression::BinaryExpr(
1508                Box::new(IntegerExpression::Atom(Location::new("l"))),
1509                IntegerOp::Add,
1510                Box::new(IntegerExpression::Param(Parameter::new("n"))),
1511            ),
1512            ComparisonOp::Eq,
1513            IntegerExpression::Const(0),
1514        );
1515
1516        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1517        assert!(got.is_err());
1518
1519        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1520            UpwardsClosedSetExtractionError::LocationConstraintWithParameters(Box::new(test_expr)),
1521        );
1522        assert_eq!(got.unwrap_err(), expected);
1523
1524        // l < 3
1525        let test_expr = (
1526            IntegerExpression::Atom(Location::new("l")),
1527            ComparisonOp::Lt,
1528            IntegerExpression::Const(3),
1529        );
1530
1531        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1532        assert!(got.is_err());
1533
1534        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1535            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1536                test_expr,
1537            )),
1538        );
1539        assert_eq!(got.unwrap_err(), expected);
1540
1541        // l <= 3
1542        let test_expr = (
1543            IntegerExpression::Atom(Location::new("l")),
1544            ComparisonOp::Leq,
1545            IntegerExpression::Const(3),
1546        );
1547
1548        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1549        assert!(got.is_err());
1550
1551        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1552            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1553                test_expr,
1554            )),
1555        );
1556        assert_eq!(got.unwrap_err(), expected);
1557
1558        // l == 3
1559        let test_expr = (
1560            IntegerExpression::Atom(Location::new("l")),
1561            ComparisonOp::Eq,
1562            IntegerExpression::Const(3),
1563        );
1564
1565        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1566        assert!(got.is_err());
1567
1568        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1569            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1570                test_expr,
1571            )),
1572        );
1573        assert_eq!(got.unwrap_err(), expected);
1574
1575        // l != 3
1576        let test_expr = (
1577            IntegerExpression::Atom(Location::new("l")),
1578            ComparisonOp::Neq,
1579            IntegerExpression::Const(3),
1580        );
1581
1582        let got = TargetConfig::try_from_loc_constr(test_expr.clone());
1583        assert!(got.is_err());
1584
1585        let expected = ReachabilityTransformationError::ReachableConfigurationsNotUpwardsClosed(
1586            UpwardsClosedSetExtractionError::LocationConstraintNotUpwardsClosed(Box::new(
1587                test_expr,
1588            )),
1589        );
1590        assert_eq!(got.unwrap_err(), expected);
1591    }
1592
1593    #[test]
1594    fn test_target_config_encode_target_config_unconstrained() {
1595        let solver_builder = SMTSolverBuilder::default();
1596
1597        let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1598
1599        let config = TargetConfig::new_unconstrained();
1600
1601        let got_expr = config
1602            .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1603            .unwrap();
1604
1605        let expected_expr = ctx.get_true();
1606        assert_eq!(got_expr, expected_expr)
1607    }
1608
1609    #[test]
1610    fn test_target_config_encode_target_config_single_loc_cov() {
1611        let solver_builder = SMTSolverBuilder::default();
1612
1613        let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1614
1615        let config = TargetConfig::new_cover([Location::new("l")]).unwrap();
1616
1617        let got_expr = config
1618            .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1619            .unwrap();
1620
1621        let solver = ctx.get_smt_solver();
1622
1623        let expected_expr = solver.and(
1624            solver.gte(
1625                ctx.get_expr_for(&Location::new("l")).unwrap(),
1626                solver.numeral(1),
1627            ),
1628            solver.true_(),
1629        );
1630
1631        assert_eq!(
1632            got_expr,
1633            expected_expr,
1634            "got: {}, expected: {}",
1635            solver.display(got_expr),
1636            solver.display(expected_expr)
1637        )
1638    }
1639
1640    #[test]
1641    fn test_target_config_encode_target_config_single_loc_uncover() {
1642        let solver_builder = SMTSolverBuilder::default();
1643
1644        let ctx = StaticSMTContext::new(solver_builder, [], [Location::new("l")], []).unwrap();
1645
1646        let config = TargetConfig::new_loc_not_covered(Location::new("l"));
1647
1648        let got_expr = config
1649            .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1650            .unwrap();
1651
1652        let solver = ctx.get_smt_solver();
1653
1654        let expected_expr = solver.and(
1655            solver.eq(
1656                ctx.get_expr_for(&Location::new("l")).unwrap(),
1657                solver.numeral(0),
1658            ),
1659            solver.true_(),
1660        );
1661
1662        assert_eq!(
1663            got_expr,
1664            expected_expr,
1665            "got: {}, expected: {}",
1666            solver.display(got_expr),
1667            solver.display(expected_expr)
1668        )
1669    }
1670
1671    #[test]
1672    fn test_target_config_encode_target_config_var_constr() {
1673        let solver_builder = SMTSolverBuilder::default();
1674
1675        let ctx = StaticSMTContext::new(
1676            solver_builder,
1677            [],
1678            [Location::new("l")],
1679            [Variable::new("v")],
1680        )
1681        .unwrap();
1682
1683        let config = TargetConfig::new_with_var_constr(BooleanVarConstraint::ComparisonExpression(
1684            Box::new(IntegerExpression::Atom(Variable::new("v"))),
1685            ComparisonOp::Eq,
1686            Box::new(IntegerExpression::Const(1)),
1687        ))
1688        .unwrap();
1689
1690        let got_expr = config
1691            .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1692            .unwrap();
1693
1694        let solver = ctx.get_smt_solver();
1695
1696        let expected_expr = solver.and(
1697            solver.lt(
1698                ctx.get_expr_for(&Variable::new("v")).unwrap(),
1699                solver.numeral(2),
1700            ),
1701            solver.gte(
1702                ctx.get_expr_for(&Variable::new("v")).unwrap(),
1703                solver.numeral(1),
1704            ),
1705        );
1706
1707        assert_eq!(
1708            got_expr,
1709            expected_expr,
1710            "got: {}, expected: {}",
1711            solver.display(got_expr),
1712            solver.display(expected_expr)
1713        )
1714    }
1715
1716    #[test]
1717    fn test_target_config_encode_target_config_all_constr() {
1718        let solver_builder = SMTSolverBuilder::default();
1719
1720        let ctx = StaticSMTContext::new(
1721            solver_builder,
1722            [],
1723            [Location::new("l1"), Location::new("l2")],
1724            [Variable::new("v")],
1725        )
1726        .unwrap();
1727
1728        let config = TargetConfig::new_reach_with_var_constr(
1729            [(Location::new("l1"), 42)],
1730            [Location::new("l2")],
1731            BooleanVarConstraint::ComparisonExpression(
1732                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1733                ComparisonOp::Eq,
1734                Box::new(IntegerExpression::Const(1)),
1735            ),
1736        )
1737        .unwrap();
1738        let got_expr = config
1739            .encode_to_smt_with_ctx(ctx.get_smt_solver(), &ctx)
1740            .unwrap();
1741
1742        let solver = ctx.get_smt_solver();
1743
1744        let expected_expr = solver.and_many([
1745            solver.gte(
1746                ctx.get_expr_for(&Location::new("l1")).unwrap(),
1747                solver.numeral(42),
1748            ),
1749            solver.eq(
1750                ctx.get_expr_for(&Location::new("l2")).unwrap(),
1751                solver.numeral(0),
1752            ),
1753            solver.and(
1754                solver.lt(
1755                    ctx.get_expr_for(&Variable::new("v")).unwrap(),
1756                    solver.numeral(2),
1757                ),
1758                solver.gte(
1759                    ctx.get_expr_for(&Variable::new("v")).unwrap(),
1760                    solver.numeral(1),
1761                ),
1762            ),
1763        ]);
1764
1765        assert_eq!(
1766            got_expr,
1767            expected_expr,
1768            "got: {}, expected: {}",
1769            solver.display(got_expr),
1770            solver.display(expected_expr)
1771        )
1772    }
1773
1774    #[test]
1775    fn test_target_config_locations_appearing() {
1776        let config = TargetConfig::new_reach_with_var_constr(
1777            [(Location::new("l1"), 42)],
1778            [Location::new("l2")],
1779            BooleanVarConstraint::ComparisonExpression(
1780                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1781                ComparisonOp::Eq,
1782                Box::new(IntegerExpression::Const(1)),
1783            ),
1784        )
1785        .unwrap();
1786
1787        let got_expr: HashSet<Location> = config
1788            .get_locations_appearing()
1789            .into_iter()
1790            .cloned()
1791            .collect();
1792
1793        let expected_locs = HashSet::from([Location::new("l1"), Location::new("l2")]);
1794
1795        assert_eq!(got_expr, expected_locs)
1796    }
1797
1798    #[test]
1799    fn test_target_config_is_reachability_constr() {
1800        let config = TargetConfig::new_reach_with_var_constr(
1801            [(Location::new("l1"), 42)],
1802            [Location::new("l2")],
1803            BooleanVarConstraint::ComparisonExpression(
1804                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1805                ComparisonOp::Eq,
1806                Box::new(IntegerExpression::Const(1)),
1807            ),
1808        )
1809        .unwrap();
1810
1811        assert!(config.is_reachability_constraint());
1812
1813        let config = TargetConfig::new_reach_with_var_constr(
1814            [(Location::new("l1"), 42)],
1815            [],
1816            BooleanVarConstraint::ComparisonExpression(
1817                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1818                ComparisonOp::Eq,
1819                Box::new(IntegerExpression::Const(1)),
1820            ),
1821        )
1822        .unwrap();
1823
1824        assert!(!config.is_reachability_constraint());
1825    }
1826
1827    #[test]
1828    fn test_disjunction_target_config_get_locations_in_target() {
1829        let config1 = TargetConfig::new_reach_with_var_constr(
1830            [(Location::new("l1"), 42)],
1831            [Location::new("l2")],
1832            BooleanVarConstraint::ComparisonExpression(
1833                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1834                ComparisonOp::Eq,
1835                Box::new(IntegerExpression::Const(1)),
1836            ),
1837        )
1838        .unwrap();
1839
1840        let config2 = TargetConfig::new_reach_with_var_constr(
1841            [(Location::new("l3"), 42)],
1842            [Location::new("l4")],
1843            BooleanVarConstraint::ComparisonExpression(
1844                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1845                ComparisonOp::Eq,
1846                Box::new(IntegerExpression::Const(1)),
1847            ),
1848        )
1849        .unwrap();
1850
1851        let dis = DisjunctionTargetConfig {
1852            property_name: "test".into(),
1853            targets: vec![config1, config2],
1854        };
1855
1856        let got_set_locations: HashSet<Location> =
1857            dis.get_locations_in_target().into_iter().cloned().collect();
1858
1859        let expected_set_locations = HashSet::from([
1860            Location::new("l1"),
1861            Location::new("l2"),
1862            Location::new("l3"),
1863            Location::new("l4"),
1864        ]);
1865
1866        assert_eq!(got_set_locations, expected_set_locations);
1867
1868        let dis = DisjunctionTargetConfig::new_empty("test".into());
1869
1870        let got_set_locations: HashSet<_> = dis.get_locations_in_target().into_iter().collect();
1871
1872        let expected_set_locations = HashSet::from([]);
1873
1874        assert_eq!(got_set_locations, expected_set_locations);
1875    }
1876
1877    #[test]
1878    fn test_disj_target_config_contains_reachability_constraint() {
1879        let config1 = TargetConfig::new_reach_with_var_constr(
1880            [(Location::new("l1"), 42)],
1881            [],
1882            BooleanVarConstraint::ComparisonExpression(
1883                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1884                ComparisonOp::Eq,
1885                Box::new(IntegerExpression::Const(1)),
1886            ),
1887        )
1888        .unwrap();
1889
1890        let config2 = TargetConfig::new_reach_with_var_constr(
1891            [(Location::new("l3"), 42)],
1892            [Location::new("l4")],
1893            BooleanVarConstraint::ComparisonExpression(
1894                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1895                ComparisonOp::Eq,
1896                Box::new(IntegerExpression::Const(1)),
1897            ),
1898        )
1899        .unwrap();
1900
1901        let dis = DisjunctionTargetConfig {
1902            property_name: "test".into(),
1903            targets: vec![config1, config2],
1904        };
1905
1906        assert!(dis.contains_reachability_constraint());
1907
1908        let disj = DisjunctionTargetConfig::new_empty("test".into());
1909
1910        assert!(!disj.contains_reachability_constraint())
1911    }
1912
1913    #[test]
1914    fn test_disj_target_config_encode_to_smt_empty() {
1915        let solver_builder = SMTSolverBuilder::default();
1916
1917        let ctx = StaticSMTContext::new(solver_builder, [], [], []).unwrap();
1918
1919        let solver = ctx.get_smt_solver();
1920
1921        let disj = DisjunctionTargetConfig::new_empty("test".into());
1922
1923        let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1924
1925        let expected_expr = solver.false_();
1926
1927        assert_eq!(
1928            got_expr,
1929            expected_expr,
1930            "got: {}, expected: {}",
1931            solver.display(got_expr),
1932            solver.display(expected_expr)
1933        )
1934    }
1935
1936    #[test]
1937    fn test_disj_target_config_encode_smt() {
1938        let solver_builder = SMTSolverBuilder::default();
1939
1940        let ctx = StaticSMTContext::new(
1941            solver_builder,
1942            [],
1943            [
1944                Location::new("l1"),
1945                Location::new("l2"),
1946                Location::new("l3"),
1947                Location::new("l4"),
1948            ],
1949            [Variable::new("v")],
1950        )
1951        .unwrap();
1952
1953        let solver = ctx.get_smt_solver();
1954
1955        let config1 = TargetConfig::new_reach_with_var_constr(
1956            [(Location::new("l1"), 42)],
1957            [Location::new("l2")],
1958            BooleanVarConstraint::ComparisonExpression(
1959                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1960                ComparisonOp::Eq,
1961                Box::new(IntegerExpression::Const(1)),
1962            ),
1963        )
1964        .unwrap();
1965
1966        let config2 = TargetConfig::new_reach_with_var_constr(
1967            [(Location::new("l3"), 42)],
1968            [Location::new("l4")],
1969            BooleanVarConstraint::ComparisonExpression(
1970                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1971                ComparisonOp::Eq,
1972                Box::new(IntegerExpression::Const(1)),
1973            ),
1974        )
1975        .unwrap();
1976
1977        let disj = DisjunctionTargetConfig {
1978            property_name: "test".into(),
1979            targets: vec![config1, config2],
1980        };
1981
1982        let got_expr = disj.encode_to_smt_with_ctx(solver, &ctx).unwrap();
1983
1984        let expected_expr = solver.or_many([
1985            solver.and_many([
1986                solver.gte(
1987                    ctx.get_expr_for(&Location::new("l1")).unwrap(),
1988                    solver.numeral(42),
1989                ),
1990                solver.eq(
1991                    ctx.get_expr_for(&Location::new("l2")).unwrap(),
1992                    solver.numeral(0),
1993                ),
1994                solver.and(
1995                    solver.lt(
1996                        ctx.get_expr_for(&Variable::new("v")).unwrap(),
1997                        solver.numeral(2),
1998                    ),
1999                    solver.gte(
2000                        ctx.get_expr_for(&Variable::new("v")).unwrap(),
2001                        solver.numeral(1),
2002                    ),
2003                ),
2004            ]),
2005            solver.and_many([
2006                solver.gte(
2007                    ctx.get_expr_for(&Location::new("l3")).unwrap(),
2008                    solver.numeral(42),
2009                ),
2010                solver.eq(
2011                    ctx.get_expr_for(&Location::new("l4")).unwrap(),
2012                    solver.numeral(0),
2013                ),
2014                solver.and(
2015                    solver.lt(
2016                        ctx.get_expr_for(&Variable::new("v")).unwrap(),
2017                        solver.numeral(2),
2018                    ),
2019                    solver.gte(
2020                        ctx.get_expr_for(&Variable::new("v")).unwrap(),
2021                        solver.numeral(1),
2022                    ),
2023                ),
2024            ]),
2025        ]);
2026
2027        assert_eq!(
2028            got_expr,
2029            expected_expr,
2030            "got: {}, expected: {}",
2031            solver.display(got_expr),
2032            solver.display(expected_expr)
2033        )
2034    }
2035
2036    #[test]
2037    fn test_reachability_property_get_name() {
2038        let reach_property = ReachabilityProperty {
2039            name: "test".to_string(),
2040            disj: vec![],
2041        };
2042
2043        assert_eq!(reach_property.name(), "test")
2044    }
2045
2046    #[test]
2047    fn test_reachability_property_contains_reachability_constraint() {
2048        let reach_property = ReachabilityProperty {
2049            name: "test".to_string(),
2050            disj: vec![],
2051        };
2052        assert!(!reach_property.contains_reachability_constraint());
2053
2054        let reach_property = ReachabilityProperty {
2055            name: "test".to_string(),
2056            disj: vec![Reachability {
2057                precondition_par: vec![],
2058                precondition_loc: vec![],
2059                precondition_var: vec![],
2060                target: DisjunctionTargetConfig {
2061                    property_name: "test".into(),
2062                    targets: vec![TargetConfig::new_loc_not_covered(Location::new("l"))],
2063                },
2064            }],
2065        };
2066        assert!(reach_property.contains_reachability_constraint());
2067    }
2068
2069    #[test]
2070    fn test_reachability_property_create_tas() {
2071        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
2072            .with_parameter(Parameter::new("n"))
2073            .unwrap()
2074            .with_location(Location::new("l"))
2075            .unwrap()
2076            .with_variable(Variable::new("v"))
2077            .unwrap()
2078            .initialize()
2079            .build();
2080
2081        let reach_property = ReachabilityProperty {
2082            name: "test".to_string(),
2083            disj: vec![],
2084        };
2085        assert_eq!(reach_property.create_tas_to_check(&ta).count(), 0);
2086
2087        let reach_property = ReachabilityProperty {
2088            name: "test".to_string(),
2089            disj: vec![Reachability {
2090                precondition_par: vec![BooleanExpression::ComparisonExpression(
2091                    Box::new(IntegerExpression::Param(Parameter::new("n"))),
2092                    ComparisonOp::Gt,
2093                    Box::new(IntegerExpression::Const(42)),
2094                )],
2095                precondition_loc: vec![BooleanExpression::ComparisonExpression(
2096                    Box::new(IntegerExpression::Atom(Location::new("l"))),
2097                    ComparisonOp::Eq,
2098                    Box::new(IntegerExpression::Const(0)),
2099                )],
2100                precondition_var: vec![],
2101                target: DisjunctionTargetConfig {
2102                    property_name: "test".into(),
2103                    targets: vec![],
2104                },
2105            }],
2106        };
2107        let mut it = reach_property.create_tas_to_check(&ta);
2108        let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2109            .with_parameter(Parameter::new("n"))
2110            .unwrap()
2111            .with_location(Location::new("l"))
2112            .unwrap()
2113            .with_variable(Variable::new("v"))
2114            .unwrap()
2115            .initialize()
2116            .with_resilience_condition(BooleanExpression::ComparisonExpression(
2117                Box::new(IntegerExpression::Param(Parameter::new("n"))),
2118                ComparisonOp::Gt,
2119                Box::new(IntegerExpression::Const(42)),
2120            ))
2121            .unwrap()
2122            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2123                Box::new(IntegerExpression::Atom(Location::new("l"))),
2124                ComparisonOp::Eq,
2125                Box::new(IntegerExpression::Const(0)),
2126            ))
2127            .unwrap()
2128            .build();
2129        let expected_disj = DisjunctionTargetConfig {
2130            property_name: "test".into(),
2131            targets: vec![],
2132        };
2133        assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2134        assert_eq!(it.next(), None);
2135
2136        let reach_property = ReachabilityProperty {
2137            name: "test".to_string(),
2138            disj: vec![Reachability {
2139                precondition_par: vec![],
2140                precondition_loc: vec![BooleanExpression::ComparisonExpression(
2141                    Box::new(IntegerExpression::Atom(Location::new("l"))),
2142                    ComparisonOp::Gt,
2143                    Box::new(IntegerExpression::Const(42)),
2144                )],
2145                precondition_var: vec![],
2146                target: DisjunctionTargetConfig {
2147                    property_name: "test".into(),
2148                    targets: vec![],
2149                },
2150            }],
2151        };
2152        let mut it = reach_property.create_tas_to_check(&ta);
2153        let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2154            .with_parameter(Parameter::new("n"))
2155            .unwrap()
2156            .with_location(Location::new("l"))
2157            .unwrap()
2158            .with_variable(Variable::new("v"))
2159            .unwrap()
2160            .initialize()
2161            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2162                Box::new(IntegerExpression::Atom(Location::new("l"))),
2163                ComparisonOp::Gt,
2164                Box::new(IntegerExpression::Const(42)),
2165            ))
2166            .unwrap()
2167            .build();
2168        let expected_disj = DisjunctionTargetConfig {
2169            property_name: "test".into(),
2170            targets: vec![],
2171        };
2172        assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2173        assert_eq!(it.next(), None);
2174
2175        let reach_property = ReachabilityProperty {
2176            name: "test".to_string(),
2177            disj: vec![Reachability {
2178                precondition_par: vec![],
2179                precondition_loc: vec![BooleanExpression::ComparisonExpression(
2180                    Box::new(IntegerExpression::Atom(Location::new("l"))),
2181                    ComparisonOp::Gt,
2182                    Box::new(IntegerExpression::Const(42)),
2183                )],
2184                precondition_var: vec![],
2185                target: DisjunctionTargetConfig {
2186                    property_name: "test".into(),
2187                    targets: vec![
2188                        TargetConfig::new_unconstrained(),
2189                        TargetConfig::new_cover([Location::new("l")]).unwrap(),
2190                    ],
2191                },
2192            }],
2193        };
2194        let mut it = reach_property.create_tas_to_check(&ta);
2195        let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2196            .with_parameter(Parameter::new("n"))
2197            .unwrap()
2198            .with_location(Location::new("l"))
2199            .unwrap()
2200            .with_variable(Variable::new("v"))
2201            .unwrap()
2202            .initialize()
2203            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2204                Box::new(IntegerExpression::Atom(Location::new("l"))),
2205                ComparisonOp::Gt,
2206                Box::new(IntegerExpression::Const(42)),
2207            ))
2208            .unwrap()
2209            .build();
2210        let expected_disj = DisjunctionTargetConfig {
2211            property_name: "test".into(),
2212            targets: vec![
2213                TargetConfig::new_unconstrained(),
2214                TargetConfig::new_cover([Location::new("l")]).unwrap(),
2215            ],
2216        };
2217        assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2218        assert_eq!(it.next(), None);
2219
2220        let reach_property = ReachabilityProperty {
2221            name: "test".to_string(),
2222            disj: vec![
2223                Reachability {
2224                    precondition_par: vec![],
2225                    precondition_loc: vec![BooleanExpression::ComparisonExpression(
2226                        Box::new(IntegerExpression::Atom(Location::new("l"))),
2227                        ComparisonOp::Gt,
2228                        Box::new(IntegerExpression::Const(42)),
2229                    )],
2230                    precondition_var: vec![],
2231                    target: DisjunctionTargetConfig {
2232                        targets: vec![],
2233                        property_name: "test".into(),
2234                    },
2235                },
2236                Reachability {
2237                    precondition_par: vec![],
2238                    precondition_loc: vec![BooleanExpression::ComparisonExpression(
2239                        Box::new(IntegerExpression::Atom(Location::new("l"))),
2240                        ComparisonOp::Lt,
2241                        Box::new(IntegerExpression::Const(42)),
2242                    )],
2243                    precondition_var: vec![],
2244                    target: DisjunctionTargetConfig {
2245                        property_name: "test".into(),
2246                        targets: vec![],
2247                    },
2248                },
2249            ],
2250        };
2251        let mut it = reach_property.create_tas_to_check(&ta);
2252        let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2253            .with_parameter(Parameter::new("n"))
2254            .unwrap()
2255            .with_location(Location::new("l"))
2256            .unwrap()
2257            .with_variable(Variable::new("v"))
2258            .unwrap()
2259            .initialize()
2260            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2261                Box::new(IntegerExpression::Atom(Location::new("l"))),
2262                ComparisonOp::Gt,
2263                Box::new(IntegerExpression::Const(42)),
2264            ))
2265            .unwrap()
2266            .build();
2267        let expected_disj = DisjunctionTargetConfig {
2268            property_name: "test".into(),
2269            targets: vec![],
2270        };
2271        assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2272        let expected_disj = DisjunctionTargetConfig {
2273            property_name: "test".into(),
2274            targets: vec![],
2275        };
2276        let expected_ta = GeneralThresholdAutomatonBuilder::new("test_ta-test")
2277            .with_parameter(Parameter::new("n"))
2278            .unwrap()
2279            .with_location(Location::new("l"))
2280            .unwrap()
2281            .with_variable(Variable::new("v"))
2282            .unwrap()
2283            .initialize()
2284            .with_initial_location_constraint(BooleanExpression::ComparisonExpression(
2285                Box::new(IntegerExpression::Atom(Location::new("l"))),
2286                ComparisonOp::Lt,
2287                Box::new(IntegerExpression::Const(42)),
2288            ))
2289            .unwrap()
2290            .build();
2291        assert_eq!(it.next().unwrap(), (expected_disj, expected_ta));
2292        assert_eq!(it.next(), None);
2293    }
2294
2295    #[test]
2296    fn test_reachability_property_from_eltl() {
2297        // true -> false -> Must be empty disjunct
2298        let eltl = ELTLExpression::True;
2299
2300        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2301
2302        let expected_reach = ReachabilityProperty {
2303            name: "test".to_string(),
2304            disj: vec![],
2305        };
2306
2307        assert_eq!(got_reach, expected_reach);
2308
2309        // false -> true -> any location
2310        let eltl = ELTLExpression::False;
2311
2312        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2313
2314        let expected_reach = ReachabilityProperty {
2315            name: "test".to_string(),
2316            disj: vec![Reachability::new_unconstrained("test".into())],
2317        };
2318
2319        assert_eq!(got_reach, expected_reach);
2320
2321        // n == 3 -> n != 3
2322        let eltl = ELTLExpression::ParameterExpr(
2323            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2324            ComparisonOp::Eq,
2325            Box::new(IntegerExpression::Const(3)),
2326        );
2327
2328        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2329
2330        let expected_reach = ReachabilityProperty {
2331            name: "test".to_string(),
2332            disj: vec![Reachability {
2333                precondition_par: vec![BooleanExpression::ComparisonExpression(
2334                    Box::new(IntegerExpression::Param(Parameter::new("n"))),
2335                    ComparisonOp::Neq,
2336                    Box::new(IntegerExpression::Const(3)),
2337                )],
2338                precondition_loc: vec![],
2339                precondition_var: vec![],
2340                target: DisjunctionTargetConfig::new_empty("test".into()),
2341            }],
2342        };
2343
2344        assert_eq!(got_reach, expected_reach);
2345
2346        // v < n -> v > n
2347        let eltl = ELTLExpression::VariableExpr(
2348            Box::new(IntegerExpression::Atom(Variable::new("v"))),
2349            ComparisonOp::Lt,
2350            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2351        );
2352
2353        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2354
2355        let expected_reach = ReachabilityProperty {
2356            name: "test".to_string(),
2357            disj: vec![Reachability {
2358                precondition_par: vec![],
2359                precondition_loc: vec![],
2360                precondition_var: vec![BooleanExpression::ComparisonExpression(
2361                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2362                    ComparisonOp::Geq,
2363                    Box::new(IntegerExpression::Param(Parameter::new("n"))),
2364                )],
2365                target: DisjunctionTargetConfig::new_empty("test".into()),
2366            }],
2367        };
2368
2369        assert_eq!(got_reach, expected_reach);
2370
2371        // l < n -> v > n
2372        let eltl = ELTLExpression::LocationExpr(
2373            Box::new(IntegerExpression::Atom(Location::new("l"))),
2374            ComparisonOp::Lt,
2375            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2376        );
2377
2378        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2379
2380        let expected_reach = ReachabilityProperty {
2381            name: "test".to_string(),
2382            disj: vec![Reachability {
2383                precondition_par: vec![],
2384                precondition_loc: vec![BooleanExpression::ComparisonExpression(
2385                    Box::new(IntegerExpression::Atom(Location::new("l"))),
2386                    ComparisonOp::Geq,
2387                    Box::new(IntegerExpression::Param(Parameter::new("n"))),
2388                )],
2389                precondition_var: vec![],
2390                target: DisjunctionTargetConfig::new_empty("test".into()),
2391            }],
2392        };
2393
2394        assert_eq!(got_reach, expected_reach);
2395
2396        // (l < 1) && (v < 3)  -> (l >= 1) || (v >= 3)
2397        let eltl = ELTLExpression::And(
2398            Box::new(ELTLExpression::LocationExpr(
2399                Box::new(IntegerExpression::Atom(Location::new("l"))),
2400                ComparisonOp::Lt,
2401                Box::new(IntegerExpression::Const(1)),
2402            )),
2403            Box::new(ELTLExpression::VariableExpr(
2404                Box::new(IntegerExpression::Atom(Variable::new("v"))),
2405                ComparisonOp::Lt,
2406                Box::new(IntegerExpression::Const(3)),
2407            )),
2408        );
2409
2410        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2411
2412        let expected_reach = ReachabilityProperty {
2413            name: "test".to_string(),
2414            disj: vec![
2415                Reachability {
2416                    precondition_par: vec![],
2417                    precondition_loc: vec![BooleanExpression::ComparisonExpression(
2418                        Box::new(IntegerExpression::Atom(Location::new("l"))),
2419                        ComparisonOp::Geq,
2420                        Box::new(IntegerExpression::Const(1)),
2421                    )],
2422                    precondition_var: vec![],
2423                    target: DisjunctionTargetConfig::new_empty("test".into()),
2424                },
2425                Reachability {
2426                    precondition_par: vec![],
2427                    precondition_loc: vec![],
2428                    precondition_var: vec![BooleanExpression::ComparisonExpression(
2429                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2430                        ComparisonOp::Geq,
2431                        Box::new(IntegerExpression::Const(3)),
2432                    )],
2433                    target: DisjunctionTargetConfig::new_empty("test".into()),
2434                },
2435            ],
2436        };
2437
2438        assert_eq!(got_reach, expected_reach);
2439
2440        // (l < 1) || (v < 3)  -> (l >= 1) && (v >= 3)
2441        let eltl = ELTLExpression::And(
2442            Box::new(ELTLExpression::LocationExpr(
2443                Box::new(IntegerExpression::Atom(Location::new("l"))),
2444                ComparisonOp::Lt,
2445                Box::new(IntegerExpression::Const(1)),
2446            )),
2447            Box::new(ELTLExpression::VariableExpr(
2448                Box::new(IntegerExpression::Atom(Variable::new("v"))),
2449                ComparisonOp::Lt,
2450                Box::new(IntegerExpression::Const(3)),
2451            )),
2452        );
2453
2454        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2455
2456        let expected_reach = ReachabilityProperty {
2457            name: "test".to_string(),
2458            disj: vec![
2459                Reachability {
2460                    precondition_par: vec![],
2461                    precondition_loc: vec![BooleanExpression::ComparisonExpression(
2462                        Box::new(IntegerExpression::Atom(Location::new("l"))),
2463                        ComparisonOp::Geq,
2464                        Box::new(IntegerExpression::Const(1)),
2465                    )],
2466                    precondition_var: vec![],
2467                    target: DisjunctionTargetConfig::new_empty("test".into()),
2468                },
2469                Reachability {
2470                    precondition_par: vec![],
2471                    precondition_loc: vec![],
2472                    precondition_var: vec![BooleanExpression::ComparisonExpression(
2473                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2474                        ComparisonOp::Geq,
2475                        Box::new(IntegerExpression::Const(3)),
2476                    )],
2477                    target: DisjunctionTargetConfig::new_empty("test".into()),
2478                },
2479            ],
2480        };
2481
2482        assert_eq!(got_reach, expected_reach);
2483
2484        // !((l < 1) && (v < 3))  -> ((l < 1) && (v < 3))
2485        let eltl = ELTLExpression::Not(Box::new(ELTLExpression::And(
2486            Box::new(ELTLExpression::LocationExpr(
2487                Box::new(IntegerExpression::Atom(Location::new("l"))),
2488                ComparisonOp::Lt,
2489                Box::new(IntegerExpression::Const(1)),
2490            )),
2491            Box::new(ELTLExpression::VariableExpr(
2492                Box::new(IntegerExpression::Atom(Variable::new("v"))),
2493                ComparisonOp::Lt,
2494                Box::new(IntegerExpression::Const(3)),
2495            )),
2496        )));
2497
2498        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2499
2500        let expected_reach = ReachabilityProperty {
2501            name: "test".to_string(),
2502            disj: vec![Reachability {
2503                precondition_par: vec![],
2504                precondition_loc: vec![BooleanExpression::ComparisonExpression(
2505                    Box::new(IntegerExpression::Atom(Location::new("l"))),
2506                    ComparisonOp::Lt,
2507                    Box::new(IntegerExpression::Const(1)),
2508                )],
2509                precondition_var: vec![BooleanExpression::ComparisonExpression(
2510                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2511                    ComparisonOp::Lt,
2512                    Box::new(IntegerExpression::Const(3)),
2513                )],
2514                target: DisjunctionTargetConfig::new_empty("test".into()),
2515            }],
2516        };
2517
2518        assert_eq!(got_reach, expected_reach);
2519
2520        // [](true) -> <>(false)
2521        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::True));
2522
2523        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2524
2525        let expected_reach = ReachabilityProperty {
2526            name: "test".to_string(),
2527            disj: vec![Reachability {
2528                precondition_par: vec![],
2529                precondition_loc: vec![],
2530                precondition_var: vec![],
2531                target: DisjunctionTargetConfig {
2532                    property_name: "test".into(),
2533                    targets: vec![],
2534                },
2535            }],
2536        };
2537
2538        assert_eq!(got_reach, expected_reach);
2539
2540        // [](false) -> <>(true)
2541        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::False));
2542
2543        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2544
2545        let expected_reach = ReachabilityProperty {
2546            name: "test".to_string(),
2547            disj: vec![Reachability {
2548                precondition_par: vec![],
2549                precondition_loc: vec![],
2550                precondition_var: vec![],
2551                target: DisjunctionTargetConfig {
2552                    property_name: "test".into(),
2553                    targets: vec![TargetConfig::new_unconstrained()],
2554                },
2555            }],
2556        };
2557
2558        assert_eq!(got_reach, expected_reach);
2559
2560        // [](v < n) -> <>(v >= n)
2561        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::VariableExpr(
2562            Box::new(IntegerExpression::Atom(Variable::new("v"))),
2563            ComparisonOp::Lt,
2564            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2565        )));
2566
2567        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2568
2569        let expected_reach = ReachabilityProperty {
2570            name: "test".to_string(),
2571            disj: vec![Reachability {
2572                precondition_par: vec![],
2573                precondition_loc: vec![],
2574                precondition_var: vec![],
2575                target: DisjunctionTargetConfig {
2576                    property_name: "test".into(),
2577                    targets: vec![
2578                        TargetConfig::new_with_var_constr(BooleanExpression::ComparisonExpression(
2579                            Box::new(IntegerExpression::Atom(Variable::new("v"))),
2580                            ComparisonOp::Geq,
2581                            Box::new(IntegerExpression::Param(Parameter::new("n"))),
2582                        ))
2583                        .unwrap(),
2584                    ],
2585                },
2586            }],
2587        };
2588
2589        assert_eq!(got_reach, expected_reach);
2590
2591        // [](l == 0) -> <>(l > 1)
2592        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::LocationExpr(
2593            Box::new(IntegerExpression::Atom(Location::new("l"))),
2594            ComparisonOp::Eq,
2595            Box::new(IntegerExpression::Const(0)),
2596        )));
2597
2598        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2599
2600        let expected_reach = ReachabilityProperty {
2601            name: "test".to_string(),
2602            disj: vec![Reachability {
2603                precondition_par: vec![],
2604                precondition_loc: vec![],
2605                precondition_var: vec![],
2606                target: DisjunctionTargetConfig {
2607                    property_name: "test".into(),
2608                    targets: vec![TargetConfig::new_cover([Location::new("l")]).unwrap()],
2609                },
2610            }],
2611        };
2612
2613        assert_eq!(got_reach, expected_reach);
2614
2615        // [](l1 == 0 || l2 != 0) -> <>(l1 != 0 && l2 == 0)
2616        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2617            Box::new(ELTLExpression::LocationExpr(
2618                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2619                ComparisonOp::Eq,
2620                Box::new(IntegerExpression::Const(0)),
2621            )),
2622            Box::new(ELTLExpression::LocationExpr(
2623                Box::new(IntegerExpression::Atom(Location::new("l2"))),
2624                ComparisonOp::Neq,
2625                Box::new(IntegerExpression::Const(0)),
2626            )),
2627        )));
2628
2629        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2630
2631        let expected_reach = ReachabilityProperty {
2632            name: "test".to_string(),
2633            disj: vec![Reachability {
2634                precondition_par: vec![],
2635                precondition_loc: vec![],
2636                precondition_var: vec![],
2637                target: DisjunctionTargetConfig {
2638                    property_name: "test".into(),
2639                    targets: vec![
2640                        TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2641                            .unwrap(),
2642                    ],
2643                },
2644            }],
2645        };
2646
2647        assert_eq!(got_reach, expected_reach);
2648
2649        // [](l1 == 0 && l2 != 0) -> <>(l1 != 0 || l2 == 0)
2650        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::And(
2651            Box::new(ELTLExpression::LocationExpr(
2652                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2653                ComparisonOp::Eq,
2654                Box::new(IntegerExpression::Const(0)),
2655            )),
2656            Box::new(ELTLExpression::LocationExpr(
2657                Box::new(IntegerExpression::Atom(Location::new("l2"))),
2658                ComparisonOp::Neq,
2659                Box::new(IntegerExpression::Const(0)),
2660            )),
2661        )));
2662
2663        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2664
2665        let expected_reach = ReachabilityProperty {
2666            name: "test".to_string(),
2667            disj: vec![Reachability {
2668                precondition_par: vec![],
2669                precondition_loc: vec![],
2670                precondition_var: vec![],
2671                target: DisjunctionTargetConfig {
2672                    property_name: "test".into(),
2673                    targets: vec![
2674                        TargetConfig::new_cover([Location::new("l1")]).unwrap(),
2675                        TargetConfig::new_loc_not_covered(Location::new("l2")),
2676                    ],
2677                },
2678            }],
2679        };
2680
2681        assert_eq!(got_reach, expected_reach);
2682
2683        // []([](l1 == 0 || l2 != 0)) -> <>(l1 != 0 && l2 == 0)
2684        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Globally(Box::new(
2685            ELTLExpression::Or(
2686                Box::new(ELTLExpression::LocationExpr(
2687                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2688                    ComparisonOp::Eq,
2689                    Box::new(IntegerExpression::Const(0)),
2690                )),
2691                Box::new(ELTLExpression::LocationExpr(
2692                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
2693                    ComparisonOp::Neq,
2694                    Box::new(IntegerExpression::Const(0)),
2695                )),
2696            ),
2697        ))));
2698
2699        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2700
2701        let expected_reach = ReachabilityProperty {
2702            name: "test".to_string(),
2703            disj: vec![Reachability {
2704                precondition_par: vec![],
2705                precondition_loc: vec![],
2706                precondition_var: vec![],
2707                target: DisjunctionTargetConfig {
2708                    property_name: "test".into(),
2709                    targets: vec![
2710                        TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2711                            .unwrap(),
2712                    ],
2713                },
2714            }],
2715        };
2716
2717        assert_eq!(got_reach, expected_reach);
2718
2719        // [](l1 == 0 || [](l2 != 0)) -> <>(l1 != 0 && l2 == 0)
2720        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2721            Box::new(ELTLExpression::LocationExpr(
2722                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2723                ComparisonOp::Eq,
2724                Box::new(IntegerExpression::Const(0)),
2725            )),
2726            Box::new(ELTLExpression::Globally(Box::new(
2727                ELTLExpression::LocationExpr(
2728                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
2729                    ComparisonOp::Neq,
2730                    Box::new(IntegerExpression::Const(0)),
2731                ),
2732            ))),
2733        )));
2734
2735        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2736
2737        let expected_reach = ReachabilityProperty {
2738            name: "test".to_string(),
2739            disj: vec![Reachability {
2740                precondition_par: vec![],
2741                precondition_loc: vec![],
2742                precondition_var: vec![],
2743                target: DisjunctionTargetConfig {
2744                    property_name: "test".into(),
2745                    targets: vec![
2746                        TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2747                            .unwrap(),
2748                    ],
2749                },
2750            }],
2751        };
2752
2753        assert_eq!(got_reach, expected_reach);
2754
2755        // (v == 0) =>  [](l1 == 0 || [](l2 != 0))
2756        let eltl = ELTLExpression::Implies(
2757            Box::new(ELTLExpression::VariableExpr(
2758                Box::new(IntegerExpression::Atom(Variable::new("v"))),
2759                ComparisonOp::Eq,
2760                Box::new(IntegerExpression::Const(0)),
2761            )),
2762            Box::new(ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2763                Box::new(ELTLExpression::LocationExpr(
2764                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2765                    ComparisonOp::Eq,
2766                    Box::new(IntegerExpression::Const(0)),
2767                )),
2768                Box::new(ELTLExpression::Globally(Box::new(
2769                    ELTLExpression::LocationExpr(
2770                        Box::new(IntegerExpression::Atom(Location::new("l2"))),
2771                        ComparisonOp::Neq,
2772                        Box::new(IntegerExpression::Const(0)),
2773                    ),
2774                ))),
2775            )))),
2776        );
2777
2778        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2779
2780        let expected_reach = ReachabilityProperty {
2781            name: "test".to_string(),
2782            disj: vec![Reachability {
2783                precondition_par: vec![],
2784                precondition_loc: vec![],
2785                precondition_var: vec![BooleanExpression::ComparisonExpression(
2786                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2787                    ComparisonOp::Eq,
2788                    Box::new(IntegerExpression::Const(0)),
2789                )],
2790                target: DisjunctionTargetConfig {
2791                    property_name: "test".into(),
2792                    targets: vec![
2793                        TargetConfig::new_reach([Location::new("l1")], [Location::new("l2")])
2794                            .unwrap(),
2795                    ],
2796                },
2797            }],
2798        };
2799
2800        assert_eq!(got_reach, expected_reach);
2801
2802        // !(l1 == 0) || [](l2 == 0)
2803        let eltl = ELTLExpression::Or(
2804            Box::new(ELTLExpression::Not(Box::new(ELTLExpression::And(
2805                Box::new(ELTLExpression::LocationExpr(
2806                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2807                    ComparisonOp::Eq,
2808                    Box::new(IntegerExpression::Const(0)),
2809                )),
2810                Box::new(ELTLExpression::LocationExpr(
2811                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
2812                    ComparisonOp::Eq,
2813                    Box::new(IntegerExpression::Const(0)),
2814                )),
2815            )))),
2816            Box::new(ELTLExpression::Globally(Box::new(
2817                ELTLExpression::LocationExpr(
2818                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
2819                    ComparisonOp::Eq,
2820                    Box::new(IntegerExpression::Const(0)),
2821                ),
2822            ))),
2823        );
2824
2825        let got_reach = ReachabilityProperty::from_named_eltl("test", eltl).unwrap();
2826
2827        let expected_reach = ReachabilityProperty {
2828            name: "test".to_string(),
2829            disj: vec![Reachability {
2830                precondition_par: vec![],
2831                precondition_loc: vec![
2832                    BooleanExpression::ComparisonExpression(
2833                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
2834                        ComparisonOp::Eq,
2835                        Box::new(IntegerExpression::Const(0)),
2836                    ),
2837                    BooleanExpression::ComparisonExpression(
2838                        Box::new(IntegerExpression::Atom(Location::new("l2"))),
2839                        ComparisonOp::Eq,
2840                        Box::new(IntegerExpression::Const(0)),
2841                    ),
2842                ],
2843                precondition_var: vec![],
2844                target: DisjunctionTargetConfig {
2845                    property_name: "test".into(),
2846                    targets: vec![TargetConfig::new_reach([Location::new("l2")], []).unwrap()],
2847                },
2848            }],
2849        };
2850
2851        assert_eq!(got_reach, expected_reach);
2852    }
2853}