taco_zcs_model_checker/
smt_encoder_steady.rs

1//! This module contains the definition of an smt_encoder
2//! which is used to check if a steady error path is spurious or not.
3//! It stores and manages all the smt variables that are constructed for the smt solver.
4
5use std::cmp::min;
6use std::fmt::{Debug, Display};
7use std::rc::Rc;
8use taco_display_utils::join_iterator;
9use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::IntoNoDivBooleanExpr;
10
11use easy_smt::Response::Sat;
12use std::collections::HashMap;
13use taco_interval_ta::IntervalThresholdAutomaton;
14use taco_interval_ta::interval::Interval;
15use taco_model_checker::reachability_specification::DisjunctionTargetConfig;
16use taco_smt_encoder::SMTSolverBuilder;
17use taco_smt_encoder::expression_encoding::DeclaresVariable;
18use taco_smt_encoder::expression_encoding::EncodeToSMT;
19use taco_smt_encoder::expression_encoding::config_ctx::{ConfigCtx, ConfigFromSMT};
20use taco_smt_encoder::expression_encoding::{GetAssignment, SMTSolverError, SMTVariableContext};
21use taco_smt_encoder::{SMTExpr, SMTSolverContext};
22use taco_smt_encoder::{SMTSolution, SMTSolver};
23use taco_threshold_automaton::ActionDefinition;
24use taco_threshold_automaton::RuleDefinition;
25use taco_threshold_automaton::ThresholdAutomaton;
26use taco_threshold_automaton::VariableConstraint;
27use taco_threshold_automaton::expressions::BooleanExpression;
28use taco_threshold_automaton::expressions::{Location, Parameter, Variable};
29use taco_threshold_automaton::general_threshold_automaton::Rule;
30use taco_threshold_automaton::general_threshold_automaton::UpdateExpression;
31use taco_threshold_automaton::general_threshold_automaton::UpdateExpression::*;
32use taco_threshold_automaton::path::PathBuilder;
33use taco_threshold_automaton::path::{Configuration, Path, Transition};
34
35use crate::paths::{SteadyErrorPath, SteadyPath};
36
37/// Type representing an SMT Encoder
38/// which is used to encode a steady error path
39/// and checking it for spuriousness.
40pub struct SMTEncoderSteady<'a> {
41    /// underlying Threshold Automaton
42    ta: &'a IntervalThresholdAutomaton,
43    /// vector of smt variables for each rule counter (counts how often this rule is applied)
44    rule_counter_to_smt: Vec<(Rule, SMTExpr)>,
45    /// indexed parameter context which stores the smt solver and manages all smt variables
46    ctx: IndexedSMTContext,
47    /// underlying steady error path
48    steady_error_path: &'a SteadyErrorPath<'a>,
49}
50impl<'a> SMTEncoderSteady<'a> {
51    /// Creates a new SMTEncoder for a given smt solver `smt`, a given threshold automaton `aut`
52    /// and a `steady_error_path` that is checked for spuriousness.
53    /// It automatically adds assertions for the resilience condition and the initial location and variable constraints to the smt solver.
54    pub fn new(
55        smt: SMTSolverBuilder,
56        aut: &'a IntervalThresholdAutomaton,
57        steady_error_path: &'a SteadyErrorPath<'a>,
58    ) -> Self {
59        // the number of configurations is given by the length of the abstract path
60        let indexed_ctx = IndexedSMTContext::new(
61            smt,
62            // note that the number of configurations has to be multiplied by 2
63            // since we always need a configuration at the beginning and end of a steady path
64            (steady_error_path.length_configurations() * 2)
65                .try_into()
66                .unwrap(),
67            aut,
68        );
69
70        let mut encoder = SMTEncoderSteady {
71            rule_counter_to_smt: Vec::new(),
72            ctx: indexed_ctx,
73            ta: aut,
74            steady_error_path,
75        };
76
77        // initialize all smt variables for rule counters
78        encoder.initialize_rule_counters(steady_error_path.concrete_rules_ordered(aut));
79
80        // encode resilience condition and initial assumptions
81        let rc_cond = encoder.encode_resilience_conditions(aut.resilience_conditions());
82        encoder
83            .ctx
84            .smt_solver
85            .assert(rc_cond)
86            .expect("error for asserting resilience condition");
87
88        let loc_cond = encoder.encode_loc_conditions(aut.initial_location_constraints());
89        encoder
90            .ctx
91            .smt_solver
92            .assert(loc_cond)
93            .expect("error for asserting initial location condition");
94
95        let var_cond = aut
96            .initial_variable_constraints()
97            .map(|constr| constr.as_boolean_expr())
98            .collect::<Vec<_>>();
99        let var_cond = encoder.encode_var_conditions(var_cond.iter());
100        encoder
101            .ctx
102            .smt_solver
103            .assert(var_cond)
104            .expect("error for asserting initial variable condition");
105
106        encoder
107    }
108
109    /// creates and stores an smt variable for every Rule `rule` with its index in the iterator
110    /// which counts how often `rule` is applied
111    fn initialize_rule_counters<'b, I>(&mut self, rules_it: I)
112    where
113        I: Iterator<Item = &'b Rule>,
114    {
115        for (i, rule) in rules_it.enumerate() {
116            let rule_smt = rule
117                .declare_variable(&mut self.ctx.smt_solver, i.try_into().unwrap())
118                .expect("Error creating shared smt variable");
119            self.rule_counter_to_smt.push((rule.clone(), rule_smt));
120        }
121    }
122
123    /// encodes and returns the resilience condition as an smt constraint
124    fn encode_resilience_conditions<'b, I>(&mut self, rc: I) -> SMTExpr
125    where
126        I: Iterator<Item = &'b BooleanExpression<Parameter>>,
127    {
128        self.ctx.set_curr_index(0);
129        let mut rc_encoding = self.ctx.smt_solver.true_();
130
131        for param_constr in rc {
132            let encoding = param_constr
133                .encode_to_smt_with_ctx(&self.ctx.smt_solver, &self.ctx)
134                .expect("error constructing the resilience condition");
135            rc_encoding = self.ctx.smt_solver.and(rc_encoding, encoding);
136        }
137
138        rc_encoding
139    }
140
141    /// encodes and stores the initial location conditions into an smt constraint
142    fn encode_loc_conditions<'b, I>(&mut self, cond: I) -> SMTExpr
143    where
144        I: Iterator<Item = &'b BooleanExpression<Location>>,
145    {
146        self.ctx.set_curr_index(0);
147        let mut initial_loc_encoding = self.ctx.smt_solver.true_();
148
149        for loc_constr in cond {
150            let loc_encoding = loc_constr
151                .encode_to_smt_with_ctx(&self.ctx.smt_solver, self.ctx.get_current_config_ctx())
152                .expect("error constructing the initial location condition");
153            initial_loc_encoding = self.ctx.smt_solver.and(initial_loc_encoding, loc_encoding);
154        }
155
156        initial_loc_encoding
157    }
158
159    /// encodes and stores the initial shared variable conditions into an smt constraint
160    fn encode_var_conditions<'b, I>(&mut self, cond: I) -> SMTExpr
161    where
162        I: Iterator<Item = &'b BooleanExpression<Variable>>,
163    {
164        self.ctx.set_curr_index(0);
165        let mut initial_var_encoding = self.ctx.smt_solver.true_();
166
167        for var_constr in cond {
168            let var_encoding = var_constr
169                .encode_to_smt_with_ctx(&self.ctx.smt_solver, self.ctx.get_current_config_ctx())
170                .expect("error constructing the initial shared variable condition");
171            initial_var_encoding = self.ctx.smt_solver.and(initial_var_encoding, var_encoding);
172        }
173
174        initial_var_encoding
175    }
176
177    /// checks if the steady error path is non-spurious
178    ///
179    /// returns additionally Some(path) if a non-spurious error state is reached
180    ///
181    /// if the last state of the steady error path is an error state, the specification needs to be provided
182    /// to ensure that the corresponding concrete state is also an error state
183    ///
184    /// 1. each indexed location, parameter, rule counter is non-negative
185    /// 2. encode steady error path
186    /// 3. encode that each steady path fragment is valid
187    /// 4. encode error states if the last state of the steady error path is an error state
188    /// 5. run smt solver
189    pub fn steady_is_non_spurious(
190        &mut self,
191        spec: Option<&DisjunctionTargetConfig>,
192    ) -> SpuriousResult {
193        // 1. each indexed location, parameter, rule counter is non-negative
194        for param_smt in self.ctx.param_to_smt.values() {
195            let param_encoding = self
196                .ctx
197                .smt_solver
198                .gte(*param_smt, self.ctx.smt_solver.numeral(0));
199            self.ctx
200                .smt_solver
201                .assert(param_encoding)
202                .expect("error for asserting a non-negative indexed location");
203        }
204        for loc in self.ta.locations() {
205            for i in 0..self.steady_error_path.length_configurations() * 2 {
206                let loc_smt = self.ctx.get_indexed_loc_smt(loc, i as usize);
207                let loc_encoding = self
208                    .ctx
209                    .smt_solver
210                    .gte(loc_smt, self.ctx.smt_solver.numeral(0));
211                self.ctx
212                    .smt_solver
213                    .assert(loc_encoding)
214                    .expect("error for asserting a non-negative indexed location");
215            }
216        }
217        for (_, rule_counter) in self.rule_counter_to_smt.iter() {
218            let rule_encoding = self
219                .ctx
220                .smt_solver
221                .gte(*rule_counter, self.ctx.smt_solver.numeral(0));
222            self.ctx
223                .smt_solver
224                .assert(rule_encoding)
225                .expect("error for asserting a non-negative rule counter");
226        }
227
228        // 2. encode steady error path
229        let abs = self.encode_steady_error_path();
230        self.ctx
231            .smt_solver
232            .assert(abs)
233            .expect("error for asserting encoded abstract path");
234
235        // 3. encode that each steady error path fragment is valid
236        let mut from_index: u32 = 0; // start from the first configuration after the initial one
237        let mut rule_index = 0;
238        for path in self.steady_error_path.steady_paths() {
239            self.ctx
240                .smt_solver
241                .assert(self.encode_steady_path(from_index, rule_index, path))
242                .expect("error for asserting steady path encoding");
243            from_index += 2; // each steady path adds two configurations
244            rule_index += path.length_transitions();
245            if rule_index < self.rule_counter_to_smt.len() as u32 {
246                self.ctx
247                    .smt_solver
248                    .assert(self.encode_step_transition(from_index, rule_index))
249                    .expect("error for asserting step encoding");
250                rule_index += 1; // each step applies one transition
251            }
252        }
253
254        // 4. encode error states
255        if let Some(specification) = spec {
256            self.ctx
257                .smt_solver
258                .assert(self.encode_error_states(specification))
259                .expect("error for asserting error states");
260        }
261        // 5. run smt solver
262        let response = self
263            .ctx
264            .smt_solver
265            .check()
266            .expect("Error checking smt encoding");
267        if response == Sat {
268            // steady error path is non-spurious
269            if spec.is_some() {
270                // reached an error state
271                let p = self
272                    .extract_non_spurious_concrete_path(SMTSolution::SAT)
273                    .expect("error extracting error path");
274                return SpuriousResult::new(true, Some(p));
275            }
276            return SpuriousResult::new(true, None);
277        }
278        // steady error path is spurious
279        SpuriousResult::new(false, None)
280    }
281
282    /// encodes that the step transition is valid
283    ///
284    /// by checking that the configuration after the steady path \sigma_i
285    /// leads to the configuration of the next steady path \sigma_{i+1}
286    /// by applying the transition r exactly once
287    pub fn encode_step_transition(&self, from_index: u32, rule_index: u32) -> SMTExpr {
288        let last_config_index = from_index - 1;
289        let next_config_index = from_index;
290        let (rule, rule_smt) = &self.rule_counter_to_smt[rule_index as usize];
291
292        let mut step_encoding = self
293            .ctx
294            .smt_solver
295            .eq(*rule_smt, self.ctx.smt_solver.numeral(1));
296
297        // processes move according to the step transition
298        let from_location = rule.source();
299        let to_location = rule.target();
300
301        let from_loc_smt = self
302            .ctx
303            .get_indexed_loc_smt(from_location, last_config_index as usize);
304        let to_loc_smt = self
305            .ctx
306            .get_indexed_loc_smt(to_location, last_config_index as usize);
307        let next_from_loc_smt = self
308            .ctx
309            .get_indexed_loc_smt(from_location, next_config_index as usize);
310        let next_to_loc_smt = self
311            .ctx
312            .get_indexed_loc_smt(to_location, next_config_index as usize);
313        if from_location == to_location {
314            // self-loop, so no change in locations but at least one process has to be in the sending location
315            step_encoding = self.ctx.smt_solver.and(
316                step_encoding,
317                self.ctx.smt_solver.eq(next_from_loc_smt, from_loc_smt),
318            );
319            step_encoding = self.ctx.smt_solver.and(
320                step_encoding,
321                self.ctx
322                    .smt_solver
323                    .gt(from_loc_smt, self.ctx.smt_solver.numeral(0)),
324            );
325        } else {
326            // \sigma'.k[from] = \sigma.k[from] - c_i
327            step_encoding = self.ctx.smt_solver.and(
328                step_encoding,
329                self.ctx.smt_solver.eq(
330                    next_from_loc_smt,
331                    self.ctx.smt_solver.sub(from_loc_smt, *rule_smt),
332                ),
333            );
334
335            // \sigma'.k[from] = \sigma.k[from] + c_i
336            step_encoding = self.ctx.smt_solver.and(
337                step_encoding,
338                self.ctx.smt_solver.eq(
339                    next_to_loc_smt,
340                    self.ctx.smt_solver.plus(to_loc_smt, *rule_smt),
341                ),
342            );
343        }
344
345        // all other locations unchanged
346        for loc in self.ta.locations() {
347            if loc != from_location && loc != to_location {
348                let loc_smt = self
349                    .ctx
350                    .get_indexed_loc_smt(loc, last_config_index as usize);
351                let next_loc_smt = self
352                    .ctx
353                    .get_indexed_loc_smt(loc, next_config_index as usize);
354                step_encoding = self
355                    .ctx
356                    .smt_solver
357                    .and(step_encoding, self.ctx.smt_solver.eq(next_loc_smt, loc_smt));
358            }
359        }
360
361        // variables are updated according to the rule
362        let action_map = rule
363            .actions()
364            .map(|action| (action.variable(), action.update().clone()))
365            .collect::<HashMap<_, _>>();
366
367        for shared_var in self.ta.variables() {
368            let shared_smt = self
369                .ctx
370                .get_indexed_shared_smt(shared_var, last_config_index as usize);
371            let next_shared_smt = self
372                .ctx
373                .get_indexed_shared_smt(shared_var, next_config_index as usize);
374            let update = action_map
375                .get(shared_var)
376                .unwrap_or(&UpdateExpression::Unchanged);
377            step_encoding = self.ctx.smt_solver.and(
378                step_encoding,
379                self.encode_update_shared_var(&shared_smt, &next_shared_smt, *rule_smt, update),
380            );
381        }
382
383        step_encoding
384    }
385
386    /// encodes that shared variable 'x_i' is updated according to the update or reset
387    /// of rule `r_i = (from, to, uv, τ, ϕ)` where `c_i` counts the number of times `r_i` is applied
388    ///
389    /// Encoding:
390    /// - uv(x) != 0 => σ_{i+1}.g(x) = σ_i.g(x) + c_i * uv(x)
391    /// - x ∈ τ => σ_{i+1}.g(x) = 0
392    /// - uv(x) = 0 ∧ x ∉ τ => σ_{i+1}.g(x) = σ_i.g(x)
393    fn encode_update_shared_var(
394        &self,
395        shared_smt: &SMTExpr,
396        next_shared_smt: &SMTExpr,
397        rule_smt: SMTExpr,
398        update: &UpdateExpression,
399    ) -> SMTExpr {
400        match update {
401            // σ_{i+1}.g(x) = σ_i.g(x) + c_i * uv(x)
402            Inc(x) => self.ctx.smt_solver.eq(
403                *next_shared_smt,
404                self.ctx.smt_solver.plus(
405                    *shared_smt,
406                    self.ctx
407                        .smt_solver
408                        .times(rule_smt, self.ctx.smt_solver.numeral(*x)),
409                ),
410            ),
411            // σ_{i+1}.g(x) = σ_i.g(x) - c_i * uv(x)
412            Dec(x) => self.ctx.smt_solver.eq(
413                *next_shared_smt,
414                self.ctx.smt_solver.sub(
415                    *shared_smt,
416                    self.ctx
417                        .smt_solver
418                        .times(rule_smt, self.ctx.smt_solver.numeral(*x)),
419                ),
420            ),
421            // σ_{i+1}.g(x) = 0
422            UpdateExpression::Reset => self
423                .ctx
424                .smt_solver
425                .eq(*next_shared_smt, self.ctx.smt_solver.numeral(0)),
426            // σ_{i+1}.g(x) = σ_i.g(x)
427            UpdateExpression::Unchanged => self.ctx.smt_solver.eq(*next_shared_smt, *shared_smt),
428        }
429    }
430
431    /// encodes the interval constraints for an steady path
432    ///
433    /// i.e., it is checked if for each step, the shared variables are in the respective interval
434    fn encode_steady_error_path(&mut self) -> SMTExpr {
435        let mut encoding = self.ctx.smt_solver.true_();
436        let mut index = 0;
437        for steady_path in self.steady_error_path.steady_paths() {
438            for (shared, interval) in steady_path
439                .variable_assignment()
440                .assignments()
441                .filter(|(v, _)| !self.ta.get_helper_vars_for_sumvars().contains_key(*v))
442            // do not encode the intervals of the replacement variables
443            {
444                self.ctx.set_curr_index(index);
445                encoding = self.ctx.smt_solver.and(
446                    encoding,
447                    self.encode_shared_interval(shared.clone(), interval.clone()),
448                );
449                self.ctx.set_curr_index(index + 1);
450                encoding = self.ctx.smt_solver.and(
451                    encoding,
452                    self.encode_shared_interval(shared.clone(), interval.clone()),
453                );
454            }
455            // encode the intervals of the sums here instead
456            // TODO: remove when ordering supports sums of variables
457            for (shared, interval) in self.ta.get_helper_vars_for_sumvars().iter().map(|(v, sv)| {
458                let interval = steady_path
459                    .variable_assignment()
460                    .assignments()
461                    .find(|(var, _)| *var == v)
462                    .expect("All variables should have an interval assignment")
463                    .1;
464                (sv, interval)
465            }) {
466                self.ctx.set_curr_index(index);
467                encoding = self.ctx.smt_solver.and(
468                    encoding,
469                    self.encode_shared_interval(shared.clone(), interval.clone()),
470                );
471                self.ctx.set_curr_index(index + 1);
472                encoding = self.ctx.smt_solver.and(
473                    encoding,
474                    self.encode_shared_interval(shared.clone(), interval.clone()),
475                );
476            }
477
478            index += 2;
479        }
480
481        encoding
482    }
483
484    /// encodes that the value of an indexed shared variable 'x_i' is in the given interval,
485    /// e.g., if 'x_i ∈ [t1,t2)' then: x_i ≥ t1 ∧ x_i < t2
486    fn encode_shared_interval<S>(&self, shared: S, interval: Interval) -> SMTExpr
487    where
488        S: IntoNoDivBooleanExpr<Variable> + Clone,
489    {
490        let interval_expression = interval.encode_into_boolean_expr(&shared);
491        interval_expression
492            .encode_to_smt_with_ctx(&self.ctx.smt_solver, self.ctx.get_current_config_ctx())
493            .expect("error encoding interval expression")
494    }
495
496    /// encodes the set of error states for the last configuration in the path
497    ///
498    /// This function works for any disjunction of Coverability, General Coverability and Reachability Specifications,
499    /// For every other specification, `smt.false` is returned
500    fn encode_error_states(&self, spec: &DisjunctionTargetConfig) -> SMTExpr {
501        // TODO: refactor to not rely on internals
502        spec.encode_to_smt_with_ctx(&self.ctx.smt_solver, self.ctx.config_ctxs.last().unwrap())
503            .expect("Failed to encode error conditions")
504    }
505
506    /// encodes that the `steady path` with rules R = \{r_0,...,r_k\} is valid
507    /// from = σ_{`from_index`} is the configuration at the beginning of the steady path
508    /// to = σ'_{`from_index`+1} is the configuration at the end of the steady path
509    ///
510    /// rule_index indicates the first rule of the steady path in the rule_counter_to_smt vector
511    ///
512    /// 1. Processes move according to the steady path
513    /// 2. Update shared variables 'x_0,...,x_n' according to the updates (resets or decrements are not allowed)
514    /// 3. Make sure that for every rule in the steady path there is a fireable chain
515    /// 4. If the error paths contains increments and decrements, ensure that monotonicity is preserved
516    fn encode_steady_path(
517        &self,
518        from_index: u32,
519        rule_index: u32,
520        steady_path: &SteadyPath,
521    ) -> SMTExpr {
522        let mut steady_path_encoding =
523            self.encode_steady_path_move_processes(from_index, rule_index, steady_path);
524        steady_path_encoding = self.ctx.smt_solver.and(
525            steady_path_encoding,
526            self.encode_steady_path_update_shared_variables(from_index, rule_index, steady_path),
527        );
528        steady_path_encoding = self.ctx.smt_solver.and(
529            steady_path_encoding,
530            self.encode_steady_path_fireable_chain(from_index, rule_index, steady_path),
531        );
532        if self.ta.has_decrements() {
533            steady_path_encoding = self.ctx.smt_solver.and(
534                steady_path_encoding,
535                self.encode_monotonicity_constraints(rule_index, steady_path),
536            );
537        }
538        steady_path_encoding
539    }
540
541    /// encodes that the processes in the 'steady path' with rules R = \{r_0,...,r_k\} move accordingly
542    /// from = σ_{`from_index`} is the configuration starting at the steady path
543    /// to = σ'_{`from_index`+1} is the configuration ending at the steady path
544    ///
545    /// `rule_index` indicates the first rule of the steady path in the rule_counter_to_smt vector
546    ///
547    /// encoding:
548    ///     ∀_{l_j ∈ L} σ'.k\[l_j\] = σ.k\[l_j\] + ∑_{r_i ∈ R with r_i.to = l_j} c_i - ∑_{r_i ∈ R with r_i.from = l_j} c_i
549    fn encode_steady_path_move_processes(
550        &self,
551        from_index: u32,
552        rule_index: u32,
553        steady_path: &SteadyPath,
554    ) -> SMTExpr {
555        let mut move_encoding = self.ctx.smt_solver.true_();
556        for loc in self.ta.locations() {
557            let loc_smt = self.ctx.get_indexed_loc_smt(loc, from_index as usize);
558            let next_loc_smt = self.ctx.get_indexed_loc_smt(loc, (from_index + 1) as usize);
559            let mut rhs = loc_smt;
560            for i in 0..steady_path.length_transitions() {
561                let (rule, rule_smt) = self.rule_counter_to_smt[(rule_index + i) as usize].clone();
562                if rule.target() == rule.source() {
563                    // self-loop, so no change in locations
564                    continue;
565                }
566                if rule.target() == loc {
567                    // σ'.k[l_j] = σ.k[l_j] + ∑_{r_i ∈ R with r_i.to = l_j} c_i - ∑_{r_i ∈ R with r_i.from = l_j} c_i
568                    rhs = self.ctx.smt_solver.plus(rhs, rule_smt);
569                }
570                if rule.source() == loc {
571                    // σ'.k[l_j] = σ.k[l_j] + ∑_{r_i ∈ R with r_i.to = l_j} c_i - ∑_{r_i ∈ R with r_i.from = l_j} c_i
572                    rhs = self.ctx.smt_solver.sub(rhs, rule_smt);
573                }
574            }
575            move_encoding = self
576                .ctx
577                .smt_solver
578                .and(move_encoding, self.ctx.smt_solver.eq(next_loc_smt, rhs));
579        }
580
581        move_encoding
582    }
583
584    /// encodes that the shared variables in the `steady path` with rules R = \{r_0,...,r_k\} are updated accordingly
585    /// from = σ_{`from_index`} is the configuration at the beginning of the steady path
586    /// to = σ'_{`from_index`+1} is the configuration at the end of the steady path
587    ///
588    /// `rule_index` indicates the first rule of the steady path in the rule_counter_to_smt vector
589    ///
590    /// returns an error if the steady path contains a reset or decrement update
591    ///
592    /// encoding:
593    ///    ∀ x: σ'.g\[x\] = σ.g\[x\] + ∑_{r_i ∈ R} c_i * uv_i\[x\] if x is not reset
594    ///    ∀ x: σ'.g\[x\] = 0 if x is reset
595    fn encode_steady_path_update_shared_variables(
596        &self,
597        from_index: u32,
598        rule_index: u32,
599        steady_path: &SteadyPath,
600    ) -> SMTExpr {
601        let mut update_encoding = self.ctx.smt_solver.true_();
602        for shared in self.ta.variables() {
603            let shared_smt = self.ctx.get_indexed_shared_smt(shared, from_index as usize);
604            let mut reset_shared = None;
605            let next_shared_smt = self
606                .ctx
607                .get_indexed_shared_smt(shared, (from_index + 1) as usize);
608            let mut rhs = shared_smt;
609            for i in 0..steady_path.length_transitions() {
610                let (rule, rule_smt) = self.rule_counter_to_smt[(rule_index + i) as usize].clone();
611                let action_map = rule
612                    .actions()
613                    .map(|action| (action.variable(), action.update().clone()))
614                    .collect::<HashMap<_, _>>();
615                let update = action_map
616                    .get(shared)
617                    .unwrap_or(&UpdateExpression::Unchanged);
618                match update {
619                    Inc(x) => {
620                        // σ'.g = σ.g + ∑_{r_i ∈ R} c_i * uv_i
621                        rhs = self.ctx.smt_solver.plus(
622                            rhs,
623                            self.ctx
624                                .smt_solver
625                                .times(rule_smt, self.ctx.smt_solver.numeral(*x)),
626                        );
627                    }
628                    Dec(x) => {
629                        // σ'.g = σ.g - ∑_{r_i ∈ R} c_i * uv_i
630                        rhs = self.ctx.smt_solver.sub(
631                            rhs,
632                            self.ctx
633                                .smt_solver
634                                .times(rule_smt, self.ctx.smt_solver.numeral(*x)),
635                        );
636                    }
637                    // σ'.g[x] = 0
638                    UpdateExpression::Reset => {
639                        reset_shared = Some(
640                            self.ctx
641                                .smt_solver
642                                .eq(next_shared_smt, self.ctx.smt_solver.numeral(0)),
643                        );
644                    }
645                    UpdateExpression::Unchanged => {
646                        continue;
647                    }
648                }
649            }
650            update_encoding = self.ctx.smt_solver.and(
651                update_encoding,
652                self.ctx.smt_solver.eq(next_shared_smt, rhs),
653            );
654            if let Some(reset_encoding) = reset_shared {
655                update_encoding = self.ctx.smt_solver.and(update_encoding, reset_encoding);
656            }
657        }
658        update_encoding
659    }
660
661    /// encodes that all the rules in the `steady path` with rules R = \{r_0,...,r_k\} lead to a fireable chain
662    /// from = σ_{`from_index`} is the configuration at the beginning of steady path
663    ///
664    /// `rule_index` indicates the first rule of the steady path in the rule_counter_to_smt vector
665    ///
666    /// Compared to the version described in the main section of the paper, we
667    /// statically filter the set S, to only include chains from R where for all i it
668    /// holds that
669    ///     r_i.to = r_{i+1}.from and r_s = r
670    /// TODO add more tests
671    fn encode_steady_path_fireable_chain(
672        &self,
673        from_index: u32,
674        rule_index: u32,
675        steady_path: &SteadyPath,
676    ) -> SMTExpr {
677        if rule_index == self.rule_counter_to_smt.len() as u32
678            || steady_path.length_transitions() == 0
679        {
680            // no rules in the steady path, so trivially true
681            return self.ctx.smt_solver.true_();
682        }
683        let vector_rules = self.rule_counter_to_smt
684            [(rule_index as usize)..((rule_index + steady_path.length_transitions()) as usize)]
685            .to_vec();
686        let vector_cloned = vector_rules
687            .iter()
688            .map(|(rule, rule_smt)| (rule, rule_smt))
689            .collect::<Vec<_>>();
690        self.ctx
691            .smt_solver
692            .and_many(vector_rules.clone().into_iter().map(|(rule, rule_smt)| {
693                // x_r > 0
694                let xr_gt_0 = self
695                    .ctx
696                    .smt_solver
697                    .gt(rule_smt, self.ctx.smt_solver.numeral(0));
698
699                // S ∈ 2^R
700                let all_possible_s =
701                    self.compute_s_for_r_for_steady_path(&vector_cloned, &rule, &rule_smt);
702
703                // prevent panics if there is no applicable chain
704                if all_possible_s.is_empty() {
705                    return self.ctx.smt_solver.false_();
706                }
707
708                // ∨_S ∈ 2^R ф_{chain}
709                let disj_s = self.ctx.smt_solver.or_many(
710                    all_possible_s
711                        .iter()
712                        .map(|s| self.encode_phi_chain_for_steady_path(from_index, s)),
713                );
714
715                //  x_r > 0 => ∨_S ∈ 2^R ф_{chain}
716                self.ctx.smt_solver.imp(xr_gt_0, disj_s)
717            }))
718    }
719
720    /// encodes that the rules applied in the `steady path` with rules R = \{r_0,...,r_k\}
721    /// guarantee mononotonicity for the guards
722    ///
723    /// i.e., it ensures that no rule `r_i` which decrements a shared variable x is applied
724    /// when a rule `r_j` which increments x is applied as well and vice versa
725    ///
726    /// `rule_index` indicates the first rule of the steady path in the rule_counter_to_smt vector
727    ///
728    /// encoding:
729    ///     ∀_{x ∈ Γ} (∑_{r_i ∈ R with uv_i\[x\] > 0} c_i = 0) ∨ (∑_{r_i ∈ R with uv_i\[x\] < 0} c_i = 0)
730    /// TODO: add more tests
731    fn encode_monotonicity_constraints(
732        &self,
733        rule_index: u32,
734        steady_path: &SteadyPath,
735    ) -> SMTExpr {
736        let mut monotonicity_encoding_constraints = Vec::new();
737        for shared in self.ta.variables() {
738            let mut inc_rules = Vec::new();
739            let mut dec_rules = Vec::new();
740            for i in 0..steady_path.length_transitions() {
741                let (rule, rule_smt) = self.rule_counter_to_smt[(rule_index + i) as usize].clone();
742                let action_map = rule
743                    .actions()
744                    .map(|action| (action.variable(), action.update().clone()))
745                    .collect::<HashMap<_, _>>();
746                let update = action_map
747                    .get(shared)
748                    .unwrap_or(&UpdateExpression::Unchanged);
749                match update {
750                    Inc(_) => inc_rules.push(rule_smt),
751                    Dec(_) => dec_rules.push(rule_smt),
752                    UpdateExpression::Reset | UpdateExpression::Unchanged => {}
753                }
754            }
755            if inc_rules.is_empty() || dec_rules.is_empty() {
756                // no increments or no decrements, so monotonicity is trivially satisfied
757                continue;
758            } else {
759                let sum_inc = self.ctx.smt_solver.plus_many(inc_rules.iter().cloned());
760                let sum_dec = self.ctx.smt_solver.plus_many(dec_rules.iter().cloned());
761                let inc_eq_0 = self
762                    .ctx
763                    .smt_solver
764                    .eq(sum_inc, self.ctx.smt_solver.numeral(0));
765                let dec_eq_0 = self
766                    .ctx
767                    .smt_solver
768                    .eq(sum_dec, self.ctx.smt_solver.numeral(0));
769                // ∀_{x ∈ Γ} (∑_{r_i ∈ R with uv_i[x] > 0} c_i = 0) ∨ (∑_{r_i ∈ R with uv_i[x] < 0} c_i = 0)
770                monotonicity_encoding_constraints.push(self.ctx.smt_solver.or(inc_eq_0, dec_eq_0));
771            }
772        }
773        if monotonicity_encoding_constraints.is_empty() {
774            // no increment and decrement for any shared variable, so monotonicity is trivially satisfied
775            self.ctx.smt_solver.true_()
776        } else {
777            self.ctx
778                .smt_solver
779                .and_many(monotonicity_encoding_constraints)
780        }
781    }
782
783    /// Encode ϕ_chain for a single non-deterministic guess for steady paths
784    ///
785    /// from_index indicates the index of the configuration at the beginning of the steady path
786    ///
787    /// This function encodes the ϕ_{chain} for set of rules s
788    /// However, this function only encodes ∧_{r ∈ s} x_r > 0 ∧ σ.k(r_1.from) > 0
789    /// as all other constraints can be statically checked during the
790    /// computation of S
791    ///
792    /// Note: This function expects the sequence of rules to be reversed
793    /// TODO: add more tests
794    fn encode_phi_chain_for_steady_path(
795        &self,
796        from_index: u32,
797        s: &[(&Rule, &SMTExpr)],
798    ) -> SMTExpr {
799        if s.is_empty() {
800            return self.ctx.smt_solver.true_();
801        }
802
803        // σ.k(r_1.from) > 0
804        let loc = s.last().unwrap().0.source(); // change if not reversed
805        let loc_var = self.ctx.get_indexed_loc_smt(loc, from_index as usize);
806        let loc_gt_0 = self
807            .ctx
808            .smt_solver
809            .gt(loc_var, self.ctx.smt_solver.numeral(0));
810
811        self.ctx.smt_solver.and_many(
812            s.iter()
813                .map(|(_, rule_smt)| {
814                    self.ctx
815                        .smt_solver
816                        .gt(**rule_smt, self.ctx.smt_solver.numeral(0))
817                })
818                .chain([loc_gt_0]),
819        )
820    }
821
822    /// Compute all sets possible values of S for rule r and 'steady path' with rules R = \{r_0,...,r_k\}
823    ///
824    /// This function computes all possible sets S for all rules r, where S is
825    /// the set of rules from R that can lead up to r.
826    /// Additionally, this function ensures that these sets are chainable,
827    /// meaning they satisfy the formula:
828    ///     ∧_{1< i ≤ s} r_{i-1}.to = r_i.from ∧ r_s = r
829    ///
830    /// This function uses the helper `compute_s_with_backtracking_recursive_for_steady_path`,
831    /// to directly compute only chainable rule sequences.
832    ///
833    /// Note: **This function returns the sequences to apply in reversed
834    /// order!**. You can use `.rev()` to iterate in reversed order.
835    /// TODO: add more tests
836    fn compute_s_for_r_for_steady_path(
837        &'a self,
838        vector_rules: &'a Vec<(&'a Rule, &'a SMTExpr)>,
839        r: &'a Rule,
840        rule_smt: &'a SMTExpr,
841    ) -> Vec<Vec<(&'a Rule, &'a SMTExpr)>> {
842        let loc = r.source();
843        Self::compute_s_with_backtracking_recursive_for_steady_path(
844            loc,
845            vector_rules,
846            vec![(r, rule_smt)],
847        )
848    }
849
850    /// Recursive helper function that extends the returns a vector that
851    /// contains:
852    ///  - the current sequence of chainable rules
853    ///  - the current sequence of rules extended with the incoming rules into
854    ///    `loc`
855    ///  - the chainable extensions for these rule sequences
856    ///
857    /// Note: Chains will be returned in reverse order !
858    /// TODO: add more tests
859    fn compute_s_with_backtracking_recursive_for_steady_path(
860        loc: &'a Location,
861        vector_rules: &[(&'a Rule, &'a SMTExpr)],
862        rules_applied: Vec<(&'a Rule, &'a SMTExpr)>,
863    ) -> Vec<Vec<(&'a Rule, &'a SMTExpr)>> {
864        // get incoming rules into loc, these will be chainable
865        let mut s = vector_rules
866            .iter()
867            .filter(|r| !rules_applied.contains(r) && r.0.target() == loc)
868            .flat_map(|r| {
869                let loc = r.0.source();
870                let mut rules_applied: Vec<_> = rules_applied.clone();
871                rules_applied.push(*r);
872
873                Self::compute_s_with_backtracking_recursive_for_steady_path(
874                    loc,
875                    vector_rules,
876                    rules_applied,
877                )
878            })
879            .collect::<Vec<_>>();
880
881        // current sequence is chainable, so add it
882        s.push(rules_applied);
883
884        s
885    }
886
887    /// extract a non-spurious counterexample trace reaching an error state from the smt solver
888    fn extract_non_spurious_concrete_path(
889        &mut self,
890        res: SMTSolution,
891    ) -> Result<Path, SMTEncoderError> {
892        // path builder to construct an error path
893        let path_builder = PathBuilder::new(self.ta.get_ta().clone());
894
895        // extract parameters
896        let mut param_assignment = HashMap::<Parameter, u32>::new();
897        for param in self.ta.parameters() {
898            if !res.is_sat() {
899                return Err(SMTEncoderError::NoSatParamAssign(param.clone()));
900            }
901
902            let param_value = self
903                .ctx
904                .get_assignment(res, param)
905                .expect("error getting assignment for parameter");
906            match param_value {
907                Some(value) => {
908                    param_assignment.insert(param.clone(), value.try_into().unwrap());
909                }
910                None => {
911                    return Err(SMTEncoderError::NoSatParamAssign(param.clone()));
912                }
913            }
914        }
915
916        let mut initialized_path_builder = path_builder
917            .add_parameter_assignment(param_assignment)
918            .expect("error adding parameter assignment");
919
920        let configs = self.ctx.get_assigned_configs(res);
921
922        let mut concrete_configs = Vec::from([configs[0].clone()]);
923        let mut concrete_transitions = vec![];
924
925        let mut config_index = 0;
926        let mut rule_index = 0;
927        for path in self.steady_error_path.steady_paths() {
928            let (transitions, updated_configs) = self.extract_steady_path(
929                res,
930                path,
931                &configs,
932                concrete_configs.last().unwrap().clone(),
933                config_index,
934                rule_index,
935            )?;
936            concrete_configs.extend(updated_configs);
937            concrete_transitions.extend(transitions);
938
939            config_index += 2; // each steady path adds two configurations
940            rule_index += path.length_transitions();
941            if config_index < configs.len() as u32 {
942                // extract step transition
943                let (config, transition) =
944                    self.extract_step_path(res, &configs, config_index, rule_index)?;
945                concrete_configs.push(config);
946                concrete_transitions.push(transition);
947                rule_index += 1; // each step applies one transition
948            }
949        }
950
951        initialized_path_builder = initialized_path_builder
952            .add_configurations(concrete_configs)
953            .expect("error adding configurations to path builder");
954        initialized_path_builder = initialized_path_builder
955            .add_transitions(concrete_transitions)
956            .expect("error adding transitions to path builder");
957
958        Ok(initialized_path_builder
959            .build()
960            .expect("error building the path"))
961    }
962
963    /// extracts the concrete step between two steady transitions
964    fn extract_step_path(
965        &mut self,
966        res: SMTSolution,
967        configurations: &[Configuration],
968        config_index: u32,
969        rule_index: u32,
970    ) -> Result<(Configuration, Transition), SMTEncoderError> {
971        // extract configuration
972        let config = configurations[config_index as usize].clone();
973
974        // add step transition to the path
975        let rule_counter = self.rule_counter_to_smt[rule_index as usize].clone();
976        let counter_value = self
977            .get_rule_counter_assignment(res, &rule_counter.1)
978            .expect("error getting rule counter assignment");
979
980        match counter_value {
981            Some(value) => {
982                assert!(
983                    value == 1,
984                    "Step transition should be applied exactly once, but was applied {value} times!"
985                );
986                let transition = Transition::new(rule_counter.0, value.try_into().unwrap());
987                Ok((config, transition))
988            }
989            None => Err(SMTEncoderError::NoSatRuleCounterAssign(
990                rule_counter.0.clone(),
991                rule_index,
992            )),
993        }
994    }
995
996    /// extracts the concrete path from a steady path
997    fn extract_steady_path(
998        &mut self,
999        res: SMTSolution,
1000        steady_path: &SteadyPath,
1001        configurations: &[Configuration],
1002        last_config: Configuration,
1003        config_index: u32,
1004        rule_index: u32,
1005    ) -> Result<(Vec<Transition>, Vec<Configuration>), SMTEncoderError> {
1006        // extracts how often each rule of the cycle is applied
1007        let mut rules = Vec::<(Rule, u32)>::new();
1008        for i in 0..steady_path.length_transitions() {
1009            let index = (i + rule_index) as usize;
1010            let rule_counter = self.rule_counter_to_smt[index].clone();
1011            let counter_value = self
1012                .get_rule_counter_assignment(res, &rule_counter.1)
1013                .expect("error getting rule counter assignment");
1014            match counter_value {
1015                Some(value) => {
1016                    rules.push((rule_counter.0, value.try_into().unwrap()));
1017                }
1018                None => {
1019                    return Err(SMTEncoderError::NoSatRuleCounterAssign(
1020                        rule_counter.0.clone(),
1021                        index.try_into().unwrap(),
1022                    ));
1023                }
1024            }
1025        }
1026
1027        rules = rules
1028            .into_iter()
1029            .filter(|(_, n)| *n > 0)
1030            .collect::<Vec<_>>();
1031
1032        let mut curr_config = last_config.clone();
1033
1034        let mut concrete_configs = vec![];
1035        let mut concrete_transitions = vec![];
1036
1037        let old_rules = rules.clone();
1038        let old_config = curr_config.clone();
1039
1040        // Try to order rules such that they are chainable
1041        while !rules.is_empty() {
1042            let mut n_apply = 0;
1043
1044            // Add enabled self-loops first
1045            for (rule, n_to_apply) in rules.iter_mut().filter(|(r, _)| r.source() == r.target()) {
1046                if curr_config.get_processes_in_location(rule.source()) > 0 {
1047                    // if the transition is a self-loop apply it n_to_apply_times
1048                    n_apply = *n_to_apply;
1049                    *n_to_apply = 0;
1050
1051                    let (transition, config) = Self::extract_transition_and_updated_config(
1052                        curr_config.clone(),
1053                        rule.clone(),
1054                        n_apply,
1055                    );
1056                    curr_config = config;
1057                    concrete_configs.push(curr_config.clone());
1058                    concrete_transitions.push(transition);
1059                }
1060            }
1061
1062            for (rule, n_to_apply) in rules.iter_mut().filter(|(_, n)| *n > 0) {
1063                // Check if rule can be applied
1064                if curr_config.get_processes_in_location(rule.source()) > 0 {
1065                    // compute the number of times it can be applied
1066                    n_apply = min(
1067                        curr_config.get_processes_in_location(rule.source()),
1068                        *n_to_apply,
1069                    );
1070
1071                    *n_to_apply -= n_apply;
1072
1073                    let (transition, config) = Self::extract_transition_and_updated_config(
1074                        curr_config,
1075                        rule.clone(),
1076                        n_apply,
1077                    );
1078                    curr_config = config;
1079                    concrete_configs.push(curr_config.clone());
1080                    concrete_transitions.push(transition);
1081
1082                    // check if there are new self loops enabled after updating
1083                    // the configuration
1084                    break;
1085                }
1086            }
1087
1088            assert!(
1089                n_apply > 0,
1090                "Failed to order rules into chainable order! Configuration that rules must be applied on:\n{}\n\nRules left to apply:\n{}\n\nSet of rules to be ordered in this steady step:\n{}\n\nInitial configuration of the steady step:\n{}\n\n ",
1091                curr_config,
1092                join_iterator(rules.iter().map(|(r, i)| format!("{i} x {r}")), ",\n"),
1093                join_iterator(old_rules.iter().map(|(r, i)| format!("{i} x {r}")), ",\n"),
1094                old_config,
1095            );
1096
1097            rules.retain(|(_, n)| *n > 0);
1098        }
1099
1100        // check that the last configuration is the same as from the smt solver
1101        let i = config_index + 1;
1102
1103        let correct_config = configurations[i as usize].clone();
1104
1105        if correct_config != last_config {
1106            assert!(concrete_configs.last().unwrap().clone() == correct_config);
1107        }
1108
1109        Ok((concrete_transitions, concrete_configs))
1110    }
1111
1112    /// Create the transition that applies rule `rule` `n` times and compute the
1113    /// resulting configuration
1114    fn extract_transition_and_updated_config(
1115        config: Configuration,
1116        rule: Rule,
1117        n: u32,
1118    ) -> (Transition, Configuration) {
1119        let transition = Transition::new(rule, n);
1120        let curr_config = config
1121            .apply_rule(&transition)
1122            .expect("Constructed Transition invalid!");
1123
1124        (transition, curr_config)
1125    }
1126
1127    /// returns a satisying assignment for a given rule counter given as SMTExpr
1128    fn get_rule_counter_assignment(
1129        &mut self,
1130        res: SMTSolution,
1131        expr: &SMTExpr,
1132    ) -> Result<Option<u64>, SMTSolverError> {
1133        match res {
1134            SMTSolution::SAT => {
1135                let solver = self.ctx.get_smt_solver_mut();
1136
1137                let solution = solver.get_value(vec![*expr])?;
1138                debug_assert!(solution.len() == 1);
1139                debug_assert!(solution[0].0 == *expr);
1140
1141                let solution_val = solver.get(solution[0].1);
1142
1143                Ok(Some(u64::try_from(solution_val).unwrap()))
1144            }
1145            SMTSolution::UNSAT => Ok(None),
1146        }
1147    }
1148}
1149
1150/// Custom struct that stores the result of the Spurious Check
1151pub struct SpuriousResult {
1152    /// indicates if the abstract path is non spurious
1153    is_non_spurious: bool,
1154    /// optional path that is non-spurious
1155    non_spurious_path: Option<Path>,
1156}
1157impl SpuriousResult {
1158    /// creates a new SMTSpuriousResult
1159    pub fn new(is_non_spurious: bool, non_spurious_path: Option<Path>) -> Self {
1160        SpuriousResult {
1161            is_non_spurious,
1162            non_spurious_path,
1163        }
1164    }
1165    /// returns a new SMTSpuriousResult indicating that the abstract path is spurious
1166    pub fn new_spurious() -> Self {
1167        SpuriousResult {
1168            is_non_spurious: false,
1169            non_spurious_path: None,
1170        }
1171    }
1172    /// returns true if the abstract path is spurious
1173    pub fn is_non_spurious(&self) -> bool {
1174        self.is_non_spurious
1175    }
1176
1177    /// returns the non-spurious path if it exists
1178    pub fn non_spurious_path(&self) -> Option<&Path> {
1179        self.non_spurious_path.as_ref()
1180    }
1181}
1182
1183/// Custom Error type to indicate an error
1184/// when calling the `SMTEncoder`
1185#[derive(Debug, Clone)]
1186#[allow(clippy::enum_variant_names)]
1187enum SMTEncoderError {
1188    /// Error indicating that there is no satisfying assignment for a given parameter
1189    NoSatParamAssign(Parameter),
1190    /// Error indicating that there is no satisfying assignemnt for a given rule counter
1191    NoSatRuleCounterAssign(Rule, u32),
1192}
1193
1194impl std::error::Error for SMTEncoderError {}
1195
1196impl Display for SMTEncoderError {
1197    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1198        match self {
1199            SMTEncoderError::NoSatParamAssign(param) => write!(
1200                f,
1201                "There exists no satisfying assignment for parameter {param}"
1202            ),
1203            SMTEncoderError::NoSatRuleCounterAssign(rule, i) => write!(
1204                f,
1205                "There exists no satisfying assignment for rule {rule} at index {i}",
1206            ),
1207        }
1208    }
1209}
1210
1211/// An indexed SMT Context which creates and manages smt variables for parameters
1212/// and indexed smt variables for Locations, Variables and Rules
1213///
1214/// The context keeps track of the current index for locations, variables and rules
1215struct IndexedSMTContext {
1216    /// smt solver which used to evaluate SMT constraints
1217    smt_solver: SMTSolver,
1218    /// smt variable contexts for configurations
1219    config_ctxs: Vec<ConfigCtx>,
1220    /// mapping from parameter to smt variable
1221    param_to_smt: Rc<HashMap<Parameter, SMTExpr>>,
1222    /// current index
1223    curr_index: usize,
1224}
1225impl IndexedSMTContext {
1226    /// creates a new IndexedSMTContext that creates smt variables
1227    /// for all indexed locations and shared variables up to index `len`,
1228    /// and all parameters
1229    fn new<T: ThresholdAutomaton>(solver_builder: SMTSolverBuilder, len: usize, ta: &T) -> Self {
1230        let mut solver = solver_builder.new_solver();
1231
1232        // initialize parameter variables
1233        let param_to_smt = ta
1234            .parameters()
1235            .map(|p| {
1236                let smt = p
1237                    .declare_variable(&mut solver, 0)
1238                    .expect("Failed to declare parameter");
1239                (p.clone(), smt)
1240            })
1241            .collect::<HashMap<_, _>>();
1242        let param_to_smt = Rc::new(param_to_smt);
1243
1244        let config_ctxs = (0..len)
1245            .map(|index| ConfigCtx::new(&mut solver, ta, param_to_smt.clone(), index))
1246            .collect();
1247
1248        IndexedSMTContext {
1249            smt_solver: solver,
1250            config_ctxs,
1251            param_to_smt,
1252            curr_index: 0,
1253        }
1254    }
1255
1256    /// updates the current index
1257    fn set_curr_index(&mut self, i: usize) {
1258        self.curr_index = i
1259    }
1260
1261    /// Get the current configuration context
1262    // TODO: refactor such that this not necessary
1263    fn get_current_config_ctx(&self) -> &ConfigCtx {
1264        &self.config_ctxs[self.curr_index]
1265    }
1266    /// Get the smt expression for the indexed shared variable at index `i`
1267    // TODO: refactor such that this not necessary
1268    fn get_indexed_shared_smt(&self, var: &Variable, i: usize) -> SMTExpr {
1269        self.config_ctxs[i]
1270            .get_expr_for(var)
1271            .expect("Failed to encode variable")
1272    }
1273    /// Get the smt expression for the indexed location at index `i`
1274    // TODO: refactor such that this not necessary
1275    fn get_indexed_loc_smt(&self, loc: &Location, i: usize) -> SMTExpr {
1276        self.config_ctxs[i]
1277            .get_expr_for(loc)
1278            .expect("Failed to encode location")
1279    }
1280
1281    /// Extract the assigned configurations
1282    fn get_assigned_configs(&mut self, res: SMTSolution) -> Vec<Configuration> {
1283        self.config_ctxs
1284            .iter()
1285            .map(|cfg_ctx| {
1286                cfg_ctx
1287                    .get_assigned_configuration(&mut self.smt_solver, res)
1288                    .expect("Failed to extract configuration assignment")
1289            })
1290            .collect()
1291    }
1292}
1293
1294impl SMTSolverContext for IndexedSMTContext {
1295    fn get_smt_solver(&self) -> &SMTSolver {
1296        &self.smt_solver
1297    }
1298
1299    fn get_smt_solver_mut(&mut self) -> &mut SMTSolver {
1300        &mut self.smt_solver
1301    }
1302}
1303
1304impl SMTVariableContext<Parameter> for IndexedSMTContext {
1305    fn get_expr_for(&self, param: &Parameter) -> Result<SMTExpr, SMTSolverError> {
1306        self.param_to_smt
1307            .get(param)
1308            .cloned()
1309            .ok_or_else(|| SMTSolverError::UndeclaredParameter(param.clone()))
1310    }
1311
1312    fn get_exprs<'a>(&'a self) -> impl IntoIterator<Item = &'a Parameter>
1313    where
1314        Parameter: 'a,
1315    {
1316        self.param_to_smt.keys()
1317    }
1318}
1319impl GetAssignment<Parameter> for IndexedSMTContext {}
1320
1321/// This context allows to use a custom SMT expression instead of a variable
1322/// when encoding expressions into SMT
1323struct _ReplacingContext {
1324    var_to_smt: HashMap<Variable, SMTExpr>,
1325    param: Rc<HashMap<Parameter, SMTExpr>>,
1326}
1327
1328impl _ReplacingContext {
1329    /// Create a new ReplacingContext
1330    fn _new(
1331        var_to_smt: HashMap<Variable, SMTExpr>,
1332        param: Rc<HashMap<Parameter, SMTExpr>>,
1333    ) -> Self {
1334        Self { var_to_smt, param }
1335    }
1336}
1337
1338impl SMTVariableContext<Variable> for _ReplacingContext {
1339    fn get_expr_for(&self, expr: &Variable) -> Result<SMTExpr, SMTSolverError> {
1340        self.var_to_smt
1341            .get(expr)
1342            .cloned()
1343            .ok_or_else(|| SMTSolverError::UndeclaredVariable(expr.clone()))
1344    }
1345
1346    fn get_exprs<'a>(&'a self) -> impl IntoIterator<Item = &'a Variable>
1347    where
1348        Variable: 'a,
1349    {
1350        self.var_to_smt.keys()
1351    }
1352}
1353
1354impl SMTVariableContext<Parameter> for _ReplacingContext {
1355    fn get_expr_for(&self, expr: &Parameter) -> Result<SMTExpr, SMTSolverError> {
1356        self.param
1357            .get(expr)
1358            .cloned()
1359            .ok_or_else(|| SMTSolverError::UndeclaredParameter(expr.clone()))
1360    }
1361
1362    fn get_exprs<'a>(&'a self) -> impl IntoIterator<Item = &'a Parameter>
1363    where
1364        Parameter: 'a,
1365    {
1366        self.param.keys()
1367    }
1368}
1369
1370#[cfg(test)]
1371mod tests {
1372    use crate::paths::SteadyErrorPath;
1373    use crate::paths::SteadyPath;
1374    use crate::paths::VariableAssignment;
1375    use crate::zcs::SymbolicVariableAssignment;
1376    use crate::zcs::builder::ZCSBuilder;
1377    use crate::zcs_error_graph::ZCSErrorGraph;
1378    use crate::zcs_error_graph::builder::ZCSErrorGraphBuilder;
1379    use taco_interval_ta::builder::IntervalTABuilder;
1380
1381    use taco_interval_ta::IntervalThresholdAutomaton;
1382    use taco_smt_encoder::SMTSolverBuilder;
1383
1384    use taco_threshold_automaton::BooleanVarConstraint;
1385    use taco_threshold_automaton::LocationConstraint;
1386    use taco_threshold_automaton::ParameterConstraint;
1387
1388    use crate::ZCSStates;
1389    use crate::smt_encoder_steady::SMTEncoderError;
1390    use crate::smt_encoder_steady::SMTEncoderSteady;
1391    use crate::smt_encoder_steady::SMTSolution;
1392    use crate::zcs::SymbolicTransition;
1393    use std::collections::HashMap;
1394    use std::collections::HashSet;
1395    use taco_interval_ta::interval::Interval;
1396    use taco_interval_ta::interval::IntervalBoundary;
1397    use taco_model_checker::reachability_specification::TargetConfig;
1398    use taco_threshold_automaton::RuleDefinition;
1399    use taco_threshold_automaton::ThresholdAutomaton;
1400    use taco_threshold_automaton::expressions::ComparisonOp;
1401    use taco_threshold_automaton::expressions::IntegerExpression;
1402    use taco_threshold_automaton::expressions::fraction::Fraction;
1403    use taco_threshold_automaton::expressions::{Location, Parameter, Variable};
1404    use taco_threshold_automaton::general_threshold_automaton::Action;
1405    use taco_threshold_automaton::general_threshold_automaton::UpdateExpression;
1406    use taco_threshold_automaton::general_threshold_automaton::builder::RuleBuilder;
1407    use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::Threshold;
1408
1409    /// Construct the following abstract threshold automaton:
1410    ///
1411    /// Threshold Automaton:
1412    /// Locations: {l0,l1,l2}
1413    /// Initial location: l0
1414    /// Parameter: {n,t,f}
1415    /// Shared Variable: x
1416    /// Resilience Condition: n > 3 * t & t >= f
1417    /// Initial location constraints: l0 = n - t & l1 = 0 & l2 = 0
1418    /// Initial variable constraints: x = 0
1419    /// Rules:
1420    ///     r0: l0 -> l1, guard: true, action: x = x + 1
1421    ///     r1: l0 -> l1, guard: true, action: x = x - 1
1422    ///     r2: l0 -> l1, guard: true, action: x = 0
1423    ///     r3: l1 -> l2, guard: x >= n - t, action: none
1424    ///     r4: l1 -> l2, guard: x >= n - t, action: x = x + 1
1425    ///
1426    /// Abstract Thershold Automaton:
1427    /// Intervals for x: I_0 = [0,1), I_1 = [1,n-t), I_2 = [n-t, infty)
1428    /// Interval Order: I_0 < I_1 < I_2
1429    /// Abstract Rules:
1430    ///     r0: l0 -> l1, guard: true, action: x = x + 1
1431    ///     r1: l0 -> l1, guard: true, action: x = x - 1
1432    ///     r2: l0 -> l1, guard: true, action: x = 0
1433    ///     r3: l1 -> l2, guard: x = I_2, action: none
1434    ///     r4: l1 -> l2, guard: x = I_2, action: x = x + 1
1435    fn get_test_automaton() -> IntervalThresholdAutomaton {
1436        let ta_builder =
1437            taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
1438                .with_locations(vec![
1439                    Location::new("l0"),
1440                    Location::new("l1"),
1441                    Location::new("l2"),
1442                ])
1443                .unwrap()
1444                .with_variable(Variable::new("x"))
1445                .unwrap()
1446                .with_parameters(vec![
1447                    Parameter::new("n"),
1448                    Parameter::new("t"),
1449                    Parameter::new("f"),
1450                ])
1451                .unwrap()
1452                .initialize()
1453                .with_resilience_conditions(vec![
1454                    ParameterConstraint::ComparisonExpression(
1455                        Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1456                        ComparisonOp::Gt,
1457                        Box::new(
1458                            IntegerExpression::Const(3)
1459                                * IntegerExpression::Param(Parameter::new("t")),
1460                        ),
1461                    ),
1462                    ParameterConstraint::ComparisonExpression(
1463                        Box::new(IntegerExpression::Atom(Parameter::new("t"))),
1464                        ComparisonOp::Geq,
1465                        Box::new(IntegerExpression::Param(Parameter::new("f"))),
1466                    ),
1467                    ParameterConstraint::ComparisonExpression(
1468                        Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1469                        ComparisonOp::Gt,
1470                        Box::new(IntegerExpression::Const(0)),
1471                    ),
1472                    ParameterConstraint::ComparisonExpression(
1473                        Box::new(IntegerExpression::Atom(Parameter::new("t"))),
1474                        ComparisonOp::Geq,
1475                        Box::new(IntegerExpression::Const(0)),
1476                    ),
1477                    ParameterConstraint::ComparisonExpression(
1478                        Box::new(IntegerExpression::Atom(Parameter::new("f"))),
1479                        ComparisonOp::Geq,
1480                        Box::new(IntegerExpression::Const(0)),
1481                    ),
1482                    ParameterConstraint::ComparisonExpression(
1483                        Box::new(IntegerExpression::Atom(Parameter::new("n")) - IntegerExpression::Atom(Parameter::new("t"))),
1484                        ComparisonOp::Gt,
1485                        Box::new(IntegerExpression::Const(1)),
1486                    ),
1487                ])
1488                .unwrap()
1489                .with_initial_location_constraints(vec![
1490                    LocationConstraint::ComparisonExpression(
1491                        Box::new(IntegerExpression::Atom(Location::new("l0"))),
1492                        ComparisonOp::Eq,
1493                        Box::new(
1494                            IntegerExpression::Param(Parameter::new("n"))
1495                                - IntegerExpression::Param(Parameter::new("t")),
1496                        ),
1497                    ),
1498                    LocationConstraint::ComparisonExpression(
1499                        Box::new(IntegerExpression::Atom(Location::new("l1"))),
1500                        ComparisonOp::Eq,
1501                        Box::new(IntegerExpression::Const(0)),
1502                    ),
1503                    LocationConstraint::ComparisonExpression(
1504                        Box::new(IntegerExpression::Atom(Location::new("l2"))),
1505                        ComparisonOp::Eq,
1506                        Box::new(IntegerExpression::Const(0)),
1507                    ),
1508                ])
1509                .unwrap()
1510                .with_rules(vec![
1511                    RuleBuilder::new(0, Location::new("l0"), Location::new("l1"))
1512                        .with_action(
1513                            Action::new(
1514                                Variable::new("x"),
1515                                IntegerExpression::Atom(Variable::new("x"))
1516                                    + IntegerExpression::Const(1),
1517                            )
1518                            .unwrap(),
1519                        )
1520                        .build(),
1521                    RuleBuilder::new(1, Location::new("l0"), Location::new("l1"))
1522                        .with_action(
1523                            Action::new(
1524                                Variable::new("x"),
1525                                IntegerExpression::Atom(Variable::new("x"))
1526                                    - IntegerExpression::Const(1),
1527                            )
1528                            .unwrap(),
1529                        )
1530                        .build(),
1531                    RuleBuilder::new(2, Location::new("l0"), Location::new("l1"))
1532                        .with_action(Action::new_reset(Variable::new("x")))
1533                        .build(),
1534                    RuleBuilder::new(3, Location::new("l1"), Location::new("l2"))
1535                        .with_guard(BooleanVarConstraint::ComparisonExpression(
1536                            Box::new(IntegerExpression::Atom(Variable::new("x"))),
1537                            ComparisonOp::Geq,
1538                            Box::new(
1539                                IntegerExpression::Param(Parameter::new("n"))
1540                                    - IntegerExpression::Param(Parameter::new("t")),
1541                            ),
1542                        ))
1543                        .build(),
1544                    RuleBuilder::new(4, Location::new("l1"), Location::new("l2"))
1545                        .with_guard(BooleanVarConstraint::ComparisonExpression(
1546                            Box::new(IntegerExpression::Atom(Variable::new("x"))),
1547                            ComparisonOp::Geq,
1548                            Box::new(
1549                                IntegerExpression::Param(Parameter::new("n"))
1550                                    - IntegerExpression::Param(Parameter::new("t")),
1551                            ),
1552                        ))
1553                        .with_action(
1554                            Action::new(
1555                                Variable::new("x"),
1556                                IntegerExpression::Atom(Variable::new("x"))
1557                                    + IntegerExpression::Const(1),
1558                            )
1559                            .unwrap(),
1560                        )
1561                        .build(),
1562                ])
1563                .unwrap();
1564        let ta = ta_builder.build();
1565        let lia =
1566            taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
1567                ta.clone(),
1568            )
1569            .unwrap();
1570
1571        let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
1572            .build()
1573            .unwrap();
1574        let ata = interval_tas.next().unwrap();
1575
1576        let nxt = interval_tas.next();
1577        assert!(nxt.is_none(), "Got additional ta {}", nxt.unwrap());
1578        ata
1579    }
1580
1581    fn get_test_error_graph(ata: &'_ IntervalThresholdAutomaton) -> ZCSErrorGraph<'_> {
1582        let mgr = taco_bdd::BDDManager::default();
1583        let builder: ZCSBuilder = ZCSBuilder::new(&mgr, ata);
1584        let cs = builder.build();
1585
1586        let l0 = cs.get_sym_state_for_loc(&Location::new("l0"));
1587        let l1 = cs.get_sym_state_for_loc(&Location::new("l1"));
1588        let l2 = cs.get_sym_state_for_loc(&Location::new("l2"));
1589        let error_states = l0
1590            .complement()
1591            .intersection(&l1.complement())
1592            .intersection(&l2);
1593
1594        let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
1595        error_graph_builder.build()
1596    }
1597
1598    // initializes a steady error path that
1599    // that applies the step transitions r_0, r_1, r_0, r_3, r_2, r_4 and switches the interval for x from I_0 to I_1 to I_2
1600    // note that this path is spurious since r_2 resets x to 0
1601    fn get_test_steady_error_path<'a>(
1602        aut: &'a IntervalThresholdAutomaton,
1603        error_graph: &'a ZCSErrorGraph,
1604    ) -> SteadyErrorPath<'a> {
1605        let full_state = error_graph.new_empty_sym_state().complement();
1606        let true_bdd = full_state.set_of_states();
1607        let idle_sym_state = ZCSStates::new(true_bdd.clone());
1608
1609        let r_0 = aut.rules().find(|r| r.id() == 0).unwrap();
1610        let r_1 = aut.rules().find(|r| r.id() == 1).unwrap();
1611        let r_2 = aut.rules().find(|r| r.id() == 2).unwrap();
1612        let r_3 = aut.rules().find(|r| r.id() == 3).unwrap();
1613        let r_4 = aut.rules().find(|r| r.id() == 4).unwrap();
1614
1615        // I = [0,1)
1616        let interval_0 = Interval::new(
1617            IntervalBoundary::from_const(0),
1618            false,
1619            IntervalBoundary::from_const(1),
1620            true,
1621        );
1622
1623        // I = [1, n-t)
1624        let interval_1 = Interval::new(
1625            IntervalBoundary::from_const(1),
1626            false,
1627            IntervalBoundary::Bound(Threshold::new(
1628                [
1629                    (Parameter::new("n"), 1.into()),
1630                    (Parameter::new("t"), Fraction::new(1, 1, true)),
1631                ],
1632                0,
1633            )),
1634            true,
1635        );
1636        // I = [n-t, \infty)
1637        let interval_2 = Interval::new(
1638            IntervalBoundary::Bound(Threshold::new(
1639                [
1640                    (Parameter::new("n"), 1.into()),
1641                    (Parameter::new("t"), Fraction::new(1, 1, true)),
1642                ],
1643                0,
1644            )),
1645            false,
1646            IntervalBoundary::new_infty(),
1647            true,
1648        );
1649
1650        let intial_assignment = VariableAssignment::new_for_testing(HashMap::from([(
1651            Variable::new("x"),
1652            interval_0.clone(),
1653        )]));
1654        let sym_initial_assignment =
1655            SymbolicVariableAssignment::new(true_bdd.clone(), intial_assignment);
1656        let mut steady_error_path = SteadyErrorPath::new(
1657            SteadyPath::new(
1658                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1659                idle_sym_state.clone(),
1660                sym_initial_assignment,
1661            ),
1662            error_graph,
1663        );
1664
1665        let sym_assignment = SymbolicVariableAssignment::new(
1666            true_bdd.clone(),
1667            VariableAssignment::new_for_testing(HashMap::from([(
1668                Variable::new("x"),
1669                interval_1.clone(),
1670            )])),
1671        );
1672        steady_error_path.add_successor(
1673            &SymbolicTransition::new(true_bdd.clone(), r_0.clone()),
1674            SteadyPath::new(
1675                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1676                idle_sym_state.clone(),
1677                sym_assignment.clone(),
1678            ),
1679        );
1680
1681        steady_error_path.add_successor(
1682            &SymbolicTransition::new(true_bdd.clone(), r_1.clone()),
1683            SteadyPath::new(
1684                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1685                idle_sym_state.clone(),
1686                sym_assignment.clone(),
1687            ),
1688        );
1689
1690        let sym_assignment = SymbolicVariableAssignment::new(
1691            true_bdd.clone(),
1692            VariableAssignment::new_for_testing(HashMap::from([(
1693                Variable::new("x"),
1694                interval_2.clone(),
1695            )])),
1696        );
1697
1698        steady_error_path.add_successor(
1699            &SymbolicTransition::new(true_bdd.clone(), r_0.clone()),
1700            SteadyPath::new(
1701                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1702                idle_sym_state.clone(),
1703                sym_assignment.clone(),
1704            ),
1705        );
1706
1707        steady_error_path.add_successor(
1708            &SymbolicTransition::new(true_bdd.clone(), r_3.clone()),
1709            SteadyPath::new(vec![], idle_sym_state.clone(), sym_assignment),
1710        );
1711
1712        let sym_assignment = SymbolicVariableAssignment::new(
1713            true_bdd.clone(),
1714            VariableAssignment::new_for_testing(HashMap::from([(
1715                Variable::new("x"),
1716                interval_0.clone(),
1717            )])),
1718        );
1719
1720        steady_error_path.add_successor(
1721            &SymbolicTransition::new(true_bdd.clone(), r_2.clone()),
1722            SteadyPath::new(vec![], idle_sym_state.clone(), sym_assignment.clone()),
1723        );
1724
1725        steady_error_path.add_successor(
1726            &SymbolicTransition::new(true_bdd.clone(), r_4.clone()),
1727            SteadyPath::new(vec![], idle_sym_state, sym_assignment),
1728        );
1729
1730        steady_error_path
1731    }
1732
1733    // initializes all smt variables and constraints of the test_automaton
1734    // for an abstract error path which applies r_0, r_1, r_3, r_2, r_4
1735    // needs to be called with get_test_automaton and get_test_abstract_path
1736    ///
1737    /// if initialized with get_test_abstract_path_one_step returns the encoder
1738    /// for the abstract error path which only applies r_0
1739    fn get_initialized_test_smt_encoder<'a>(
1740        aut: &'a IntervalThresholdAutomaton,
1741        path: &'a SteadyErrorPath,
1742    ) -> SMTEncoderSteady<'a> {
1743        let solver = SMTSolverBuilder::default();
1744        SMTEncoderSteady::new(solver, aut, path)
1745    }
1746
1747    // initializes a steady error path for one step
1748    // that applies step transition r_0 and switches the interval for x from I_0 to I_1
1749    fn get_test_steady_error_path_one_step<'a>(
1750        aut: &'a IntervalThresholdAutomaton,
1751        error_graph: &'a ZCSErrorGraph,
1752    ) -> SteadyErrorPath<'a> {
1753        let full_state = error_graph.new_empty_sym_state().complement();
1754        let true_bdd = full_state.set_of_states();
1755        let idle_sym_state = ZCSStates::new(true_bdd.clone());
1756
1757        let r_0 = aut.rules().find(|r| r.id() == 0).unwrap();
1758
1759        // I = [0,1)
1760        let interval_0 = Interval::new(
1761            IntervalBoundary::from_const(0),
1762            false,
1763            IntervalBoundary::from_const(1),
1764            true,
1765        );
1766
1767        // I = [1, n-t)
1768        let interval_1 = Interval::new(
1769            IntervalBoundary::from_const(1),
1770            false,
1771            IntervalBoundary::Bound(Threshold::new(
1772                [
1773                    (Parameter::new("n"), 1.into()),
1774                    (Parameter::new("t"), Fraction::new(1, 1, true)),
1775                ],
1776                0,
1777            )),
1778            true,
1779        );
1780
1781        let intial_assignment = VariableAssignment::new_for_testing(HashMap::from([(
1782            Variable::new("x"),
1783            interval_0.clone(),
1784        )]));
1785        let sym_initial_assignment =
1786            SymbolicVariableAssignment::new(true_bdd.clone(), intial_assignment);
1787        let mut steady_error_path = SteadyErrorPath::new(
1788            SteadyPath::new(
1789                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1790                idle_sym_state.clone(),
1791                sym_initial_assignment,
1792            ),
1793            error_graph,
1794        );
1795
1796        let sym_assignment = SymbolicVariableAssignment::new(
1797            true_bdd.clone(),
1798            VariableAssignment::new_for_testing(HashMap::from([(
1799                Variable::new("x"),
1800                interval_1.clone(),
1801            )])),
1802        );
1803        steady_error_path.add_successor(
1804            &SymbolicTransition::new(true_bdd.clone(), r_0.clone()),
1805            SteadyPath::new(
1806                vec![&SymbolicTransition::new(true_bdd.clone(), r_0.clone())],
1807                idle_sym_state.clone(),
1808                sym_assignment.clone(),
1809            ),
1810        );
1811
1812        steady_error_path
1813    }
1814
1815    #[test]
1816    fn test_new_rule_counter() {
1817        let solver = SMTSolverBuilder::default();
1818
1819        let r_0 = RuleBuilder::new(8, Location::new("l_0"), Location::new("l_1")).build();
1820        let r_1 = RuleBuilder::new(9, Location::new("l_1"), Location::new("l_0")).build();
1821
1822        let aut = get_test_automaton();
1823        let error_graph = get_test_error_graph(&aut);
1824        let path = get_test_steady_error_path(&aut, &error_graph);
1825        let mut encoder = SMTEncoderSteady::new(solver, &aut, &path);
1826
1827        let rules = vec![&r_0, &r_1, &r_0];
1828        encoder.rule_counter_to_smt.clear();
1829        encoder.initialize_rule_counters(rules.into_iter());
1830
1831        let r_0_0 = encoder.rule_counter_to_smt[0].1;
1832        let r_1_1 = encoder.rule_counter_to_smt[1].1;
1833        let r_0_2 = encoder.rule_counter_to_smt[2].1;
1834
1835        assert_eq!(
1836            format!("{}", encoder.ctx.smt_solver.display(r_0_0)),
1837            "rule_8_0"
1838        );
1839        assert_eq!(
1840            format!("{}", encoder.ctx.smt_solver.display(r_1_1)),
1841            "rule_9_1"
1842        );
1843        assert_eq!(
1844            format!("{}", encoder.ctx.smt_solver.display(r_0_2)),
1845            "rule_8_2"
1846        );
1847    }
1848
1849    #[test]
1850    fn test_initialize_rule_counters() {
1851        let solver = SMTSolverBuilder::default();
1852
1853        let r_0 = RuleBuilder::new(8, Location::new("l_0"), Location::new("l_1")).build();
1854        let r_1 = RuleBuilder::new(9, Location::new("l_1"), Location::new("l_0")).build();
1855
1856        let aut = get_test_automaton();
1857        let error_graph = get_test_error_graph(&aut);
1858        let path = get_test_steady_error_path(&aut, &error_graph);
1859        let mut encoder = SMTEncoderSteady::new(solver, &aut, &path);
1860
1861        let vec = vec![&r_0, &r_1, &r_0];
1862
1863        encoder.rule_counter_to_smt.clear();
1864
1865        encoder.initialize_rule_counters(vec.into_iter());
1866
1867        assert_eq!(
1868            format!(
1869                "{}",
1870                encoder
1871                    .ctx
1872                    .smt_solver
1873                    .display(encoder.rule_counter_to_smt.first().unwrap().1)
1874            ),
1875            "rule_8_0"
1876        );
1877        assert_eq!(
1878            format!(
1879                "{}",
1880                encoder
1881                    .ctx
1882                    .smt_solver
1883                    .display(encoder.rule_counter_to_smt.get(1).unwrap().1)
1884            ),
1885            "rule_9_1"
1886        );
1887        assert_eq!(
1888            format!(
1889                "{}",
1890                encoder
1891                    .ctx
1892                    .smt_solver
1893                    .display(encoder.rule_counter_to_smt.get(2).unwrap().1)
1894            ),
1895            "rule_8_2"
1896        );
1897    }
1898
1899    #[test]
1900    fn test_encode_step_transition() {
1901        let aut = get_test_automaton();
1902        let error_graph = get_test_error_graph(&aut);
1903        let path = get_test_steady_error_path(&aut, &error_graph);
1904
1905        let encoder = get_initialized_test_smt_encoder(&aut, &path);
1906        // increment step transition
1907        let encoding = encoder.encode_step_transition(2, 1);
1908
1909        let smt = format!("{}", encoder.ctx.smt_solver.display(encoding));
1910
1911        assert!(smt.contains("(= rule_0_1 1)"));
1912        assert!(smt.contains("(= loc_l0_2 (- loc_l0_1 rule_0_1))"));
1913        assert!(smt.contains("(= loc_l1_2 (+ loc_l1_1 rule_0_1))"));
1914        assert!(smt.contains("(= loc_l2_2 loc_l2_1)"));
1915        assert!(smt.contains("(= var_x_2 (+ var_x_1 (* rule_0_1 1)))"));
1916
1917        // decrement step transition
1918        let encoding = encoder.encode_step_transition(4, 3);
1919
1920        let smt = format!("{}", encoder.ctx.smt_solver.display(encoding));
1921        assert!(smt.contains("(= rule_1_3 1)"));
1922        assert!(smt.contains("(= loc_l0_4 (- loc_l0_3 rule_1_3))"));
1923        assert!(smt.contains("(= loc_l1_4 (+ loc_l1_3 rule_1_3))"));
1924        assert!(smt.contains("(= loc_l2_4 loc_l2_3)"));
1925        assert!(smt.contains("(= var_x_4 (- var_x_3 (* rule_1_3 1)))"));
1926
1927        // reset step transition
1928        let encoding = encoder.encode_step_transition(10, 8);
1929
1930        let smt = format!("{}", encoder.ctx.smt_solver.display(encoding));
1931        assert!(smt.contains("(= rule_2_8 1)"));
1932        assert!(smt.contains("(= loc_l0_10 (- loc_l0_9 rule_2_8))"));
1933        assert!(smt.contains("(= loc_l1_10 (+ loc_l1_9 rule_2_8))"));
1934        assert!(smt.contains("(= loc_l2_10 loc_l2_9))"));
1935        assert!(smt.contains("(= var_x_10 0)"));
1936    }
1937
1938    #[test]
1939    fn test_encode_update_shared_vars_inc() {
1940        let aut = get_test_automaton();
1941        let error_graph = get_test_error_graph(&aut);
1942        let path = get_test_steady_error_path(&aut, &error_graph);
1943        let encoder = SMTEncoderSteady::new(SMTSolverBuilder::default(), &aut, &path);
1944        let shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 1);
1945        let next_shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 2);
1946        let rule_smt = encoder.rule_counter_to_smt.get(1).unwrap().1;
1947        let encoding = encoder.encode_update_shared_var(
1948            &shared_smt,
1949            &next_shared_smt,
1950            rule_smt,
1951            &UpdateExpression::Inc(1),
1952        );
1953
1954        let correct_encoding = "(= var_x_2 (+ var_x_1 (* rule_0_1 1)))";
1955
1956        assert_eq!(
1957            format!("{}", encoder.ctx.smt_solver.display(encoding)),
1958            correct_encoding
1959        );
1960    }
1961
1962    #[test]
1963    fn test_encode_update_shared_vars_dec() {
1964        let aut = get_test_automaton();
1965        let error_graph = get_test_error_graph(&aut);
1966        let path = get_test_steady_error_path(&aut, &error_graph);
1967        let encoder = SMTEncoderSteady::new(SMTSolverBuilder::default(), &aut, &path);
1968        let shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 1);
1969        let next_shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 2);
1970        let rule_smt = encoder.rule_counter_to_smt.get(1).unwrap().1;
1971        let encoding = encoder.encode_update_shared_var(
1972            &shared_smt,
1973            &next_shared_smt,
1974            rule_smt,
1975            &UpdateExpression::Dec(1),
1976        );
1977
1978        let correct_encoding = "(= var_x_2 (- var_x_1 (* rule_0_1 1)))";
1979
1980        assert_eq!(
1981            format!("{}", encoder.ctx.smt_solver.display(encoding)),
1982            correct_encoding
1983        );
1984    }
1985
1986    #[test]
1987    fn test_encode_update_shared_vars_reset() {
1988        let aut = get_test_automaton();
1989        let error_graph = get_test_error_graph(&aut);
1990        let path = get_test_steady_error_path(&aut, &error_graph);
1991        let encoder = SMTEncoderSteady::new(SMTSolverBuilder::default(), &aut, &path);
1992        let shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 1);
1993        let next_shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 2);
1994        let rule_smt = encoder.rule_counter_to_smt.get(1).unwrap().1;
1995        let encoding = encoder.encode_update_shared_var(
1996            &shared_smt,
1997            &next_shared_smt,
1998            rule_smt,
1999            &UpdateExpression::Reset,
2000        );
2001
2002        let correct_encoding = "(= var_x_2 0)";
2003
2004        assert_eq!(
2005            format!("{}", encoder.ctx.smt_solver.display(encoding)),
2006            correct_encoding
2007        );
2008    }
2009
2010    #[test]
2011    fn test_encode_update_shared_vars_unchanged() {
2012        let aut = get_test_automaton();
2013        let error_graph = get_test_error_graph(&aut);
2014        let path = get_test_steady_error_path(&aut, &error_graph);
2015        let encoder = SMTEncoderSteady::new(SMTSolverBuilder::default(), &aut, &path);
2016        let shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 1);
2017        let next_shared_smt = encoder.ctx.get_indexed_shared_smt(&Variable::new("x"), 2);
2018        let rule_smt = encoder.rule_counter_to_smt.get(1).unwrap().1;
2019        let encoding = encoder.encode_update_shared_var(
2020            &shared_smt,
2021            &next_shared_smt,
2022            rule_smt,
2023            &UpdateExpression::Unchanged,
2024        );
2025
2026        let correct_encoding = "(= var_x_2 var_x_1)";
2027
2028        assert_eq!(
2029            format!("{}", encoder.ctx.smt_solver.display(encoding)),
2030            correct_encoding
2031        );
2032    }
2033
2034    #[test]
2035    fn test_encode_resilience_conditions() {
2036        let solver = SMTSolverBuilder::default();
2037        let aut = get_test_automaton();
2038        let error_graph = get_test_error_graph(&aut);
2039        let path = get_test_steady_error_path(&aut, &error_graph);
2040        let mut encoder = SMTEncoderSteady::new(solver, &aut, &path);
2041
2042        let rc_1 = ParameterConstraint::ComparisonExpression(
2043            Box::new(IntegerExpression::Atom(Parameter::new("n"))),
2044            ComparisonOp::Gt,
2045            Box::new(IntegerExpression::Const(3) * IntegerExpression::Param(Parameter::new("t"))),
2046        );
2047
2048        let rc_2 = ParameterConstraint::ComparisonExpression(
2049            Box::new(IntegerExpression::Atom(Parameter::new("t"))),
2050            ComparisonOp::Geq,
2051            Box::new(IntegerExpression::Param(Parameter::new("f"))),
2052        );
2053        let rcs = vec![&rc_1, &rc_2];
2054
2055        let res = encoder.encode_resilience_conditions(rcs.into_iter());
2056
2057        let correct_encoding = "(and (and true (> param_n (* 3 param_t))) (>= param_t param_f))";
2058
2059        assert_eq!(
2060            format!("{}", encoder.ctx.smt_solver.display(res)),
2061            correct_encoding
2062        );
2063    }
2064
2065    #[test]
2066    fn test_encode_loc_conditions() {
2067        let solver = SMTSolverBuilder::default();
2068        let aut = get_test_automaton();
2069        let error_graph = get_test_error_graph(&aut);
2070        let path = get_test_steady_error_path(&aut, &error_graph);
2071        let mut encoder = SMTEncoderSteady::new(solver, &aut, &path);
2072
2073        let loc_constr_1 = LocationConstraint::ComparisonExpression(
2074            Box::new(IntegerExpression::Atom(Location::new("l0"))),
2075            ComparisonOp::Eq,
2076            Box::new(
2077                IntegerExpression::Param(Parameter::new("n"))
2078                    - IntegerExpression::Param(Parameter::new("t")),
2079            ),
2080        );
2081        let loc_constr_2 = LocationConstraint::ComparisonExpression(
2082            Box::new(IntegerExpression::Atom(Location::new("l1"))),
2083            ComparisonOp::Eq,
2084            Box::new(IntegerExpression::Const(0)),
2085        );
2086        let loc_constr_3 = LocationConstraint::ComparisonExpression(
2087            Box::new(IntegerExpression::Atom(Location::new("l2"))),
2088            ComparisonOp::Eq,
2089            Box::new(IntegerExpression::Const(0)),
2090        );
2091
2092        let init_loc_constrs = vec![&loc_constr_1, &loc_constr_2, &loc_constr_3];
2093
2094        let res = encoder.encode_loc_conditions(init_loc_constrs.into_iter());
2095
2096        let correct_encoding =
2097            "(and (and (and true (= loc_l0_0 (- param_n param_t))) (= loc_l1_0 0)) (= loc_l2_0 0))";
2098
2099        assert_eq!(
2100            format!("{}", encoder.ctx.smt_solver.display(res)),
2101            correct_encoding
2102        );
2103    }
2104
2105    #[test]
2106    fn test_encode_var_conditions() {
2107        let aut = get_test_automaton();
2108        let error_graph = get_test_error_graph(&aut);
2109        let path = get_test_steady_error_path(&aut, &error_graph);
2110        let mut encoder = SMTEncoderSteady::new(SMTSolverBuilder::default(), &aut, &path);
2111
2112        let init_var_constr = BooleanVarConstraint::ComparisonExpression(
2113            Box::new(IntegerExpression::Atom(Variable::new("x"))),
2114            ComparisonOp::Eq,
2115            Box::new(IntegerExpression::Const(0)),
2116        );
2117
2118        let res = encoder.encode_var_conditions(vec![&init_var_constr].into_iter());
2119
2120        let correct_encoding = "(and true (= var_x_0 0))";
2121
2122        assert_eq!(
2123            format!("{}", encoder.ctx.smt_solver.display(res)),
2124            correct_encoding
2125        );
2126    }
2127
2128    #[test]
2129    fn test_encode_shared_interval_left_infty() {
2130        let aut = get_test_automaton();
2131        let error_graph = get_test_error_graph(&aut);
2132        let path = get_test_steady_error_path(&aut, &error_graph);
2133        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2134
2135        // I = (infty,infty)
2136        let interval = Interval::new(
2137            IntervalBoundary::new_infty(),
2138            true,
2139            IntervalBoundary::new_infty(),
2140            true,
2141        );
2142        encoder.ctx.set_curr_index(1);
2143        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2144
2145        assert_eq!(format!("{}", encoder.ctx.smt_solver.display(res)), "false");
2146    }
2147
2148    #[test]
2149    fn test_encode_shared_interval_right_infty() {
2150        let aut = get_test_automaton();
2151        let error_graph = get_test_error_graph(&aut);
2152        let path = get_test_steady_error_path(&aut, &error_graph);
2153        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2154
2155        // I = [n-t,infty)
2156        let interval = Interval::new(
2157            IntervalBoundary::Bound(Threshold::new(
2158                [
2159                    (Parameter::new("n"), 1.into()),
2160                    (Parameter::new("t"), Fraction::new(1, 1, true)),
2161                ],
2162                0,
2163            )),
2164            false,
2165            IntervalBoundary::new_infty(),
2166            true,
2167        );
2168
2169        encoder.ctx.set_curr_index(1);
2170        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2171
2172        assert_eq!(
2173            format!("{}", encoder.ctx.smt_solver.display(res)),
2174            "(and (>= var_x_1 (+ param_n (* (- 1) param_t))) true)"
2175        );
2176    }
2177
2178    #[test]
2179    fn test_encode_shared_interval_interval_left_closed_right_closed() {
2180        let aut = get_test_automaton();
2181        let error_graph = get_test_error_graph(&aut);
2182        let path = get_test_steady_error_path(&aut, &error_graph);
2183        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2184
2185        // I = [0,n-t]
2186        let interval = Interval::new(
2187            IntervalBoundary::from_const(0),
2188            false,
2189            IntervalBoundary::Bound(Threshold::new(
2190                [
2191                    (Parameter::new("n"), 1.into()),
2192                    (Parameter::new("t"), Fraction::new(1, 1, true)),
2193                ],
2194                0,
2195            )),
2196            false,
2197        );
2198
2199        encoder.ctx.set_curr_index(1);
2200        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2201
2202        assert_eq!(
2203            format!("{}", encoder.ctx.smt_solver.display(res)),
2204            "(and (>= var_x_1 0) (<= var_x_1 (+ param_n (* (- 1) param_t))))"
2205        );
2206    }
2207
2208    #[test]
2209    fn test_encode_shared_interval_interval_left_open_right_closed() {
2210        let aut = get_test_automaton();
2211        let error_graph = get_test_error_graph(&aut);
2212        let path = get_test_steady_error_path(&aut, &error_graph);
2213        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2214
2215        // I = (0,n-t]
2216        let interval = Interval::new(
2217            IntervalBoundary::from_const(0),
2218            true,
2219            IntervalBoundary::Bound(Threshold::new(
2220                [
2221                    (Parameter::new("n"), 1.into()),
2222                    (Parameter::new("t"), Fraction::new(1, 1, true)),
2223                ],
2224                0,
2225            )),
2226            false,
2227        );
2228
2229        encoder.ctx.set_curr_index(1);
2230        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2231
2232        assert_eq!(
2233            format!("{}", encoder.ctx.smt_solver.display(res)),
2234            "(and (> var_x_1 0) (<= var_x_1 (+ param_n (* (- 1) param_t))))"
2235        );
2236    }
2237
2238    #[test]
2239    fn test_encode_shared_interval_interval_left_closed_right_open() {
2240        let aut = get_test_automaton();
2241        let error_graph = get_test_error_graph(&aut);
2242        let path = get_test_steady_error_path(&aut, &error_graph);
2243        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2244
2245        // I = [0,n-t)
2246        let interval = Interval::new(
2247            IntervalBoundary::from_const(0),
2248            false,
2249            IntervalBoundary::Bound(Threshold::new(
2250                [
2251                    (Parameter::new("n"), 1.into()),
2252                    (Parameter::new("t"), Fraction::new(1, 1, true)),
2253                ],
2254                0,
2255            )),
2256            true,
2257        );
2258
2259        encoder.ctx.set_curr_index(1);
2260        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2261
2262        assert_eq!(
2263            format!("{}", encoder.ctx.smt_solver.display(res)),
2264            "(and (>= var_x_1 0) (< var_x_1 (+ param_n (* (- 1) param_t))))"
2265        );
2266    }
2267
2268    #[test]
2269    fn test_encode_shared_interval_interval_left_open_right_open() {
2270        let aut = get_test_automaton();
2271        let error_graph = get_test_error_graph(&aut);
2272        let path = get_test_steady_error_path(&aut, &error_graph);
2273        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2274
2275        // I = (0,n-t)
2276        let interval = Interval::new(
2277            IntervalBoundary::from_const(0),
2278            true,
2279            IntervalBoundary::Bound(Threshold::new(
2280                [
2281                    (Parameter::new("n"), 1.into()),
2282                    (Parameter::new("t"), Fraction::new(1, 1, true)),
2283                ],
2284                0,
2285            )),
2286            true,
2287        );
2288
2289        encoder.ctx.set_curr_index(1);
2290        let res = encoder.encode_shared_interval(Variable::new("x"), interval);
2291
2292        assert_eq!(
2293            format!("{}", encoder.ctx.smt_solver.display(res)),
2294            "(and (> var_x_1 0) (< var_x_1 (+ param_n (* (- 1) param_t))))"
2295        );
2296    }
2297
2298    #[test]
2299    fn test_encode_abstract_path() {
2300        let aut = get_test_automaton();
2301        let error_graph = get_test_error_graph(&aut);
2302        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2303        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2304
2305        let res = encoder.encode_steady_error_path();
2306
2307        assert_eq!(
2308            format!("{}", encoder.ctx.smt_solver.display(res)),
2309            "(and (and (and (and true (and (>= var_x_0 0) (< var_x_0 1))) (and (>= var_x_1 0) (< var_x_1 1))) (and (>= var_x_2 1) (< var_x_2 (+ param_n (* (- 1) param_t))))) (and (>= var_x_3 1) (< var_x_3 (+ param_n (* (- 1) param_t)))))"
2310        );
2311    }
2312
2313    #[test]
2314    fn test_set_curr_index() {
2315        let aut = get_test_automaton();
2316        let error_graph = get_test_error_graph(&aut);
2317        let path = get_test_steady_error_path(&aut, &error_graph);
2318        let mut ctx = get_initialized_test_smt_encoder(&aut, &path).ctx;
2319
2320        assert_eq!(ctx.curr_index, 0);
2321
2322        ctx.set_curr_index(3);
2323
2324        assert_eq!(ctx.curr_index, 3);
2325    }
2326
2327    #[test]
2328    fn test_encode_step_processes_self_loop() {
2329        let solver = SMTSolverBuilder::default();
2330
2331        let ta_builder =
2332            taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
2333                .with_locations(vec![Location::new("l0"), Location::new("l1"), Location::new("l2")])
2334                .unwrap()
2335                .initialize()
2336                .with_rules([RuleBuilder::new(0, Location::new("l0"), Location::new("l0")).build(), RuleBuilder::new(1, Location::new("l0"), Location::new("l1")).build(), RuleBuilder::new(2, Location::new("l0"), Location::new("l2")).build()])
2337                .unwrap();
2338        let ta = ta_builder.build();
2339        let lia =
2340            taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
2341                ta.clone(),
2342            )
2343            .unwrap();
2344
2345        let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
2346            .build()
2347            .unwrap();
2348        let ata = interval_tas.next().unwrap();
2349
2350        let nxt = interval_tas.next();
2351
2352        assert!(nxt.is_none(), "Got additional ta {}", nxt.unwrap());
2353
2354        let error_graph = get_test_error_graph(&ata);
2355        let path = get_test_steady_error_path_one_step(&ata, &error_graph);
2356
2357        let encoder = SMTEncoderSteady::new(solver, &ata, &path);
2358
2359        let res = encoder.encode_step_transition(2, 1);
2360
2361        let smt = format!("{}", encoder.ctx.smt_solver.display(res));
2362        assert!(smt.contains("(= rule_0_1 1)"));
2363        assert!(smt.contains("(= loc_l0_2 loc_l0_1))"));
2364        assert!(smt.contains("(= loc_l1_2 loc_l1_1))"));
2365        assert!(smt.contains("(= loc_l2_2 loc_l2_1))"));
2366    }
2367
2368    #[test]
2369    fn test_encode_steady_path() {
2370        let aut = get_test_automaton();
2371        let error_graph = get_test_error_graph(&aut);
2372        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2373        let encoder = get_initialized_test_smt_encoder(&aut, &path);
2374
2375        let res = encoder.encode_steady_path(0, 0, path.steady_paths().next().unwrap());
2376
2377        let smt = format!("{}", encoder.ctx.smt_solver.display(res));
2378
2379        assert!(smt.contains("(= loc_l2_1 loc_l2_0)"));
2380        assert!(smt.contains("(= loc_l0_1 (- loc_l0_0 rule_0_0))"));
2381        assert!(smt.contains("(= loc_l1_1 (+ loc_l1_0 rule_0_0))"));
2382        assert!(smt.contains("(= var_x_1 (+ var_x_0 (* rule_0_0 1)))"));
2383        assert!(smt.contains("(=> (> rule_0_0 0) (and (> rule_0_0 0) (> loc_l0_0 0)))"));
2384    }
2385
2386    #[test]
2387    fn test_encode_steady_path_move_processes() {
2388        let aut = get_test_automaton();
2389        let error_graph = get_test_error_graph(&aut);
2390        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2391        let encoder = get_initialized_test_smt_encoder(&aut, &path);
2392
2393        let res =
2394            encoder.encode_steady_path_move_processes(0, 0, path.steady_paths().next().unwrap());
2395
2396        let smt = format!("{}", encoder.ctx.smt_solver.display(res));
2397        assert!(smt.contains("(= loc_l1_1 (+ loc_l1_0 rule_0_0))"));
2398        assert!(smt.contains("(= loc_l2_1 loc_l2_0)"));
2399        assert!(smt.contains("(= loc_l0_1 (- loc_l0_0 rule_0_0))"));
2400    }
2401
2402    #[test]
2403    fn test_encode_steady_path_update_shared_variables() {
2404        let aut = get_test_automaton();
2405        let error_graph = get_test_error_graph(&aut);
2406        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2407        let encoder = get_initialized_test_smt_encoder(&aut, &path);
2408
2409        let res = encoder.encode_steady_path_update_shared_variables(
2410            0,
2411            0,
2412            path.steady_paths().next().unwrap(),
2413        );
2414
2415        assert_eq!(
2416            format!("{}", encoder.ctx.smt_solver.display(res)),
2417            "(and true (= var_x_1 (+ var_x_0 (* rule_0_0 1))))"
2418        );
2419    }
2420
2421    #[test]
2422    fn test_abstract_is_non_spurious_without_spec() {
2423        let aut = get_test_automaton();
2424        let error_graph = get_test_error_graph(&aut);
2425        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2426        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2427
2428        let res = encoder.steady_is_non_spurious(None);
2429
2430        assert!(res.is_non_spurious());
2431    }
2432
2433    #[test]
2434    fn test_abstract_is_non_spurious_with_spec() {
2435        let aut = get_test_automaton();
2436        let error_graph = get_test_error_graph(&aut);
2437        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2438        let mut encoder = get_initialized_test_smt_encoder(&aut, &path);
2439
2440        let res = encoder.steady_is_non_spurious(Some(
2441            &TargetConfig::new_cover(HashSet::from([Location::new("l1"), Location::new("l2")]))
2442                .unwrap()
2443                .into_disjunct_with_name("cover"),
2444        ));
2445        assert!(!res.is_non_spurious());
2446    }
2447
2448    #[test]
2449    fn test_extract_non_spurious_concrete_path_no_param_assignment() {
2450        let aut = get_test_automaton();
2451        let error_graph = get_test_error_graph(&aut);
2452        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2453        let mut smt_encoder = get_initialized_test_smt_encoder(&aut, &path);
2454
2455        let res = smt_encoder.extract_non_spurious_concrete_path(SMTSolution::UNSAT);
2456
2457        assert!(res.is_err());
2458
2459        assert!(matches!(
2460            res.clone().unwrap_err(),
2461            SMTEncoderError::NoSatParamAssign(_),
2462        ));
2463    }
2464
2465    #[test]
2466    fn test_extract_non_spurious_concrete_path() {
2467        let aut = get_test_automaton();
2468        let error_graph = get_test_error_graph(&aut);
2469        let path = get_test_steady_error_path_one_step(&aut, &error_graph);
2470        let mut smt_encoder = get_initialized_test_smt_encoder(&aut, &path);
2471
2472        smt_encoder.steady_is_non_spurious(None);
2473
2474        let res = smt_encoder.extract_non_spurious_concrete_path(SMTSolution::SAT);
2475
2476        assert!(res.is_ok());
2477
2478        let path = res.unwrap();
2479
2480        assert!((path.parameter_values().count() != 0));
2481        assert!(path.get_parameter_value(&Parameter::new("n")).unwrap() > 0);
2482
2483        assert_eq!(path.transitions().count(), 1);
2484        assert_eq!(path.transitions().next().unwrap().rule().id(), 0);
2485
2486        assert_eq!(path.configurations().count(), 2);
2487
2488        let configs = path.configurations().collect::<Vec<_>>();
2489
2490        let conf_0 = configs.first().unwrap();
2491        let conf_1 = configs.get(1).unwrap();
2492
2493        assert_eq!(conf_0.get_variable_value(&Variable::new("x")), 0);
2494        assert!(conf_1.get_variable_value(&Variable::new("x")) > 0);
2495
2496        assert_eq!(conf_0.get_processes_in_location(&Location::new("l2")), 0);
2497        assert_eq!(conf_0.get_processes_in_location(&Location::new("l1")), 0);
2498        assert_eq!(conf_1.get_processes_in_location(&Location::new("l2")), 0);
2499        assert!(conf_1.get_processes_in_location(&Location::new("l1")) > 0);
2500        assert_eq!(
2501            conf_1.get_processes_in_location(&Location::new("l0")),
2502            conf_0.get_processes_in_location(&Location::new("l0"))
2503                - conf_1.get_processes_in_location(&Location::new("l1"))
2504        );
2505    }
2506}