Skip to main content

taco_model_checker/internal_spec/
extraction.rs

1//! Module implementing the extraction of internal specification type from
2//! general LTL formulas
3
4use core::{error, fmt};
5use std::ops::Not;
6
7use taco_threshold_automaton::{
8    expressions::BooleanExpression,
9    lia_threshold_automaton::{
10        ConstraintRewriteError, LIAVariableConstraint, integer_thresholds::DeriveFromIntegerComp,
11    },
12};
13
14use crate::{
15    eltl::{ELTLExpression, remove_negations::NonNegatedELTLExpression},
16    internal_spec::{
17        Disjunction, ErrorAtom, ErrorFormula, ErrorSpec,
18        ErrorTarget::{self, Ensure, Invariant, Reach, Repeat, Top},
19        InitRestriction,
20        extraction::ExtractionError::NestedTemporalOperator,
21        upwards_closed_set::{UpwardsClosedSet, UpwardsClosedSetExtractionError},
22    },
23};
24
25impl ErrorSpec {
26    /// Error specification of a named ELTL formula
27    ///
28    /// `property` describes the erroneous behavior and is not negated here.
29    /// Negations are pushed to the atoms, then the formula is transformed into
30    /// an [`ErrorFormula`]. Temporal operators at the top level become
31    /// targets. Atoms at the top level restrict the initial configuration.
32    pub fn from_named_eltl<S: Into<String>>(
33        name: S,
34        property: ELTLExpression,
35    ) -> Result<Self, ExtractionError> {
36        let ef = extract_error_formula(property.clone().not().into())?;
37
38        Ok(Self {
39            name: name.into(),
40            source: Box::new(property),
41            ef,
42        })
43    }
44}
45
46impl ErrorFormula {
47    /// Create a new Error formula for the specific target
48    pub fn new(init: InitRestriction, tgt: ErrorTarget) -> Self {
49        Disjunction::from([ErrorAtom::new(init, tgt)])
50    }
51}
52
53impl ErrorAtom {
54    /// Conjunction of two atoms that are the operands of one `&&`
55    ///
56    /// Create conjunction of initial constraints and check whether targets can
57    /// be unified with [`ErrorTarget::try_unify_and`].
58    /// Otherwise throws [`ExtractionError::UnsupportedConjunction`]
59    fn try_and(self, other: Self) -> Result<Self, ExtractionError> {
60        let target = self
61            .target
62            .try_unify_and(&other.target)
63            .ok_or_else(|| ExtractionError::UnsupportedConjunction(self.target, other.target))?;
64
65        Ok(Self::new(
66            self.init_restriction & other.init_restriction,
67            target,
68        ))
69    }
70
71    /// Disjunction of two atoms if a sound merge exists
72    fn try_unify_or(&self, other: &Self) -> Option<Self> {
73        // `(i && t) || (j && t)` is equal to `(i || j) && t`
74        if self.target == other.target {
75            let init = self.init_restriction.try_or(&other.init_restriction)?;
76            return Some(Self::new(init, self.target.clone()));
77        }
78
79        // `(i && t) || (i && u)` is equal to `i && (t || u)`
80        if self.init_restriction == other.init_restriction {
81            let target = self.target.try_unify_or(&other.target)?;
82            return Some(Self::new(self.init_restriction.clone(), target));
83        }
84
85        None
86    }
87}
88
89impl ErrorTarget {
90    /// Merge two targets that are the operands of one `&&`, if possible
91    fn try_unify_and(&self, other: &Self) -> Option<Self> {
92        // Distributivity only holds for []
93        match (self, other) {
94            (Top, tgt) | (tgt, Top) => Some(tgt.clone()),
95            (Invariant(l), Invariant(r)) => Some(Invariant(l.clone() & r.clone())),
96            // `[](a) && [](pre && <>(inv))` is equal to `[](a && pre && <>(inv))`
97            (Invariant(a), Repeat { pre, inv }) | (Repeat { pre, inv }, Invariant(a)) => {
98                Some(Repeat {
99                    pre: pre.clone() & a.clone(),
100                    inv: inv.clone(),
101                })
102            }
103            // `<>` and `[]<>` do not distribute over `&&`
104            (_, _) => None,
105        }
106    }
107
108    /// Merge two targets that are the operands of one `||`, if possible
109    fn try_unify_or(&self, other: &Self) -> Option<Self> {
110        match (self, other) {
111            (Reach(l), Reach(r)) => Some(Self::Reach(l.clone() | r.clone())),
112            (
113                Ensure {
114                    pre: pre_l,
115                    inv: inv_l,
116                },
117                Ensure {
118                    pre: pre_r,
119                    inv: inv_r,
120                },
121            ) => {
122                if inv_l != inv_r {
123                    return None;
124                }
125
126                let pre = pre_l.clone() | pre_r.clone();
127                Some(Ensure {
128                    pre,
129                    inv: inv_l.clone(),
130                })
131            }
132            (_, _) => None,
133        }
134    }
135
136    /// Extract the invariant or repeat target of a formula inside a `[]`
137    fn extract_from_inner_globally(
138        expr: NonNegatedELTLExpression,
139    ) -> Result<ErrorTarget, ExtractionError> {
140        extract_from_inner_globally_helper(expr, true)
141    }
142
143    /// Extract the reach and repeat targets of a formula inside an `<>`
144    fn extract_from_inner_eventually(
145        expr: NonNegatedELTLExpression,
146    ) -> Result<Disjunction<ErrorTarget>, ExtractionError> {
147        extract_from_inner_eventually_helper(expr, true)
148    }
149}
150
151/// Error formula of an ELTL formula without negations
152///
153/// Temporal operators at the top level become targets. Atoms at the top level
154/// restrict the initial configuration. `&&` distributes over the disjunction
155/// of atoms. Atoms of one `||` are merged where a sound merge exists.
156fn extract_error_formula(expr: NonNegatedELTLExpression) -> Result<ErrorFormula, ExtractionError> {
157    match expr {
158        NonNegatedELTLExpression::Globally(inner) => {
159            let tgt = ErrorTarget::extract_from_inner_globally(*inner)?;
160            Ok(ErrorFormula::new(InitRestriction::new_top(), tgt))
161        }
162        NonNegatedELTLExpression::Eventually(inner) => {
163            let tgts = ErrorTarget::extract_from_inner_eventually(*inner)?;
164            Ok(tgts
165                .into_iter()
166                .map(|tgt| ErrorAtom::new(InitRestriction::new_top(), tgt))
167                .collect())
168        }
169        // `(a || b) && (c || d)` is equal to
170        // `(a && c) || (a && d) || (b && c) || (b && d)`
171        NonNegatedELTLExpression::And(lhs, rhs) => {
172            let lhs = extract_error_formula(*lhs)?;
173            let rhs = extract_error_formula(*rhs)?;
174
175            lhs.into_iter()
176                .flat_map(|l| rhs.iter().map(move |r| l.clone().try_and(r.clone())))
177                .collect()
178        }
179        NonNegatedELTLExpression::Or(lhs, rhs) => {
180            let lhs = extract_error_formula(*lhs)?;
181            let rhs = extract_error_formula(*rhs)?;
182
183            Ok(unify_all_possible(lhs, rhs, ErrorAtom::try_unify_or))
184        }
185        // Atoms hold at the first position of the run. Because of this, they
186        // restrict the initial configuration.
187        NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
188            let constr = BooleanExpression::ComparisonExpression(lhs, op, rhs);
189            let init = InitRestriction::new_location_constraint(constr);
190            Ok(ErrorFormula::new(init, Top))
191        }
192        NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
193            let constr = BooleanExpression::ComparisonExpression(lhs, op, rhs);
194            let init = InitRestriction::new_variable_constraint(constr);
195            Ok(ErrorFormula::new(init, Top))
196        }
197        NonNegatedELTLExpression::ParameterExpr(lhs, op, rhs) => {
198            let constr = BooleanExpression::ComparisonExpression(lhs, op, rhs);
199            let init = InitRestriction::new_parameter_constraint(constr);
200            Ok(ErrorFormula::new(init, Top))
201        }
202        NonNegatedELTLExpression::True => Ok(ErrorFormula::new(InitRestriction::new_top(), Top)),
203        // The empty disjunction is `false`
204        NonNegatedELTLExpression::False => Ok(ErrorFormula::new_bot()),
205    }
206}
207
208/// Extract the targets of an ELTL formula that is inside an outer `<>`
209///
210/// The result is a disjunction of [`Reach`] and [`Repeat`] targets.
211/// `ev_not_in_conj` is `true` if until now, constraints inside the eventually
212/// have not yet appeared in a conjunction. Only then can we apply
213/// distributivity and remove nested `<>`
214fn extract_from_inner_eventually_helper(
215    expr: NonNegatedELTLExpression,
216    ev_not_in_conj: bool,
217) -> Result<Disjunction<ErrorTarget>, ExtractionError> {
218    match expr {
219        NonNegatedELTLExpression::Globally(expr) => {
220            match ErrorTarget::extract_from_inner_globally(*expr)? {
221                // `<>([](inv))` -> `<>(true && [](inv))`
222                Invariant(inv) => {
223                    let tgt = ErrorTarget::new_ensure(UpwardsClosedSet::new_top(), inv);
224                    Ok([tgt].into())
225                }
226                // `<>([](pre && <>(inv)))` cannot be translated
227                _ => Err(NestedTemporalOperator),
228            }
229        }
230        NonNegatedELTLExpression::Eventually(expr) => {
231            // Distributivity does not apply
232            if !ev_not_in_conj {
233                return Err(ExtractionError::NestedTemporalOperator);
234            }
235            extract_from_inner_eventually_helper(*expr, true)
236        }
237        // Both operands hold at the same position of the run. Because of this,
238        // each target of `lhs` is combined with each target of `rhs`.
239        NonNegatedELTLExpression::And(lhs, rhs) => {
240            let lhs = extract_from_inner_eventually_helper(*lhs, false)?;
241            let rhs = extract_from_inner_eventually_helper(*rhs, false)?;
242
243            // Note: Here we know that lhs and rhs do not contain a nested <>,
244            // and they can only contain Reach and Repeat specifications
245            // This means that any
246
247            Ok(lhs
248                .into_iter()
249                .flat_map(|l| {
250                    rhs.iter()
251                        .map(move |r| and_inside_eventually(l.clone(), r.clone()))
252                })
253                .collect())
254        }
255
256        NonNegatedELTLExpression::Or(lhs, rhs) => {
257            let lhs = extract_from_inner_eventually_helper(*lhs, ev_not_in_conj)?;
258            let rhs = extract_from_inner_eventually_helper(*rhs, ev_not_in_conj)?;
259
260            let tgts = unify_all_possible(lhs, rhs, ErrorTarget::try_unify_or);
261
262            Ok(tgts)
263        }
264        s => Ok([Reach(extract_atom_helper(s)?)].into()),
265    }
266}
267
268/// Helper function to extract the target of an ELTL formula that appears
269/// inside of an outer []
270///
271/// The result is an [`Invariant`] or a [`Repeat`] target.
272/// `appears_in_conj` specifies whether inner constraints have until now
273/// only appeared in a conjunction. Only if this is the case, distributivity
274/// holds and an inner globally operator can be parsed as a simple conjunct
275fn extract_from_inner_globally_helper(
276    expr: NonNegatedELTLExpression,
277    appears_in_conj: bool,
278) -> Result<ErrorTarget, ExtractionError> {
279    match expr {
280        NonNegatedELTLExpression::Globally(expr) => {
281            if !appears_in_conj {
282                return Err(ExtractionError::NestedTemporalOperator);
283            }
284
285            extract_from_inner_globally_helper(*expr, true)
286        }
287
288        NonNegatedELTLExpression::Eventually(expr) => {
289            let tgts = ErrorTarget::extract_from_inner_eventually(*expr)?;
290            let inv = tgts
291                .into_iter()
292                // `<>(a || b)` is equal to `<>(a) || <>(b)`
293                .try_fold(UpwardsClosedSet::new_bot(), |acc, tgt| match tgt {
294                    Reach(set) => Ok(acc | set),
295                    _ => Err(NestedTemporalOperator),
296                })?;
297
298            Ok(Repeat {
299                pre: UpwardsClosedSet::new_top(),
300                inv,
301            })
302        }
303        NonNegatedELTLExpression::And(lhs, rhs) => {
304            let lhs = extract_from_inner_globally_helper(*lhs, appears_in_conj)?;
305            let rhs = extract_from_inner_globally_helper(*rhs, appears_in_conj)?;
306
307            lhs.try_unify_and(&rhs)
308                .ok_or(ExtractionError::UnsupportedConjunction(lhs, rhs))
309        }
310        NonNegatedELTLExpression::Or(lhs, rhs) => {
311            let lhs = extract_from_inner_globally_helper(*lhs, false)?;
312            let rhs = extract_from_inner_globally_helper(*rhs, false)?;
313
314            or_inside_globally(lhs, rhs)
315        }
316        // The remaining cases are atoms
317        s => Ok(Invariant(extract_atom_helper(s)?)),
318    }
319}
320
321/// Disjunction of two targets that are the operands of one `||` inside an
322/// outer `[]`
323///
324/// `[]` does not distribute over `||`. Because of this, the operands are merged
325/// below the `[]` into a single target.
326fn or_inside_globally(l: ErrorTarget, r: ErrorTarget) -> Result<ErrorTarget, ExtractionError> {
327    match (l, r) {
328        (Invariant(a), Invariant(b)) => Ok(Invariant(a | b)),
329        // `(p && <>(a)) || (p && <>(b))` is equal to `p && <>(a || b)`
330        (
331            Repeat {
332                pre: pre_l,
333                inv: inv_l,
334            },
335            Repeat {
336                pre: pre_r,
337                inv: inv_r,
338            },
339        ) if pre_l == pre_r => Ok(Repeat {
340            pre: pre_l,
341            inv: inv_l | inv_r,
342        }),
343        // `a || <>(b)` and `(p && <>(a)) || (q && <>(b))` have no single target
344        (_, _) => Err(NestedTemporalOperator),
345    }
346}
347
348/// Helper function to and constraints inside an eventually specification
349///
350/// Note that this function does only compute correct results if no nested <>
351/// appeared while parsing the inner constraints.
352/// This is necessary, because otherwise would need distributivity, which does
353/// not hold in the and case for <>
354fn and_inside_eventually(l: ErrorTarget, r: ErrorTarget) -> ErrorTarget {
355    match (l, r) {
356        (Top, o) | (o, Top) => o,
357        (Reach(r1), Reach(r2)) => Reach(r1 & r2),
358        (Reach(r), Ensure { pre, inv }) | (Ensure { pre, inv }, Reach(r)) => {
359            Ensure { pre: pre & r, inv }
360        }
361        (
362            Ensure {
363                pre: pre1,
364                inv: inv1,
365            },
366            Ensure {
367                pre: pre2,
368                inv: inv2,
369            },
370        ) => Ensure {
371            pre: pre1 & pre2,
372            inv: inv1 & inv2,
373        },
374        (_, _) => unreachable!("No other target should have been parsed"),
375    }
376}
377
378/// Upwards closed set of a single atom
379fn extract_atom_helper(
380    expr: NonNegatedELTLExpression,
381) -> Result<UpwardsClosedSet, ExtractionError> {
382    match expr {
383        NonNegatedELTLExpression::LocationExpr(lhs, op, rhs) => {
384            Ok(UpwardsClosedSet::from_integer_expr(*lhs, op, *rhs)?)
385        }
386        NonNegatedELTLExpression::VariableExpr(lhs, op, rhs) => {
387            let var = LIAVariableConstraint::from_integer_expr(*lhs, op, *rhs)?;
388
389            Ok(UpwardsClosedSet::new_var_constraint(var))
390        }
391        NonNegatedELTLExpression::True => Ok(UpwardsClosedSet::new_top()),
392        NonNegatedELTLExpression::False => Ok(UpwardsClosedSet::new_bot()),
393        NonNegatedELTLExpression::ParameterExpr(_, _, _) => unreachable!(
394            "This case should have been filtered out when validating the specification"
395        ),
396
397        NonNegatedELTLExpression::And(_, _)
398        | NonNegatedELTLExpression::Or(_, _)
399        | NonNegatedELTLExpression::Globally(_)
400        | NonNegatedELTLExpression::Eventually(_) => {
401            unreachable!("This helper should only be used for atoms")
402        }
403    }
404}
405
406/// Helper function for combining two disjunctions into one
407///
408/// This function will try to unify as many disjuncts of `lhs` with disjuncts
409/// of `rhs` as possible, using `try_unify`
410fn unify_all_possible<T: PartialEq>(
411    mut lhs: Disjunction<T>,
412    mut rhs: Disjunction<T>,
413    try_unify: impl Fn(&T, &T) -> Option<T>,
414) -> Disjunction<T> {
415    let mut lhs_idx = 0;
416    let mut rhs_idx = 0;
417
418    while lhs_idx < lhs.len() && rhs_idx < rhs.len() {
419        if let Some(unified) = try_unify(&lhs[lhs_idx], &rhs[rhs_idx]) {
420            lhs.0.remove(lhs_idx);
421            rhs.0.remove(rhs_idx);
422
423            lhs.0.push(unified);
424            rhs_idx = 0;
425            continue;
426        }
427
428        if rhs_idx + 1 < rhs.len() {
429            rhs_idx += 1;
430            continue;
431        }
432
433        lhs_idx += 1;
434        rhs_idx = 0;
435    }
436
437    lhs.into_iter().chain(rhs).collect::<Vec<_>>().into()
438}
439
440#[derive(Debug, Clone, PartialEq)]
441pub enum ExtractionError {
442    NestedTemporalOperator,
443    /// Two targets of one `&&` that have no single target, e.g. `<>(a) && <>(b)`
444    UnsupportedConjunction(ErrorTarget, ErrorTarget),
445    LocationConstraintNotUpwardsClosed(UpwardsClosedSetExtractionError),
446    VariableExprNonLinear(ConstraintRewriteError),
447}
448
449impl fmt::Display for ExtractionError {
450    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
451        match self {
452            NestedTemporalOperator => write!(f, "Temporal Operator alternation"),
453            ExtractionError::UnsupportedConjunction(a, b) => {
454                write!(f, "Unsupported Conjunction between'{a}' and {b}")
455            }
456            ExtractionError::LocationConstraintNotUpwardsClosed(err) => {
457                write!(f, "Unsupported Specification: {err}")
458            }
459            ExtractionError::VariableExprNonLinear(err) => {
460                write!(f, "Unsupported Specification: {err}")
461            }
462        }
463    }
464}
465
466impl error::Error for ExtractionError {}
467
468impl From<UpwardsClosedSetExtractionError> for ExtractionError {
469    fn from(value: UpwardsClosedSetExtractionError) -> Self {
470        Self::LocationConstraintNotUpwardsClosed(value)
471    }
472}
473
474impl From<ConstraintRewriteError> for ExtractionError {
475    fn from(value: ConstraintRewriteError) -> Self {
476        Self::VariableExprNonLinear(value)
477    }
478}
479
480#[cfg(test)]
481mod test {
482    use taco_threshold_automaton::{
483        expressions::{
484            BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
485        },
486        lia_threshold_automaton::{
487            LIAVariableConstraint, integer_thresholds::DeriveFromIntegerComp,
488        },
489    };
490
491    use crate::{
492        eltl::ELTLExpression,
493        internal_spec::{
494            ErrorAtom, ErrorFormula, ErrorSpec, ErrorTarget, InitRestriction,
495            extraction::ExtractionError, upwards_closed_set::UpwardsClosedSet,
496        },
497    };
498
499    #[test]
500    fn simple_ensure() {
501        // [](loc0 > 1 && loc1 = 0 || var = 1)
502        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
503            Box::new(ELTLExpression::And(
504                Box::new(ELTLExpression::LocationExpr(
505                    Box::new(IntegerExpression::Atom(Location::new("loc0"))),
506                    ComparisonOp::Gt,
507                    Box::new(IntegerExpression::Const(1)),
508                )),
509                Box::new(ELTLExpression::LocationExpr(
510                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
511                    ComparisonOp::Eq,
512                    Box::new(IntegerExpression::Const(0)),
513                )),
514            )),
515            Box::new(ELTLExpression::VariableExpr(
516                Box::new(IntegerExpression::Atom(Variable::new("var"))),
517                ComparisonOp::Eq,
518                Box::new(IntegerExpression::Const(1)),
519            )),
520        )));
521
522        let got = ErrorSpec::from_named_eltl("test", eltl.clone());
523
524        // `[](loc0 > 1 && loc1 = 0 || var = 1)` is an ensure target with the
525        // upwards closed set `(loc0 > 1 && loc1 = 0) || var = 1`.
526        let loc0_gt_1 = UpwardsClosedSet::from_integer_expr(
527            IntegerExpression::Atom(Location::new("loc0")),
528            ComparisonOp::Gt,
529            IntegerExpression::Const(1),
530        )
531        .unwrap();
532        let loc1_eq_0 = UpwardsClosedSet::from_integer_expr(
533            IntegerExpression::Atom(Location::new("loc1")),
534            ComparisonOp::Eq,
535            IntegerExpression::Const(0),
536        )
537        .unwrap();
538        let var_eq_1 = LIAVariableConstraint::from_integer_expr(
539            IntegerExpression::Atom(Variable::new("var")),
540            ComparisonOp::Eq,
541            IntegerExpression::Const(1),
542        )
543        .unwrap();
544        let inv = (loc0_gt_1 & loc1_eq_0) | UpwardsClosedSet::new_var_constraint(var_eq_1);
545
546        let expected = ErrorSpec {
547            name: "test".to_string(),
548            source: Box::new(eltl.clone()),
549            ef: ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Invariant(inv)),
550        };
551
552        assert!(got.is_ok(), "Error:{}", got.unwrap_err());
553        let got = got.unwrap();
554
555        assert_eq!(got, expected, "Expected:{expected}\nGot:{got}");
556    }
557
558    #[test]
559    fn simple_ensure_distributivity() {
560        // [](loc0 > 1 && [](loc1 = 0))
561        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::And(
562            Box::new(ELTLExpression::LocationExpr(
563                Box::new(IntegerExpression::Atom(Location::new("loc0"))),
564                ComparisonOp::Gt,
565                Box::new(IntegerExpression::Const(1)),
566            )),
567            Box::new(ELTLExpression::Globally(Box::new(
568                ELTLExpression::LocationExpr(
569                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
570                    ComparisonOp::Eq,
571                    Box::new(IntegerExpression::Const(0)),
572                ),
573            ))),
574        )));
575
576        let got = ErrorSpec::from_named_eltl("test", eltl.clone());
577
578        // `loc0 > 1 && loc1 = 0`.
579        let loc0_gt_1 = UpwardsClosedSet::from_integer_expr(
580            IntegerExpression::Atom(Location::new("loc0")),
581            ComparisonOp::Gt,
582            IntegerExpression::Const(1),
583        )
584        .unwrap();
585        let loc1_eq_0 = UpwardsClosedSet::from_integer_expr(
586            IntegerExpression::Atom(Location::new("loc1")),
587            ComparisonOp::Eq,
588            IntegerExpression::Const(0),
589        )
590        .unwrap();
591
592        let inv = loc0_gt_1 & loc1_eq_0;
593
594        let expected = ErrorSpec {
595            name: "test".to_string(),
596            source: Box::new(eltl.clone()),
597            ef: ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Invariant(inv)),
598        };
599
600        assert!(got.is_ok(), "Error:{}", got.unwrap_err());
601        let got = got.unwrap();
602
603        assert_eq!(got, expected, "Expected:{expected}\nGot:{got}");
604    }
605
606    #[test]
607    fn simple_ensure_not_distributive() {
608        // [](loc0 > 1 || [](loc1 = 0))
609        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
610            Box::new(ELTLExpression::LocationExpr(
611                Box::new(IntegerExpression::Atom(Location::new("loc0"))),
612                ComparisonOp::Gt,
613                Box::new(IntegerExpression::Const(1)),
614            )),
615            Box::new(ELTLExpression::Globally(Box::new(
616                ELTLExpression::LocationExpr(
617                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
618                    ComparisonOp::Eq,
619                    Box::new(IntegerExpression::Const(0)),
620                ),
621            ))),
622        )));
623
624        let got = ErrorSpec::from_named_eltl("test", eltl.clone());
625
626        assert!(
627            !got.is_ok(),
628            "Unexpectedly extracted specification:{}",
629            got.unwrap()
630        );
631        let got = got.unwrap_err();
632
633        let expected = ExtractionError::NestedTemporalOperator;
634
635        assert_eq!(got, expected, "Expected:{expected}\nGot:{got}");
636    }
637
638    #[test]
639    fn simple_ensure_with_init_constr() {
640        // loc0 > 1 && [](loc1 = 0)
641        let ltl = !ELTLExpression::And(
642            Box::new(ELTLExpression::LocationExpr(
643                Box::new(IntegerExpression::Atom(Location::new("loc0"))),
644                ComparisonOp::Gt,
645                Box::new(IntegerExpression::Const(1)),
646            )),
647            Box::new(ELTLExpression::Globally(Box::new(
648                ELTLExpression::LocationExpr(
649                    Box::new(IntegerExpression::Atom(Location::new("loc1"))),
650                    ComparisonOp::Eq,
651                    Box::new(IntegerExpression::Const(0)),
652                ),
653            ))),
654        );
655
656        let got = ErrorSpec::from_named_eltl("test", ltl.clone());
657
658        // The atom `loc0 > 1` restricts the initial configuration, the
659        // `[](loc1 = 0)` part becomes an ensure target.
660        let init =
661            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
662                Box::new(IntegerExpression::Atom(Location::new("loc0"))),
663                ComparisonOp::Gt,
664                Box::new(IntegerExpression::Const(1)),
665            ));
666        let inv = UpwardsClosedSet::from_integer_expr(
667            IntegerExpression::Atom(Location::new("loc1")),
668            ComparisonOp::Eq,
669            IntegerExpression::Const(0),
670        )
671        .unwrap();
672
673        let expected = ErrorSpec {
674            name: "test".to_string(),
675            source: Box::new(ltl.clone()),
676            ef: ErrorFormula::new(init, ErrorTarget::Invariant(inv)),
677        };
678
679        assert!(got.is_ok(), "Error:{}", got.unwrap_err());
680        let got = got.unwrap();
681
682        assert_eq!(got, expected, "Expected:{expected}\nGot:{got}");
683    }
684
685    #[test]
686    fn simple_ensure_with_init_constr_two() {
687        // (loc3 = 0 || loc2 = 0) && loc0 > 1 && [](loc1 = 0)
688        let ltl = !ELTLExpression::And(
689            Box::new(ELTLExpression::Or(
690                Box::new(ELTLExpression::LocationExpr(
691                    Box::new(IntegerExpression::Atom(Location::new("loc3"))),
692                    ComparisonOp::Eq,
693                    Box::new(IntegerExpression::Const(0)),
694                )),
695                Box::new(ELTLExpression::LocationExpr(
696                    Box::new(IntegerExpression::Atom(Location::new("loc2"))),
697                    ComparisonOp::Eq,
698                    Box::new(IntegerExpression::Const(0)),
699                )),
700            )),
701            Box::new(ELTLExpression::And(
702                Box::new(ELTLExpression::LocationExpr(
703                    Box::new(IntegerExpression::Atom(Location::new("loc0"))),
704                    ComparisonOp::Gt,
705                    Box::new(IntegerExpression::Const(1)),
706                )),
707                Box::new(ELTLExpression::Globally(Box::new(
708                    ELTLExpression::LocationExpr(
709                        Box::new(IntegerExpression::Atom(Location::new("loc1"))),
710                        ComparisonOp::Eq,
711                        Box::new(IntegerExpression::Const(0)),
712                    ),
713                ))),
714            )),
715        );
716
717        let got = ErrorSpec::from_named_eltl("test", ltl.clone());
718
719        let loc3_eq_0 = BooleanExpression::ComparisonExpression(
720            Box::new(IntegerExpression::Atom(Location::new("loc3"))),
721            ComparisonOp::Eq,
722            Box::new(IntegerExpression::Const(0)),
723        );
724
725        let loc2_eq_0 = BooleanExpression::ComparisonExpression(
726            Box::new(IntegerExpression::Atom(Location::new("loc2"))),
727            ComparisonOp::Eq,
728            Box::new(IntegerExpression::Const(0)),
729        );
730        let loc0_gt_1 = BooleanExpression::ComparisonExpression(
731            Box::new(IntegerExpression::Atom(Location::new("loc0"))),
732            ComparisonOp::Gt,
733            Box::new(IntegerExpression::Const(1)),
734        );
735        let init = InitRestriction::new_location_constraint(loc3_eq_0 | loc2_eq_0)
736            & InitRestriction::new_location_constraint(loc0_gt_1);
737        let inv = UpwardsClosedSet::from_integer_expr(
738            IntegerExpression::Atom(Location::new("loc1")),
739            ComparisonOp::Eq,
740            IntegerExpression::Const(0),
741        )
742        .unwrap();
743
744        let expected = ErrorSpec {
745            name: "test".to_string(),
746            source: Box::new(ltl.clone()),
747            ef: ErrorFormula::new(init, ErrorTarget::Invariant(inv)),
748        };
749
750        assert!(got.is_ok(), "Error:{}", got.unwrap_err());
751        let got = got.unwrap();
752
753        assert_eq!(got, expected, "Expected:{}\nGot:{}", expected.ef, got.ef);
754    }
755
756    #[test]
757    fn reachability_true() {
758        // true -> false -> empty disjunction
759        let eltl = !ELTLExpression::Not(Box::new(ELTLExpression::True));
760
761        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
762
763        assert_eq!(got, ErrorFormula::new_bot());
764    }
765
766    #[test]
767    fn reachability_false() {
768        // false -> true -> every run
769        let eltl = ELTLExpression::False;
770
771        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
772
773        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Top);
774        assert_eq!(got, expected);
775    }
776
777    #[test]
778    fn reachability_parameter_atom() {
779        // n == 3 -> n != 3
780        let eltl = ELTLExpression::ParameterExpr(
781            Box::new(IntegerExpression::Param(Parameter::new("n"))),
782            ComparisonOp::Eq,
783            Box::new(IntegerExpression::Const(3)),
784        );
785
786        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
787
788        let init =
789            InitRestriction::new_parameter_constraint(BooleanExpression::ComparisonExpression(
790                Box::new(IntegerExpression::Param(Parameter::new("n"))),
791                ComparisonOp::Neq,
792                Box::new(IntegerExpression::Const(3)),
793            ));
794        let expected = ErrorFormula::new(init, ErrorTarget::Top);
795        assert_eq!(got, expected);
796    }
797
798    #[test]
799    fn reachability_variable_atom() {
800        // v < n -> v >= n
801        let eltl = ELTLExpression::VariableExpr(
802            Box::new(IntegerExpression::Atom(Variable::new("v"))),
803            ComparisonOp::Lt,
804            Box::new(IntegerExpression::Param(Parameter::new("n"))),
805        );
806
807        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
808
809        let init =
810            InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
811                Box::new(IntegerExpression::Atom(Variable::new("v"))),
812                ComparisonOp::Geq,
813                Box::new(IntegerExpression::Param(Parameter::new("n"))),
814            ));
815        let expected = ErrorFormula::new(init, ErrorTarget::Top);
816        assert_eq!(got, expected);
817    }
818
819    #[test]
820    fn reachability_location_atom() {
821        // l < n -> l >= n
822        let eltl = ELTLExpression::LocationExpr(
823            Box::new(IntegerExpression::Atom(Location::new("l"))),
824            ComparisonOp::Lt,
825            Box::new(IntegerExpression::Param(Parameter::new("n"))),
826        );
827
828        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
829
830        let init =
831            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
832                Box::new(IntegerExpression::Atom(Location::new("l"))),
833                ComparisonOp::Geq,
834                Box::new(IntegerExpression::Param(Parameter::new("n"))),
835            ));
836        let expected = ErrorFormula::new(init, ErrorTarget::Top);
837        assert_eq!(got, expected);
838    }
839
840    #[test]
841    fn reachability_and_of_atoms() {
842        // (l < 1) && (v < 3) -> (l >= 1) || (v >= 3)
843        let eltl = ELTLExpression::And(
844            Box::new(ELTLExpression::LocationExpr(
845                Box::new(IntegerExpression::Atom(Location::new("l"))),
846                ComparisonOp::Lt,
847                Box::new(IntegerExpression::Const(1)),
848            )),
849            Box::new(ELTLExpression::VariableExpr(
850                Box::new(IntegerExpression::Atom(Variable::new("v"))),
851                ComparisonOp::Lt,
852                Box::new(IntegerExpression::Const(3)),
853            )),
854        );
855
856        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
857
858        // The two restrictions differ in two kinds of atoms. Because of this,
859        // they stay two atoms.
860        let init_l =
861            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
862                Box::new(IntegerExpression::Atom(Location::new("l"))),
863                ComparisonOp::Geq,
864                Box::new(IntegerExpression::Const(1)),
865            ));
866        let init_v =
867            InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
868                Box::new(IntegerExpression::Atom(Variable::new("v"))),
869                ComparisonOp::Geq,
870                Box::new(IntegerExpression::Const(3)),
871            ));
872        let expected = ErrorFormula::from([
873            ErrorAtom::new(init_l, ErrorTarget::Top),
874            ErrorAtom::new(init_v, ErrorTarget::Top),
875        ]);
876        assert_eq!(got, expected);
877    }
878
879    #[test]
880    fn reachability_or_of_atoms() {
881        // (l < 1) || (v < 3) -> (l >= 1) && (v >= 3)
882
883        let eltl = ELTLExpression::Or(
884            Box::new(ELTLExpression::LocationExpr(
885                Box::new(IntegerExpression::Atom(Location::new("l"))),
886                ComparisonOp::Lt,
887                Box::new(IntegerExpression::Const(1)),
888            )),
889            Box::new(ELTLExpression::VariableExpr(
890                Box::new(IntegerExpression::Atom(Variable::new("v"))),
891                ComparisonOp::Lt,
892                Box::new(IntegerExpression::Const(3)),
893            )),
894        );
895
896        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
897
898        let init =
899            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
900                Box::new(IntegerExpression::Atom(Location::new("l"))),
901                ComparisonOp::Geq,
902                Box::new(IntegerExpression::Const(1)),
903            )) & InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
904                Box::new(IntegerExpression::Atom(Variable::new("v"))),
905                ComparisonOp::Geq,
906                Box::new(IntegerExpression::Const(3)),
907            ));
908        let expected = ErrorFormula::new(init, ErrorTarget::Top);
909        assert_eq!(got, expected);
910    }
911
912    #[test]
913    fn reachability_negated_and_of_atoms() {
914        // !((l < 1) && (v < 3)) -> (l < 1) && (v < 3)
915        let eltl = ELTLExpression::Not(Box::new(ELTLExpression::And(
916            Box::new(ELTLExpression::LocationExpr(
917                Box::new(IntegerExpression::Atom(Location::new("l"))),
918                ComparisonOp::Lt,
919                Box::new(IntegerExpression::Const(1)),
920            )),
921            Box::new(ELTLExpression::VariableExpr(
922                Box::new(IntegerExpression::Atom(Variable::new("v"))),
923                ComparisonOp::Lt,
924                Box::new(IntegerExpression::Const(3)),
925            )),
926        )));
927
928        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
929
930        let init =
931            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
932                Box::new(IntegerExpression::Atom(Location::new("l"))),
933                ComparisonOp::Lt,
934                Box::new(IntegerExpression::Const(1)),
935            )) & InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
936                Box::new(IntegerExpression::Atom(Variable::new("v"))),
937                ComparisonOp::Lt,
938                Box::new(IntegerExpression::Const(3)),
939            ));
940        let expected = ErrorFormula::new(init, ErrorTarget::Top);
941        assert_eq!(got, expected);
942    }
943
944    #[test]
945    fn reachability_globally_true() {
946        // [](true) -> <>(false)
947        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::True));
948
949        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
950
951        let expected = ErrorFormula::new(
952            InitRestriction::new_top(),
953            ErrorTarget::Reach(UpwardsClosedSet::new_bot()),
954        );
955        assert_eq!(got, expected);
956    }
957
958    #[test]
959    fn reachability_globally_false() {
960        // [](false) -> <>(true)
961        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::False));
962
963        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
964
965        let expected = ErrorFormula::new(
966            InitRestriction::new_top(),
967            ErrorTarget::Reach(UpwardsClosedSet::new_top()),
968        );
969        assert_eq!(got, expected);
970    }
971
972    #[test]
973    fn reachability_globally_variable() {
974        // [](v < n) -> <>(v >= n)
975        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::VariableExpr(
976            Box::new(IntegerExpression::Atom(Variable::new("v"))),
977            ComparisonOp::Lt,
978            Box::new(IntegerExpression::Param(Parameter::new("n"))),
979        )));
980
981        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
982
983        let target = UpwardsClosedSet::new_var_constraint(
984            LIAVariableConstraint::from_integer_expr(
985                IntegerExpression::Atom(Variable::new("v")),
986                ComparisonOp::Geq,
987                IntegerExpression::Param(Parameter::new("n")),
988            )
989            .unwrap(),
990        );
991        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Reach(target));
992        assert_eq!(got, expected);
993    }
994
995    #[test]
996    fn reachability_globally_location() {
997        // [](l == 0) -> <>(l != 0)
998        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::LocationExpr(
999            Box::new(IntegerExpression::Atom(Location::new("l"))),
1000            ComparisonOp::Eq,
1001            Box::new(IntegerExpression::Const(0)),
1002        )));
1003
1004        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1005
1006        let target = UpwardsClosedSet::new_cover([Location::new("l")]);
1007        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Reach(target));
1008        assert_eq!(got, expected);
1009    }
1010
1011    #[test]
1012    fn reachability_globally_or() {
1013        // [](l1 == 0 || l2 != 0) -> <>(l1 != 0 && l2 == 0)
1014        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
1015            Box::new(ELTLExpression::LocationExpr(
1016                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1017                ComparisonOp::Eq,
1018                Box::new(IntegerExpression::Const(0)),
1019            )),
1020            Box::new(ELTLExpression::LocationExpr(
1021                Box::new(IntegerExpression::Atom(Location::new("l2"))),
1022                ComparisonOp::Neq,
1023                Box::new(IntegerExpression::Const(0)),
1024            )),
1025        )));
1026
1027        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1028
1029        let target = UpwardsClosedSet::new_reach([(Location::new("l1"), 1)], [Location::new("l2")]);
1030        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Reach(target));
1031        assert_eq!(got, expected);
1032    }
1033
1034    #[test]
1035    fn reachability_globally_and() {
1036        // [](l1 == 0 && l2 != 0) -> <>(l1 != 0 || l2 == 0)
1037        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::And(
1038            Box::new(ELTLExpression::LocationExpr(
1039                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1040                ComparisonOp::Eq,
1041                Box::new(IntegerExpression::Const(0)),
1042            )),
1043            Box::new(ELTLExpression::LocationExpr(
1044                Box::new(IntegerExpression::Atom(Location::new("l2"))),
1045                ComparisonOp::Neq,
1046                Box::new(IntegerExpression::Const(0)),
1047            )),
1048        )));
1049
1050        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1051
1052        // `<>(a) || <>(b)` is equal to `<>(a || b)`, so this is one atom
1053        let target = UpwardsClosedSet::new_cover([Location::new("l1")])
1054            | UpwardsClosedSet::new_reach([], [Location::new("l2")]);
1055        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Reach(target));
1056        assert_eq!(got, expected);
1057    }
1058
1059    #[test]
1060    fn reachability_nested_globally() {
1061        // []([](l1 == 0 || l2 != 0)) -> <>(<>(l1 != 0 && l2 == 0))
1062        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Globally(Box::new(
1063            ELTLExpression::Or(
1064                Box::new(ELTLExpression::LocationExpr(
1065                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1066                    ComparisonOp::Eq,
1067                    Box::new(IntegerExpression::Const(0)),
1068                )),
1069                Box::new(ELTLExpression::LocationExpr(
1070                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
1071                    ComparisonOp::Neq,
1072                    Box::new(IntegerExpression::Const(0)),
1073                )),
1074            ),
1075        ))));
1076
1077        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1078
1079        let target = UpwardsClosedSet::new_reach([(Location::new("l1"), 1)], [Location::new("l2")]);
1080        let expected = ErrorFormula::new(InitRestriction::new_top(), ErrorTarget::Reach(target));
1081        assert_eq!(got, expected);
1082    }
1083
1084    #[test]
1085    fn reachability_globally_or_nested_globally() {
1086        // [](l1 == 0 || [](l2 != 0)) -> <>(l1 != 0 && <>(l2 == 0))
1087        //
1088        // The old extraction returned `<>(l1 != 0 && l2 == 0)`, which is not
1089        // equal to the error formula. `<>` does not distribute over `&&`, so
1090        // the new extraction rejects the nested `<>`.
1091        let eltl = ELTLExpression::Globally(Box::new(ELTLExpression::Or(
1092            Box::new(ELTLExpression::LocationExpr(
1093                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1094                ComparisonOp::Eq,
1095                Box::new(IntegerExpression::Const(0)),
1096            )),
1097            Box::new(ELTLExpression::Globally(Box::new(
1098                ELTLExpression::LocationExpr(
1099                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
1100                    ComparisonOp::Neq,
1101                    Box::new(IntegerExpression::Const(0)),
1102                ),
1103            ))),
1104        )));
1105
1106        let got = ErrorSpec::from_named_eltl("test", eltl);
1107
1108        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
1109    }
1110
1111    #[test]
1112    fn reachability_implies_globally_or_nested_globally() {
1113        // (v == 0) => [](l1 == 0 || [](l2 != 0))
1114        //   -> v == 0 && <>(l1 != 0 && <>(l2 == 0))
1115        //
1116        // --> Reject because of missing distributivity
1117        let eltl = ELTLExpression::Implies(
1118            Box::new(ELTLExpression::VariableExpr(
1119                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1120                ComparisonOp::Eq,
1121                Box::new(IntegerExpression::Const(0)),
1122            )),
1123            Box::new(ELTLExpression::Globally(Box::new(ELTLExpression::Or(
1124                Box::new(ELTLExpression::LocationExpr(
1125                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1126                    ComparisonOp::Eq,
1127                    Box::new(IntegerExpression::Const(0)),
1128                )),
1129                Box::new(ELTLExpression::Globally(Box::new(
1130                    ELTLExpression::LocationExpr(
1131                        Box::new(IntegerExpression::Atom(Location::new("l2"))),
1132                        ComparisonOp::Neq,
1133                        Box::new(IntegerExpression::Const(0)),
1134                    ),
1135                ))),
1136            )))),
1137        );
1138
1139        let got = ErrorSpec::from_named_eltl("test", eltl);
1140
1141        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
1142    }
1143
1144    #[test]
1145    fn reachability_or_negated_and_globally() {
1146        // !(l1 == 0 && l2 == 0) || [](l2 == 0)
1147        //   -> (l1 == 0 && l2 == 0) && <>(l2 != 0)
1148        let eltl = ELTLExpression::Or(
1149            Box::new(ELTLExpression::Not(Box::new(ELTLExpression::And(
1150                Box::new(ELTLExpression::LocationExpr(
1151                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1152                    ComparisonOp::Eq,
1153                    Box::new(IntegerExpression::Const(0)),
1154                )),
1155                Box::new(ELTLExpression::LocationExpr(
1156                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
1157                    ComparisonOp::Eq,
1158                    Box::new(IntegerExpression::Const(0)),
1159                )),
1160            )))),
1161            Box::new(ELTLExpression::Globally(Box::new(
1162                ELTLExpression::LocationExpr(
1163                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
1164                    ComparisonOp::Eq,
1165                    Box::new(IntegerExpression::Const(0)),
1166                ),
1167            ))),
1168        );
1169
1170        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1171
1172        let init =
1173            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
1174                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1175                ComparisonOp::Eq,
1176                Box::new(IntegerExpression::Const(0)),
1177            )) & InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
1178                Box::new(IntegerExpression::Atom(Location::new("l2"))),
1179                ComparisonOp::Eq,
1180                Box::new(IntegerExpression::Const(0)),
1181            ));
1182        let target = UpwardsClosedSet::new_cover([Location::new("l2")]);
1183        let expected = ErrorFormula::new(init, ErrorTarget::Reach(target));
1184        assert_eq!(got, expected);
1185    }
1186
1187    // The `ensure_*` and `repeat_*` tests take the expression as the error
1188    // formula, i.e., it is not negated.
1189
1190    #[test]
1191    fn ensure_true_and_false() {
1192        // [](true)
1193        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::True));
1194
1195        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1196
1197        let expected = ErrorFormula::new(
1198            InitRestriction::new_top(),
1199            ErrorTarget::Invariant(UpwardsClosedSet::new_top()),
1200        );
1201        assert_eq!(got, expected);
1202
1203        // [](false)
1204        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::False));
1205
1206        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1207
1208        let expected = ErrorFormula::new(
1209            InitRestriction::new_top(),
1210            ErrorTarget::Invariant(UpwardsClosedSet::new_bot()),
1211        );
1212        assert_eq!(got, expected);
1213    }
1214
1215    #[test]
1216    fn ensure_and_of_globally() {
1217        // [](l0 > 1) && [](l1 = 0) is equal to [](l0 > 1 && l1 = 0)
1218        let eltl = !ELTLExpression::And(
1219            Box::new(ELTLExpression::Globally(Box::new(
1220                ELTLExpression::LocationExpr(
1221                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1222                    ComparisonOp::Gt,
1223                    Box::new(IntegerExpression::Const(1)),
1224                ),
1225            ))),
1226            Box::new(ELTLExpression::Globally(Box::new(
1227                ELTLExpression::LocationExpr(
1228                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1229                    ComparisonOp::Eq,
1230                    Box::new(IntegerExpression::Const(0)),
1231                ),
1232            ))),
1233        );
1234
1235        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1236
1237        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1238            IntegerExpression::Atom(Location::new("l0")),
1239            ComparisonOp::Gt,
1240            IntegerExpression::Const(1),
1241        )
1242        .unwrap();
1243        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1244            IntegerExpression::Atom(Location::new("l1")),
1245            ComparisonOp::Eq,
1246            IntegerExpression::Const(0),
1247        )
1248        .unwrap();
1249        let expected = ErrorFormula::new(
1250            InitRestriction::new_top(),
1251            ErrorTarget::Invariant(l0_gt_1 & l1_eq_0),
1252        );
1253        assert_eq!(got, expected);
1254    }
1255
1256    #[test]
1257    fn ensure_or_of_globally() {
1258        // [](l0 > 1) || [](l1 = 0)
1259        let eltl = !ELTLExpression::Or(
1260            Box::new(ELTLExpression::Globally(Box::new(
1261                ELTLExpression::LocationExpr(
1262                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1263                    ComparisonOp::Gt,
1264                    Box::new(IntegerExpression::Const(1)),
1265                ),
1266            ))),
1267            Box::new(ELTLExpression::Globally(Box::new(
1268                ELTLExpression::LocationExpr(
1269                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1270                    ComparisonOp::Eq,
1271                    Box::new(IntegerExpression::Const(0)),
1272                ),
1273            ))),
1274        );
1275
1276        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1277
1278        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1279            IntegerExpression::Atom(Location::new("l0")),
1280            ComparisonOp::Gt,
1281            IntegerExpression::Const(1),
1282        )
1283        .unwrap();
1284        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1285            IntegerExpression::Atom(Location::new("l1")),
1286            ComparisonOp::Eq,
1287            IntegerExpression::Const(0),
1288        )
1289        .unwrap();
1290        let expected = ErrorFormula::from([
1291            ErrorAtom::new(InitRestriction::new_top(), ErrorTarget::Invariant(l0_gt_1)),
1292            ErrorAtom::new(InitRestriction::new_top(), ErrorTarget::Invariant(l1_eq_0)),
1293        ]);
1294        assert_eq!(got, expected);
1295    }
1296
1297    #[test]
1298    fn ensure_or_with_init_atom() {
1299        // [](l0 > 1) || v = 1
1300        let eltl = !ELTLExpression::Or(
1301            Box::new(ELTLExpression::Globally(Box::new(
1302                ELTLExpression::LocationExpr(
1303                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1304                    ComparisonOp::Gt,
1305                    Box::new(IntegerExpression::Const(1)),
1306                ),
1307            ))),
1308            Box::new(ELTLExpression::VariableExpr(
1309                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1310                ComparisonOp::Eq,
1311                Box::new(IntegerExpression::Const(1)),
1312            )),
1313        );
1314
1315        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1316
1317        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1318            IntegerExpression::Atom(Location::new("l0")),
1319            ComparisonOp::Gt,
1320            IntegerExpression::Const(1),
1321        )
1322        .unwrap();
1323        let init_v =
1324            InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
1325                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1326                ComparisonOp::Eq,
1327                Box::new(IntegerExpression::Const(1)),
1328            ));
1329        let expected = ErrorFormula::from([
1330            ErrorAtom::new(InitRestriction::new_top(), ErrorTarget::Invariant(l0_gt_1)),
1331            ErrorAtom::new(init_v, ErrorTarget::Top),
1332        ]);
1333        assert_eq!(got, expected);
1334    }
1335
1336    #[test]
1337    fn ensure_with_init_or_of_two_kinds() {
1338        // (l0 > 1 || v = 1) && [](l1 = 0): the init restrictions differ in two
1339        // kinds of atoms, so the disjunction is split into two atoms
1340        let eltl = !ELTLExpression::And(
1341            Box::new(ELTLExpression::Or(
1342                Box::new(ELTLExpression::LocationExpr(
1343                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1344                    ComparisonOp::Gt,
1345                    Box::new(IntegerExpression::Const(1)),
1346                )),
1347                Box::new(ELTLExpression::VariableExpr(
1348                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
1349                    ComparisonOp::Eq,
1350                    Box::new(IntegerExpression::Const(1)),
1351                )),
1352            )),
1353            Box::new(ELTLExpression::Globally(Box::new(
1354                ELTLExpression::LocationExpr(
1355                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1356                    ComparisonOp::Eq,
1357                    Box::new(IntegerExpression::Const(0)),
1358                ),
1359            ))),
1360        );
1361
1362        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1363
1364        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1365            IntegerExpression::Atom(Location::new("l1")),
1366            ComparisonOp::Eq,
1367            IntegerExpression::Const(0),
1368        )
1369        .unwrap();
1370        let init_l0 =
1371            InitRestriction::new_location_constraint(BooleanExpression::ComparisonExpression(
1372                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1373                ComparisonOp::Gt,
1374                Box::new(IntegerExpression::Const(1)),
1375            ));
1376        let init_v =
1377            InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
1378                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1379                ComparisonOp::Eq,
1380                Box::new(IntegerExpression::Const(1)),
1381            ));
1382        let expected = ErrorFormula::from([
1383            ErrorAtom::new(init_l0, ErrorTarget::Invariant(l1_eq_0.clone())),
1384            ErrorAtom::new(init_v, ErrorTarget::Invariant(l1_eq_0)),
1385        ]);
1386        assert_eq!(got, expected);
1387    }
1388
1389    #[test]
1390    fn ensure_or_below_and_with_nested_globally() {
1391        // []((l0 > 1 || l1 = 0) && [](v = 1)) is equal to
1392        // []((l0 > 1 || l1 = 0) && v = 1)
1393        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::And(
1394            Box::new(ELTLExpression::Or(
1395                Box::new(ELTLExpression::LocationExpr(
1396                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1397                    ComparisonOp::Gt,
1398                    Box::new(IntegerExpression::Const(1)),
1399                )),
1400                Box::new(ELTLExpression::LocationExpr(
1401                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1402                    ComparisonOp::Eq,
1403                    Box::new(IntegerExpression::Const(0)),
1404                )),
1405            )),
1406            Box::new(ELTLExpression::Globally(Box::new(
1407                ELTLExpression::VariableExpr(
1408                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
1409                    ComparisonOp::Eq,
1410                    Box::new(IntegerExpression::Const(1)),
1411                ),
1412            ))),
1413        )));
1414
1415        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1416
1417        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1418            IntegerExpression::Atom(Location::new("l0")),
1419            ComparisonOp::Gt,
1420            IntegerExpression::Const(1),
1421        )
1422        .unwrap();
1423        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1424            IntegerExpression::Atom(Location::new("l1")),
1425            ComparisonOp::Eq,
1426            IntegerExpression::Const(0),
1427        )
1428        .unwrap();
1429        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1430            LIAVariableConstraint::from_integer_expr(
1431                IntegerExpression::Atom(Variable::new("v")),
1432                ComparisonOp::Eq,
1433                IntegerExpression::Const(1),
1434            )
1435            .unwrap(),
1436        );
1437        let expected = ErrorFormula::new(
1438            InitRestriction::new_top(),
1439            ErrorTarget::Invariant((l0_gt_1 | l1_eq_0) & v_eq_1),
1440        );
1441        assert_eq!(got, expected);
1442    }
1443
1444    #[test]
1445    fn ensure_and_below_or_with_nested_globally() {
1446        // [](l0 > 1 || (l1 = 0 && [](v = 1))) has no single set
1447        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
1448            Box::new(ELTLExpression::LocationExpr(
1449                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1450                ComparisonOp::Gt,
1451                Box::new(IntegerExpression::Const(1)),
1452            )),
1453            Box::new(ELTLExpression::And(
1454                Box::new(ELTLExpression::LocationExpr(
1455                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1456                    ComparisonOp::Eq,
1457                    Box::new(IntegerExpression::Const(0)),
1458                )),
1459                Box::new(ELTLExpression::Globally(Box::new(
1460                    ELTLExpression::VariableExpr(
1461                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1462                        ComparisonOp::Eq,
1463                        Box::new(IntegerExpression::Const(1)),
1464                    ),
1465                ))),
1466            )),
1467        )));
1468
1469        let got = ErrorSpec::from_named_eltl("test", eltl);
1470
1471        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
1472    }
1473
1474    #[test]
1475    fn ensure_and_reach_unsupported() {
1476        // [](l0 > 1) && <>(l1 = 0) has no single target
1477        let eltl = !ELTLExpression::And(
1478            Box::new(ELTLExpression::Globally(Box::new(
1479                ELTLExpression::LocationExpr(
1480                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1481                    ComparisonOp::Gt,
1482                    Box::new(IntegerExpression::Const(1)),
1483                ),
1484            ))),
1485            Box::new(ELTLExpression::Eventually(Box::new(
1486                ELTLExpression::LocationExpr(
1487                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1488                    ComparisonOp::Eq,
1489                    Box::new(IntegerExpression::Const(0)),
1490                ),
1491            ))),
1492        );
1493
1494        let got = ErrorSpec::from_named_eltl("test", eltl);
1495
1496        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1497            IntegerExpression::Atom(Location::new("l0")),
1498            ComparisonOp::Gt,
1499            IntegerExpression::Const(1),
1500        )
1501        .unwrap();
1502        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1503            IntegerExpression::Atom(Location::new("l1")),
1504            ComparisonOp::Eq,
1505            IntegerExpression::Const(0),
1506        )
1507        .unwrap();
1508        let expected = ExtractionError::UnsupportedConjunction(
1509            ErrorTarget::Invariant(l0_gt_1),
1510            ErrorTarget::Reach(l1_eq_0),
1511        );
1512        assert_eq!(got, Err(expected));
1513    }
1514
1515    #[test]
1516    fn repeat_simple() {
1517        // <>(l0 > 1 && [](l1 = 0))
1518        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1519            Box::new(ELTLExpression::LocationExpr(
1520                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1521                ComparisonOp::Gt,
1522                Box::new(IntegerExpression::Const(1)),
1523            )),
1524            Box::new(ELTLExpression::Globally(Box::new(
1525                ELTLExpression::LocationExpr(
1526                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1527                    ComparisonOp::Eq,
1528                    Box::new(IntegerExpression::Const(0)),
1529                ),
1530            ))),
1531        )));
1532
1533        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1534
1535        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1536            IntegerExpression::Atom(Location::new("l0")),
1537            ComparisonOp::Gt,
1538            IntegerExpression::Const(1),
1539        )
1540        .unwrap();
1541        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1542            IntegerExpression::Atom(Location::new("l1")),
1543            ComparisonOp::Eq,
1544            IntegerExpression::Const(0),
1545        )
1546        .unwrap();
1547        let expected = ErrorFormula::new(
1548            InitRestriction::new_top(),
1549            ErrorTarget::new_ensure(l0_gt_1, l1_eq_0),
1550        );
1551        assert_eq!(got, expected);
1552    }
1553
1554    #[test]
1555    fn repeat_globally_only() {
1556        // <>([](l1 = 0)) has the precondition `true`
1557        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::Globally(Box::new(
1558            ELTLExpression::LocationExpr(
1559                Box::new(IntegerExpression::Atom(Location::new("l1"))),
1560                ComparisonOp::Eq,
1561                Box::new(IntegerExpression::Const(0)),
1562            ),
1563        ))));
1564
1565        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1566
1567        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1568            IntegerExpression::Atom(Location::new("l1")),
1569            ComparisonOp::Eq,
1570            IntegerExpression::Const(0),
1571        )
1572        .unwrap();
1573        let expected = ErrorFormula::new(
1574            InitRestriction::new_top(),
1575            ErrorTarget::new_ensure(UpwardsClosedSet::new_top(), l1_eq_0),
1576        );
1577        assert_eq!(got, expected);
1578    }
1579
1580    #[test]
1581    fn repeat_with_init() {
1582        // v = 1 && <>(l0 > 1 && [](l1 = 0))
1583        let eltl = !ELTLExpression::And(
1584            Box::new(ELTLExpression::VariableExpr(
1585                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1586                ComparisonOp::Eq,
1587                Box::new(IntegerExpression::Const(1)),
1588            )),
1589            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1590                Box::new(ELTLExpression::LocationExpr(
1591                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1592                    ComparisonOp::Gt,
1593                    Box::new(IntegerExpression::Const(1)),
1594                )),
1595                Box::new(ELTLExpression::Globally(Box::new(
1596                    ELTLExpression::LocationExpr(
1597                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
1598                        ComparisonOp::Eq,
1599                        Box::new(IntegerExpression::Const(0)),
1600                    ),
1601                ))),
1602            )))),
1603        );
1604
1605        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1606
1607        let init =
1608            InitRestriction::new_variable_constraint(BooleanExpression::ComparisonExpression(
1609                Box::new(IntegerExpression::Atom(Variable::new("v"))),
1610                ComparisonOp::Eq,
1611                Box::new(IntegerExpression::Const(1)),
1612            ));
1613        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1614            IntegerExpression::Atom(Location::new("l0")),
1615            ComparisonOp::Gt,
1616            IntegerExpression::Const(1),
1617        )
1618        .unwrap();
1619        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1620            IntegerExpression::Atom(Location::new("l1")),
1621            ComparisonOp::Eq,
1622            IntegerExpression::Const(0),
1623        )
1624        .unwrap();
1625        let expected = ErrorFormula::new(init, ErrorTarget::new_ensure(l0_gt_1, l1_eq_0));
1626        assert_eq!(got, expected);
1627    }
1628
1629    #[test]
1630    fn repeat_and_of_two_globally() {
1631        // <>(l0 > 1 && [](l1 = 0) && [](v = 1)) is equal to
1632        // <>(l0 > 1 && [](l1 = 0 && v = 1))
1633        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1634            Box::new(ELTLExpression::LocationExpr(
1635                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1636                ComparisonOp::Gt,
1637                Box::new(IntegerExpression::Const(1)),
1638            )),
1639            Box::new(ELTLExpression::And(
1640                Box::new(ELTLExpression::Globally(Box::new(
1641                    ELTLExpression::LocationExpr(
1642                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
1643                        ComparisonOp::Eq,
1644                        Box::new(IntegerExpression::Const(0)),
1645                    ),
1646                ))),
1647                Box::new(ELTLExpression::Globally(Box::new(
1648                    ELTLExpression::VariableExpr(
1649                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1650                        ComparisonOp::Eq,
1651                        Box::new(IntegerExpression::Const(1)),
1652                    ),
1653                ))),
1654            )),
1655        )));
1656
1657        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1658
1659        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1660            IntegerExpression::Atom(Location::new("l0")),
1661            ComparisonOp::Gt,
1662            IntegerExpression::Const(1),
1663        )
1664        .unwrap();
1665        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1666            IntegerExpression::Atom(Location::new("l1")),
1667            ComparisonOp::Eq,
1668            IntegerExpression::Const(0),
1669        )
1670        .unwrap();
1671        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1672            LIAVariableConstraint::from_integer_expr(
1673                IntegerExpression::Atom(Variable::new("v")),
1674                ComparisonOp::Eq,
1675                IntegerExpression::Const(1),
1676            )
1677            .unwrap(),
1678        );
1679        let expected = ErrorFormula::new(
1680            InitRestriction::new_top(),
1681            ErrorTarget::new_ensure(l0_gt_1, l1_eq_0 & v_eq_1),
1682        );
1683        assert_eq!(got, expected);
1684    }
1685
1686    #[test]
1687    fn repeat_nested_globally_in_globally() {
1688        // <>(l0 > 1 && [](l1 = 0 && [](v = 1))) is equal to
1689        // <>(l0 > 1 && [](l1 = 0 && v = 1))
1690        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1691            Box::new(ELTLExpression::LocationExpr(
1692                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1693                ComparisonOp::Gt,
1694                Box::new(IntegerExpression::Const(1)),
1695            )),
1696            Box::new(ELTLExpression::Globally(Box::new(ELTLExpression::And(
1697                Box::new(ELTLExpression::LocationExpr(
1698                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1699                    ComparisonOp::Eq,
1700                    Box::new(IntegerExpression::Const(0)),
1701                )),
1702                Box::new(ELTLExpression::Globally(Box::new(
1703                    ELTLExpression::VariableExpr(
1704                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1705                        ComparisonOp::Eq,
1706                        Box::new(IntegerExpression::Const(1)),
1707                    ),
1708                ))),
1709            )))),
1710        )));
1711
1712        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1713
1714        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1715            IntegerExpression::Atom(Location::new("l0")),
1716            ComparisonOp::Gt,
1717            IntegerExpression::Const(1),
1718        )
1719        .unwrap();
1720        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1721            IntegerExpression::Atom(Location::new("l1")),
1722            ComparisonOp::Eq,
1723            IntegerExpression::Const(0),
1724        )
1725        .unwrap();
1726        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1727            LIAVariableConstraint::from_integer_expr(
1728                IntegerExpression::Atom(Variable::new("v")),
1729                ComparisonOp::Eq,
1730                IntegerExpression::Const(1),
1731            )
1732            .unwrap(),
1733        );
1734        let expected = ErrorFormula::new(
1735            InitRestriction::new_top(),
1736            ErrorTarget::new_ensure(l0_gt_1, l1_eq_0 & v_eq_1),
1737        );
1738        assert_eq!(got, expected);
1739    }
1740
1741    #[test]
1742    fn repeat_or_of_same_invariant() {
1743        // <>((l0 > 1 && [](v = 1)) || (l1 = 0 && [](v = 1))) is equal to
1744        // <>((l0 > 1 || l1 = 0) && [](v = 1))
1745        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::Or(
1746            Box::new(ELTLExpression::And(
1747                Box::new(ELTLExpression::LocationExpr(
1748                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1749                    ComparisonOp::Gt,
1750                    Box::new(IntegerExpression::Const(1)),
1751                )),
1752                Box::new(ELTLExpression::Globally(Box::new(
1753                    ELTLExpression::VariableExpr(
1754                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1755                        ComparisonOp::Eq,
1756                        Box::new(IntegerExpression::Const(1)),
1757                    ),
1758                ))),
1759            )),
1760            Box::new(ELTLExpression::And(
1761                Box::new(ELTLExpression::LocationExpr(
1762                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1763                    ComparisonOp::Eq,
1764                    Box::new(IntegerExpression::Const(0)),
1765                )),
1766                Box::new(ELTLExpression::Globally(Box::new(
1767                    ELTLExpression::VariableExpr(
1768                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1769                        ComparisonOp::Eq,
1770                        Box::new(IntegerExpression::Const(1)),
1771                    ),
1772                ))),
1773            )),
1774        )));
1775
1776        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1777
1778        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1779            IntegerExpression::Atom(Location::new("l0")),
1780            ComparisonOp::Gt,
1781            IntegerExpression::Const(1),
1782        )
1783        .unwrap();
1784        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1785            IntegerExpression::Atom(Location::new("l1")),
1786            ComparisonOp::Eq,
1787            IntegerExpression::Const(0),
1788        )
1789        .unwrap();
1790        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1791            LIAVariableConstraint::from_integer_expr(
1792                IntegerExpression::Atom(Variable::new("v")),
1793                ComparisonOp::Eq,
1794                IntegerExpression::Const(1),
1795            )
1796            .unwrap(),
1797        );
1798        let expected = ErrorFormula::new(
1799            InitRestriction::new_top(),
1800            ErrorTarget::new_ensure(l0_gt_1 | l1_eq_0, v_eq_1),
1801        );
1802        assert_eq!(got, expected);
1803    }
1804
1805    #[test]
1806    fn repeat_or_of_different_invariants() {
1807        // <>([](l0 > 1) || [](l1 = 0)) stays two atoms
1808        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::Or(
1809            Box::new(ELTLExpression::Globally(Box::new(
1810                ELTLExpression::LocationExpr(
1811                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
1812                    ComparisonOp::Gt,
1813                    Box::new(IntegerExpression::Const(1)),
1814                ),
1815            ))),
1816            Box::new(ELTLExpression::Globally(Box::new(
1817                ELTLExpression::LocationExpr(
1818                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1819                    ComparisonOp::Eq,
1820                    Box::new(IntegerExpression::Const(0)),
1821                ),
1822            ))),
1823        )));
1824
1825        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1826
1827        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1828            IntegerExpression::Atom(Location::new("l0")),
1829            ComparisonOp::Gt,
1830            IntegerExpression::Const(1),
1831        )
1832        .unwrap();
1833        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1834            IntegerExpression::Atom(Location::new("l1")),
1835            ComparisonOp::Eq,
1836            IntegerExpression::Const(0),
1837        )
1838        .unwrap();
1839        let expected = ErrorFormula::from([
1840            ErrorAtom::new(
1841                InitRestriction::new_top(),
1842                ErrorTarget::new_ensure(UpwardsClosedSet::new_top(), l0_gt_1),
1843            ),
1844            ErrorAtom::new(
1845                InitRestriction::new_top(),
1846                ErrorTarget::new_ensure(UpwardsClosedSet::new_top(), l1_eq_0),
1847            ),
1848        ]);
1849        assert_eq!(got, expected);
1850    }
1851
1852    #[test]
1853    fn repeat_and_distributes_over_or() {
1854        // <>(l0 > 1 && (l1 = 0 || [](v = 1))) is equal to
1855        // <>(l0 > 1 && l1 = 0) || <>(l0 > 1 && [](v = 1))
1856        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1857            Box::new(ELTLExpression::LocationExpr(
1858                Box::new(IntegerExpression::Atom(Location::new("l0"))),
1859                ComparisonOp::Gt,
1860                Box::new(IntegerExpression::Const(1)),
1861            )),
1862            Box::new(ELTLExpression::Or(
1863                Box::new(ELTLExpression::LocationExpr(
1864                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1865                    ComparisonOp::Eq,
1866                    Box::new(IntegerExpression::Const(0)),
1867                )),
1868                Box::new(ELTLExpression::Globally(Box::new(
1869                    ELTLExpression::VariableExpr(
1870                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1871                        ComparisonOp::Eq,
1872                        Box::new(IntegerExpression::Const(1)),
1873                    ),
1874                ))),
1875            )),
1876        )));
1877
1878        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1879
1880        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1881            IntegerExpression::Atom(Location::new("l0")),
1882            ComparisonOp::Gt,
1883            IntegerExpression::Const(1),
1884        )
1885        .unwrap();
1886        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1887            IntegerExpression::Atom(Location::new("l1")),
1888            ComparisonOp::Eq,
1889            IntegerExpression::Const(0),
1890        )
1891        .unwrap();
1892        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1893            LIAVariableConstraint::from_integer_expr(
1894                IntegerExpression::Atom(Variable::new("v")),
1895                ComparisonOp::Eq,
1896                IntegerExpression::Const(1),
1897            )
1898            .unwrap(),
1899        );
1900        let expected = ErrorFormula::from([
1901            ErrorAtom::new(
1902                InitRestriction::new_top(),
1903                ErrorTarget::Reach(l0_gt_1.clone() & l1_eq_0),
1904            ),
1905            ErrorAtom::new(
1906                InitRestriction::new_top(),
1907                ErrorTarget::new_ensure(l0_gt_1, v_eq_1),
1908            ),
1909        ]);
1910        assert_eq!(got, expected);
1911    }
1912
1913    #[test]
1914    fn repeat_product_of_two_ors() {
1915        // <>(([](l0 > 1) || l1 = 0) && ([](v = 1) || l2 > 0))
1916        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
1917            Box::new(ELTLExpression::Or(
1918                Box::new(ELTLExpression::Globally(Box::new(
1919                    ELTLExpression::LocationExpr(
1920                        Box::new(IntegerExpression::Atom(Location::new("l0"))),
1921                        ComparisonOp::Gt,
1922                        Box::new(IntegerExpression::Const(1)),
1923                    ),
1924                ))),
1925                Box::new(ELTLExpression::LocationExpr(
1926                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
1927                    ComparisonOp::Eq,
1928                    Box::new(IntegerExpression::Const(0)),
1929                )),
1930            )),
1931            Box::new(ELTLExpression::Or(
1932                Box::new(ELTLExpression::Globally(Box::new(
1933                    ELTLExpression::VariableExpr(
1934                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
1935                        ComparisonOp::Eq,
1936                        Box::new(IntegerExpression::Const(1)),
1937                    ),
1938                ))),
1939                Box::new(ELTLExpression::LocationExpr(
1940                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
1941                    ComparisonOp::Gt,
1942                    Box::new(IntegerExpression::Const(0)),
1943                )),
1944            )),
1945        )));
1946
1947        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
1948
1949        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
1950            IntegerExpression::Atom(Location::new("l0")),
1951            ComparisonOp::Gt,
1952            IntegerExpression::Const(1),
1953        )
1954        .unwrap();
1955        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
1956            IntegerExpression::Atom(Location::new("l1")),
1957            ComparisonOp::Eq,
1958            IntegerExpression::Const(0),
1959        )
1960        .unwrap();
1961        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
1962            LIAVariableConstraint::from_integer_expr(
1963                IntegerExpression::Atom(Variable::new("v")),
1964                ComparisonOp::Eq,
1965                IntegerExpression::Const(1),
1966            )
1967            .unwrap(),
1968        );
1969        let l2_gt_0 = UpwardsClosedSet::from_integer_expr(
1970            IntegerExpression::Atom(Location::new("l2")),
1971            ComparisonOp::Gt,
1972            IntegerExpression::Const(0),
1973        )
1974        .unwrap();
1975        let expected = ErrorFormula::from([
1976            // [](l0 > 1) && [](v = 1)
1977            ErrorAtom::new(
1978                InitRestriction::new_top(),
1979                ErrorTarget::new_ensure(
1980                    UpwardsClosedSet::new_top(),
1981                    l0_gt_1.clone() & v_eq_1.clone(),
1982                ),
1983            ),
1984            // [](l0 > 1) && l2 > 0
1985            ErrorAtom::new(
1986                InitRestriction::new_top(),
1987                ErrorTarget::new_ensure(l2_gt_0.clone(), l0_gt_1),
1988            ),
1989            // l1 = 0 && [](v = 1)
1990            ErrorAtom::new(
1991                InitRestriction::new_top(),
1992                ErrorTarget::new_ensure(l1_eq_0.clone(), v_eq_1),
1993            ),
1994            // l1 = 0 && l2 > 0
1995            ErrorAtom::new(
1996                InitRestriction::new_top(),
1997                ErrorTarget::Reach(l1_eq_0 & l2_gt_0),
1998            ),
1999        ]);
2000        assert_eq!(got, expected);
2001    }
2002
2003    #[test]
2004    fn repeat_nested_eventually_in_or() {
2005        // <>(l0 > 1 || <>(l1 = 0 && [](v = 1))) is equal to
2006        // <>(l0 > 1) || <>(l1 = 0 && [](v = 1))
2007        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::Or(
2008            Box::new(ELTLExpression::LocationExpr(
2009                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2010                ComparisonOp::Gt,
2011                Box::new(IntegerExpression::Const(1)),
2012            )),
2013            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2014                Box::new(ELTLExpression::LocationExpr(
2015                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2016                    ComparisonOp::Eq,
2017                    Box::new(IntegerExpression::Const(0)),
2018                )),
2019                Box::new(ELTLExpression::Globally(Box::new(
2020                    ELTLExpression::VariableExpr(
2021                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2022                        ComparisonOp::Eq,
2023                        Box::new(IntegerExpression::Const(1)),
2024                    ),
2025                ))),
2026            )))),
2027        )));
2028
2029        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2030
2031        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2032            IntegerExpression::Atom(Location::new("l0")),
2033            ComparisonOp::Gt,
2034            IntegerExpression::Const(1),
2035        )
2036        .unwrap();
2037        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2038            IntegerExpression::Atom(Location::new("l1")),
2039            ComparisonOp::Eq,
2040            IntegerExpression::Const(0),
2041        )
2042        .unwrap();
2043        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
2044            LIAVariableConstraint::from_integer_expr(
2045                IntegerExpression::Atom(Variable::new("v")),
2046                ComparisonOp::Eq,
2047                IntegerExpression::Const(1),
2048            )
2049            .unwrap(),
2050        );
2051        let expected = ErrorFormula::from([
2052            ErrorAtom::new(InitRestriction::new_top(), ErrorTarget::Reach(l0_gt_1)),
2053            ErrorAtom::new(
2054                InitRestriction::new_top(),
2055                ErrorTarget::new_ensure(l1_eq_0, v_eq_1),
2056            ),
2057        ]);
2058        assert_eq!(got, expected);
2059    }
2060
2061    #[test]
2062    fn repeat_nested_eventually_in_and() {
2063        // <>(l0 > 1 && <>([](l1 = 0))) is not a repeat target
2064        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2065            Box::new(ELTLExpression::LocationExpr(
2066                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2067                ComparisonOp::Gt,
2068                Box::new(IntegerExpression::Const(1)),
2069            )),
2070            Box::new(ELTLExpression::Eventually(Box::new(
2071                ELTLExpression::Globally(Box::new(ELTLExpression::LocationExpr(
2072                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2073                    ComparisonOp::Eq,
2074                    Box::new(IntegerExpression::Const(0)),
2075                ))),
2076            ))),
2077        )));
2078
2079        let got = ErrorSpec::from_named_eltl("test", eltl);
2080
2081        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2082    }
2083
2084    #[test]
2085    fn repeat_or_below_globally() {
2086        // <>(l0 > 1 && [](l1 = 0 || [](v = 1))) has no single invariant
2087        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2088            Box::new(ELTLExpression::LocationExpr(
2089                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2090                ComparisonOp::Gt,
2091                Box::new(IntegerExpression::Const(1)),
2092            )),
2093            Box::new(ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2094                Box::new(ELTLExpression::LocationExpr(
2095                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2096                    ComparisonOp::Eq,
2097                    Box::new(IntegerExpression::Const(0)),
2098                )),
2099                Box::new(ELTLExpression::Globally(Box::new(
2100                    ELTLExpression::VariableExpr(
2101                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2102                        ComparisonOp::Eq,
2103                        Box::new(IntegerExpression::Const(1)),
2104                    ),
2105                ))),
2106            )))),
2107        )));
2108
2109        let got = ErrorSpec::from_named_eltl("test", eltl);
2110
2111        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2112    }
2113
2114    #[test]
2115    fn repeat_globally_eventually() {
2116        // <>([](<>(l0 > 1))) is not supported
2117        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::Globally(Box::new(
2118            ELTLExpression::Eventually(Box::new(ELTLExpression::LocationExpr(
2119                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2120                ComparisonOp::Gt,
2121                Box::new(IntegerExpression::Const(1)),
2122            ))),
2123        ))));
2124
2125        let got = ErrorSpec::from_named_eltl("test", eltl);
2126
2127        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2128    }
2129
2130    #[test]
2131    fn repeat_and_repeat_unsupported() {
2132        // <>(l0 > 1 && [](l1 = 0)) && <>(v = 1 && [](l2 > 0)) has no single
2133        // target
2134        let eltl = !ELTLExpression::And(
2135            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2136                Box::new(ELTLExpression::LocationExpr(
2137                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2138                    ComparisonOp::Gt,
2139                    Box::new(IntegerExpression::Const(1)),
2140                )),
2141                Box::new(ELTLExpression::Globally(Box::new(
2142                    ELTLExpression::LocationExpr(
2143                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
2144                        ComparisonOp::Eq,
2145                        Box::new(IntegerExpression::Const(0)),
2146                    ),
2147                ))),
2148            )))),
2149            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2150                Box::new(ELTLExpression::VariableExpr(
2151                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2152                    ComparisonOp::Eq,
2153                    Box::new(IntegerExpression::Const(1)),
2154                )),
2155                Box::new(ELTLExpression::Globally(Box::new(
2156                    ELTLExpression::LocationExpr(
2157                        Box::new(IntegerExpression::Atom(Location::new("l2"))),
2158                        ComparisonOp::Gt,
2159                        Box::new(IntegerExpression::Const(0)),
2160                    ),
2161                ))),
2162            )))),
2163        );
2164
2165        let got = ErrorSpec::from_named_eltl("test", eltl);
2166
2167        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2168            IntegerExpression::Atom(Location::new("l0")),
2169            ComparisonOp::Gt,
2170            IntegerExpression::Const(1),
2171        )
2172        .unwrap();
2173        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2174            IntegerExpression::Atom(Location::new("l1")),
2175            ComparisonOp::Eq,
2176            IntegerExpression::Const(0),
2177        )
2178        .unwrap();
2179        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
2180            LIAVariableConstraint::from_integer_expr(
2181                IntegerExpression::Atom(Variable::new("v")),
2182                ComparisonOp::Eq,
2183                IntegerExpression::Const(1),
2184            )
2185            .unwrap(),
2186        );
2187        let l2_gt_0 = UpwardsClosedSet::from_integer_expr(
2188            IntegerExpression::Atom(Location::new("l2")),
2189            ComparisonOp::Gt,
2190            IntegerExpression::Const(0),
2191        )
2192        .unwrap();
2193        let expected = ExtractionError::UnsupportedConjunction(
2194            ErrorTarget::new_ensure(l0_gt_1, l1_eq_0),
2195            ErrorTarget::new_ensure(v_eq_1, l2_gt_0),
2196        );
2197        assert_eq!(got, Err(expected));
2198    }
2199
2200    #[test]
2201    fn repeat_and_ensure_unsupported() {
2202        // [](l0 > 1) && <>(l1 = 0 && [](v = 1)) has no single target
2203        let eltl = !ELTLExpression::And(
2204            Box::new(ELTLExpression::Globally(Box::new(
2205                ELTLExpression::LocationExpr(
2206                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2207                    ComparisonOp::Gt,
2208                    Box::new(IntegerExpression::Const(1)),
2209                ),
2210            ))),
2211            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2212                Box::new(ELTLExpression::LocationExpr(
2213                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2214                    ComparisonOp::Eq,
2215                    Box::new(IntegerExpression::Const(0)),
2216                )),
2217                Box::new(ELTLExpression::Globally(Box::new(
2218                    ELTLExpression::VariableExpr(
2219                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2220                        ComparisonOp::Eq,
2221                        Box::new(IntegerExpression::Const(1)),
2222                    ),
2223                ))),
2224            )))),
2225        );
2226
2227        let got = ErrorSpec::from_named_eltl("test", eltl);
2228
2229        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2230            IntegerExpression::Atom(Location::new("l0")),
2231            ComparisonOp::Gt,
2232            IntegerExpression::Const(1),
2233        )
2234        .unwrap();
2235        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2236            IntegerExpression::Atom(Location::new("l1")),
2237            ComparisonOp::Eq,
2238            IntegerExpression::Const(0),
2239        )
2240        .unwrap();
2241        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
2242            LIAVariableConstraint::from_integer_expr(
2243                IntegerExpression::Atom(Variable::new("v")),
2244                ComparisonOp::Eq,
2245                IntegerExpression::Const(1),
2246            )
2247            .unwrap(),
2248        );
2249        let expected = ExtractionError::UnsupportedConjunction(
2250            ErrorTarget::Invariant(l0_gt_1),
2251            ErrorTarget::new_ensure(l1_eq_0, v_eq_1),
2252        );
2253        assert_eq!(got, Err(expected));
2254    }
2255
2256    #[test]
2257    fn repeat_or_ensure() {
2258        // [](l0 > 1) || <>(l1 = 0 && [](v = 1)) stays two atoms
2259        let eltl = !ELTLExpression::Or(
2260            Box::new(ELTLExpression::Globally(Box::new(
2261                ELTLExpression::LocationExpr(
2262                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2263                    ComparisonOp::Gt,
2264                    Box::new(IntegerExpression::Const(1)),
2265                ),
2266            ))),
2267            Box::new(ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2268                Box::new(ELTLExpression::LocationExpr(
2269                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2270                    ComparisonOp::Eq,
2271                    Box::new(IntegerExpression::Const(0)),
2272                )),
2273                Box::new(ELTLExpression::Globally(Box::new(
2274                    ELTLExpression::VariableExpr(
2275                        Box::new(IntegerExpression::Atom(Variable::new("v"))),
2276                        ComparisonOp::Eq,
2277                        Box::new(IntegerExpression::Const(1)),
2278                    ),
2279                ))),
2280            )))),
2281        );
2282
2283        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2284
2285        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2286            IntegerExpression::Atom(Location::new("l0")),
2287            ComparisonOp::Gt,
2288            IntegerExpression::Const(1),
2289        )
2290        .unwrap();
2291        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2292            IntegerExpression::Atom(Location::new("l1")),
2293            ComparisonOp::Eq,
2294            IntegerExpression::Const(0),
2295        )
2296        .unwrap();
2297        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
2298            LIAVariableConstraint::from_integer_expr(
2299                IntegerExpression::Atom(Variable::new("v")),
2300                ComparisonOp::Eq,
2301                IntegerExpression::Const(1),
2302            )
2303            .unwrap(),
2304        );
2305        let expected = ErrorFormula::from([
2306            ErrorAtom::new(InitRestriction::new_top(), ErrorTarget::Invariant(l0_gt_1)),
2307            ErrorAtom::new(
2308                InitRestriction::new_top(),
2309                ErrorTarget::new_ensure(l1_eq_0, v_eq_1),
2310            ),
2311        ]);
2312        assert_eq!(got, expected);
2313    }
2314
2315    #[test]
2316    fn globally_eventually_simple() {
2317        // [](l0 > 1 && <>(l1 = 0))
2318        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::And(
2319            Box::new(ELTLExpression::LocationExpr(
2320                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2321                ComparisonOp::Gt,
2322                Box::new(IntegerExpression::Const(1)),
2323            )),
2324            Box::new(ELTLExpression::Eventually(Box::new(
2325                ELTLExpression::LocationExpr(
2326                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2327                    ComparisonOp::Eq,
2328                    Box::new(IntegerExpression::Const(0)),
2329                ),
2330            ))),
2331        )));
2332
2333        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2334
2335        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2336            IntegerExpression::Atom(Location::new("l0")),
2337            ComparisonOp::Gt,
2338            IntegerExpression::Const(1),
2339        )
2340        .unwrap();
2341        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2342            IntegerExpression::Atom(Location::new("l1")),
2343            ComparisonOp::Eq,
2344            IntegerExpression::Const(0),
2345        )
2346        .unwrap();
2347        let expected = ErrorFormula::new(
2348            InitRestriction::new_top(),
2349            ErrorTarget::Repeat {
2350                pre: l0_gt_1,
2351                inv: l1_eq_0,
2352            },
2353        );
2354        assert_eq!(got, expected);
2355    }
2356
2357    #[test]
2358    fn globally_eventually_only() {
2359        // [](<>(l1 = 0)) has the invariant `true`
2360        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Eventually(Box::new(
2361            ELTLExpression::LocationExpr(
2362                Box::new(IntegerExpression::Atom(Location::new("l1"))),
2363                ComparisonOp::Eq,
2364                Box::new(IntegerExpression::Const(0)),
2365            ),
2366        ))));
2367
2368        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2369
2370        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2371            IntegerExpression::Atom(Location::new("l1")),
2372            ComparisonOp::Eq,
2373            IntegerExpression::Const(0),
2374        )
2375        .unwrap();
2376        let expected = ErrorFormula::new(
2377            InitRestriction::new_top(),
2378            ErrorTarget::Repeat {
2379                pre: UpwardsClosedSet::new_top(),
2380                inv: l1_eq_0,
2381            },
2382        );
2383        assert_eq!(got, expected);
2384    }
2385
2386    #[test]
2387    fn globally_eventually_or_of_eventually() {
2388        // [](<>(l0 > 1) || <>(l1 = 0)) is equal to [](<>(l0 > 1 || l1 = 0))
2389        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2390            Box::new(ELTLExpression::Eventually(Box::new(
2391                ELTLExpression::LocationExpr(
2392                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2393                    ComparisonOp::Gt,
2394                    Box::new(IntegerExpression::Const(1)),
2395                ),
2396            ))),
2397            Box::new(ELTLExpression::Eventually(Box::new(
2398                ELTLExpression::LocationExpr(
2399                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2400                    ComparisonOp::Eq,
2401                    Box::new(IntegerExpression::Const(0)),
2402                ),
2403            ))),
2404        )));
2405
2406        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2407
2408        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2409            IntegerExpression::Atom(Location::new("l0")),
2410            ComparisonOp::Gt,
2411            IntegerExpression::Const(1),
2412        )
2413        .unwrap();
2414        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2415            IntegerExpression::Atom(Location::new("l1")),
2416            ComparisonOp::Eq,
2417            IntegerExpression::Const(0),
2418        )
2419        .unwrap();
2420        let expected = ErrorFormula::new(
2421            InitRestriction::new_top(),
2422            ErrorTarget::Repeat {
2423                pre: UpwardsClosedSet::new_top(),
2424                inv: l0_gt_1 | l1_eq_0,
2425            },
2426        );
2427        assert_eq!(got, expected);
2428    }
2429
2430    #[test]
2431    fn globally_eventually_nested_eventually() {
2432        // [](l0 > 1 && <>(<>(l1 = 0))) is equal to [](l0 > 1 && <>(l1 = 0))
2433        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::And(
2434            Box::new(ELTLExpression::LocationExpr(
2435                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2436                ComparisonOp::Gt,
2437                Box::new(IntegerExpression::Const(1)),
2438            )),
2439            Box::new(ELTLExpression::Eventually(Box::new(
2440                ELTLExpression::Eventually(Box::new(ELTLExpression::LocationExpr(
2441                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2442                    ComparisonOp::Eq,
2443                    Box::new(IntegerExpression::Const(0)),
2444                ))),
2445            ))),
2446        )));
2447
2448        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2449
2450        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2451            IntegerExpression::Atom(Location::new("l0")),
2452            ComparisonOp::Gt,
2453            IntegerExpression::Const(1),
2454        )
2455        .unwrap();
2456        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2457            IntegerExpression::Atom(Location::new("l1")),
2458            ComparisonOp::Eq,
2459            IntegerExpression::Const(0),
2460        )
2461        .unwrap();
2462        let expected = ErrorFormula::new(
2463            InitRestriction::new_top(),
2464            ErrorTarget::Repeat {
2465                pre: l0_gt_1,
2466                inv: l1_eq_0,
2467            },
2468        );
2469        assert_eq!(got, expected);
2470    }
2471
2472    #[test]
2473    fn globally_eventually_and_of_globally_top_level() {
2474        // [](l0 > 1) && [](<>(l1 = 0)) is equal to [](l0 > 1 && <>(l1 = 0))
2475        let eltl = !ELTLExpression::And(
2476            Box::new(ELTLExpression::Globally(Box::new(
2477                ELTLExpression::LocationExpr(
2478                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2479                    ComparisonOp::Gt,
2480                    Box::new(IntegerExpression::Const(1)),
2481                ),
2482            ))),
2483            Box::new(ELTLExpression::Globally(Box::new(
2484                ELTLExpression::Eventually(Box::new(ELTLExpression::LocationExpr(
2485                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2486                    ComparisonOp::Eq,
2487                    Box::new(IntegerExpression::Const(0)),
2488                ))),
2489            ))),
2490        );
2491
2492        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2493
2494        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2495            IntegerExpression::Atom(Location::new("l0")),
2496            ComparisonOp::Gt,
2497            IntegerExpression::Const(1),
2498        )
2499        .unwrap();
2500        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2501            IntegerExpression::Atom(Location::new("l1")),
2502            ComparisonOp::Eq,
2503            IntegerExpression::Const(0),
2504        )
2505        .unwrap();
2506        let expected = ErrorFormula::new(
2507            InitRestriction::new_top(),
2508            ErrorTarget::Repeat {
2509                pre: l0_gt_1,
2510                inv: l1_eq_0,
2511            },
2512        );
2513        assert_eq!(got, expected);
2514    }
2515
2516    #[test]
2517    fn globally_eventually_or_of_same_invariant() {
2518        // []((v = 1 && <>(l0 > 1)) || (v = 1 && <>(l1 = 0))) is equal to
2519        // [](v = 1 && <>(l0 > 1 || l1 = 0))
2520        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2521            Box::new(ELTLExpression::And(
2522                Box::new(ELTLExpression::VariableExpr(
2523                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2524                    ComparisonOp::Eq,
2525                    Box::new(IntegerExpression::Const(1)),
2526                )),
2527                Box::new(ELTLExpression::Eventually(Box::new(
2528                    ELTLExpression::LocationExpr(
2529                        Box::new(IntegerExpression::Atom(Location::new("l0"))),
2530                        ComparisonOp::Gt,
2531                        Box::new(IntegerExpression::Const(1)),
2532                    ),
2533                ))),
2534            )),
2535            Box::new(ELTLExpression::And(
2536                Box::new(ELTLExpression::VariableExpr(
2537                    Box::new(IntegerExpression::Atom(Variable::new("v"))),
2538                    ComparisonOp::Eq,
2539                    Box::new(IntegerExpression::Const(1)),
2540                )),
2541                Box::new(ELTLExpression::Eventually(Box::new(
2542                    ELTLExpression::LocationExpr(
2543                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
2544                        ComparisonOp::Eq,
2545                        Box::new(IntegerExpression::Const(0)),
2546                    ),
2547                ))),
2548            )),
2549        )));
2550
2551        let got = ErrorSpec::from_named_eltl("test", eltl).unwrap().ef;
2552
2553        let v_eq_1 = UpwardsClosedSet::new_var_constraint(
2554            LIAVariableConstraint::from_integer_expr(
2555                IntegerExpression::Atom(Variable::new("v")),
2556                ComparisonOp::Eq,
2557                IntegerExpression::Const(1),
2558            )
2559            .unwrap(),
2560        );
2561        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2562            IntegerExpression::Atom(Location::new("l0")),
2563            ComparisonOp::Gt,
2564            IntegerExpression::Const(1),
2565        )
2566        .unwrap();
2567        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2568            IntegerExpression::Atom(Location::new("l1")),
2569            ComparisonOp::Eq,
2570            IntegerExpression::Const(0),
2571        )
2572        .unwrap();
2573        let expected = ErrorFormula::new(
2574            InitRestriction::new_top(),
2575            ErrorTarget::Repeat {
2576                pre: v_eq_1,
2577                inv: l0_gt_1 | l1_eq_0,
2578            },
2579        );
2580        assert_eq!(got, expected);
2581    }
2582
2583    #[test]
2584    fn globally_eventually_two_eventually_unsupported() {
2585        // [](<>(l0 > 1) && <>(l1 = 0)) has no single target
2586        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::And(
2587            Box::new(ELTLExpression::Eventually(Box::new(
2588                ELTLExpression::LocationExpr(
2589                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2590                    ComparisonOp::Gt,
2591                    Box::new(IntegerExpression::Const(1)),
2592                ),
2593            ))),
2594            Box::new(ELTLExpression::Eventually(Box::new(
2595                ELTLExpression::LocationExpr(
2596                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2597                    ComparisonOp::Eq,
2598                    Box::new(IntegerExpression::Const(0)),
2599                ),
2600            ))),
2601        )));
2602
2603        let got = ErrorSpec::from_named_eltl("test", eltl);
2604
2605        let l0_gt_1 = UpwardsClosedSet::from_integer_expr(
2606            IntegerExpression::Atom(Location::new("l0")),
2607            ComparisonOp::Gt,
2608            IntegerExpression::Const(1),
2609        )
2610        .unwrap();
2611        let l1_eq_0 = UpwardsClosedSet::from_integer_expr(
2612            IntegerExpression::Atom(Location::new("l1")),
2613            ComparisonOp::Eq,
2614            IntegerExpression::Const(0),
2615        )
2616        .unwrap();
2617        let expected = ExtractionError::UnsupportedConjunction(
2618            ErrorTarget::Repeat {
2619                pre: UpwardsClosedSet::new_top(),
2620                inv: l0_gt_1,
2621            },
2622            ErrorTarget::Repeat {
2623                pre: UpwardsClosedSet::new_top(),
2624                inv: l1_eq_0,
2625            },
2626        );
2627        assert_eq!(got, Err(expected));
2628    }
2629
2630    #[test]
2631    fn globally_eventually_atom_or_eventually() {
2632        // [](l0 > 1 || <>(l1 = 0)) has no single target
2633        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Or(
2634            Box::new(ELTLExpression::LocationExpr(
2635                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2636                ComparisonOp::Gt,
2637                Box::new(IntegerExpression::Const(1)),
2638            )),
2639            Box::new(ELTLExpression::Eventually(Box::new(
2640                ELTLExpression::LocationExpr(
2641                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2642                    ComparisonOp::Eq,
2643                    Box::new(IntegerExpression::Const(0)),
2644                ),
2645            ))),
2646        )));
2647
2648        let got = ErrorSpec::from_named_eltl("test", eltl);
2649
2650        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2651    }
2652
2653    #[test]
2654    fn globally_eventually_globally_below_eventually() {
2655        // [](<>(l0 > 1 && [](l1 = 0))) has no single target
2656        let eltl = !ELTLExpression::Globally(Box::new(ELTLExpression::Eventually(Box::new(
2657            ELTLExpression::And(
2658                Box::new(ELTLExpression::LocationExpr(
2659                    Box::new(IntegerExpression::Atom(Location::new("l0"))),
2660                    ComparisonOp::Gt,
2661                    Box::new(IntegerExpression::Const(1)),
2662                )),
2663                Box::new(ELTLExpression::Globally(Box::new(
2664                    ELTLExpression::LocationExpr(
2665                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
2666                        ComparisonOp::Eq,
2667                        Box::new(IntegerExpression::Const(0)),
2668                    ),
2669                ))),
2670            ),
2671        ))));
2672
2673        let got = ErrorSpec::from_named_eltl("test", eltl);
2674
2675        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2676    }
2677
2678    #[test]
2679    fn globally_eventually_below_eventually_unsupported() {
2680        // <>(l0 > 1 && [](<>(l1 = 0))) has no single target
2681        let eltl = !ELTLExpression::Eventually(Box::new(ELTLExpression::And(
2682            Box::new(ELTLExpression::LocationExpr(
2683                Box::new(IntegerExpression::Atom(Location::new("l0"))),
2684                ComparisonOp::Gt,
2685                Box::new(IntegerExpression::Const(1)),
2686            )),
2687            Box::new(ELTLExpression::Globally(Box::new(
2688                ELTLExpression::Eventually(Box::new(ELTLExpression::LocationExpr(
2689                    Box::new(IntegerExpression::Atom(Location::new("l1"))),
2690                    ComparisonOp::Eq,
2691                    Box::new(IntegerExpression::Const(0)),
2692                ))),
2693            ))),
2694        )));
2695
2696        let got = ErrorSpec::from_named_eltl("test", eltl);
2697
2698        assert_eq!(got, Err(ExtractionError::NestedTemporalOperator));
2699    }
2700}