Skip to main content

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