taco_smt_encoder/expression_encoding/
step_ctx.rs

1//! Module for encoding possible paths between configurations in a threshold
2//! automaton
3//!
4//! This module implements an encoding of potential paths in an (extended)
5//! threshold automaton. The encoding is described in the paper
6//! ["Complexity of Verification and Synthesis of Threshold Automata"](https://arxiv.org/abs/2002.08507)
7//! by A. R. Balasubramanian, Javier Esparza, Marijana Lazic.
8//!
9//! The paper does not provide a complete encoding of the threshold automata,
10//! instead many implementation details are not provided. As we were not
11//! provided with the source code (nor an executable) of the implementation used
12//! in the paper, we  had to make some assumptions about the implementation
13//! details.
14//!
15//! We will explicitly mark functions that are not directly described in the
16//! paper.
17
18use std::{
19    collections::{BTreeMap, HashMap},
20    fmt,
21    rc::Rc,
22};
23
24use taco_threshold_automaton::{
25    ActionDefinition, RuleDefinition, ThresholdAutomaton, VariableConstraint,
26    expressions::{Location, Parameter, Variable},
27    general_threshold_automaton::{Rule, UpdateExpression},
28};
29
30use crate::{
31    SMTExpr, SMTSolution, SMTSolver,
32    expression_encoding::{DeclaresVariable, EncodeToSMT, SMTSolverError, SMTVariableContext},
33};
34
35/// Trait implemented by symbolic steps
36pub trait StepSMT: SMTVariableContext<Rule> {
37    /// Returns the number of times a rule has been applied in the solution to
38    /// this context
39    fn get_rules_applied(
40        &self,
41        solver: &mut SMTSolver,
42        res: SMTSolution,
43    ) -> Result<impl Iterator<Item = (Rule, u32)>, SMTSolverError> {
44        let rules_used = self.get_solution(solver, res)?;
45
46        Ok(rules_used.into_iter().filter(|(_, n)| *n > 0))
47    }
48}
49
50/// Context for rule applications in a path on a threshold automaton
51///
52/// This context creates SMT variables for rule applications for all rules that
53/// were supplied during the context creation.
54///
55/// It then allows to encode the possible paths using the
56/// [`LazyStepContext::encode_phi_step`] and
57/// [`LazyStepContext::encode_phi_steady`] functions.
58#[derive(Debug, Clone)]
59pub struct LazyStepContext<TA, C>
60where
61    TA: ThresholdAutomaton,
62    C: SMTVariableContext<Parameter> + SMTVariableContext<Location> + SMTVariableContext<Variable>,
63{
64    /// index of the configuration
65    index: usize,
66    /// variables for rules
67    rule_vars: BTreeMap<Rule, SMTExpr>,
68    /// threshold automaton
69    ta: Rc<TA>,
70    /// previous configuration
71    prev_config: Rc<C>,
72    /// current configuration
73    curr_config: Rc<C>,
74}
75
76impl<TA, C> SMTVariableContext<Rule> for LazyStepContext<TA, C>
77where
78    TA: ThresholdAutomaton,
79    C: SMTVariableContext<Parameter> + SMTVariableContext<Location> + SMTVariableContext<Variable>,
80{
81    fn get_expr_for(&self, expr: &Rule) -> Result<SMTExpr, SMTSolverError> {
82        self.rule_vars
83            .get(expr)
84            .cloned()
85            .ok_or_else(|| SMTSolverError::UndeclaredRule(expr.clone()))
86    }
87
88    fn get_exprs<'a>(&'a self) -> impl IntoIterator<Item = &'a Rule>
89    where
90        Rule: 'a,
91    {
92        self.rule_vars.keys()
93    }
94
95    fn get_solution<'a>(
96        &'a self,
97        solver: &mut crate::SMTSolver,
98        res: crate::SMTSolution,
99    ) -> Result<HashMap<Rule, u32>, super::SMTSolverError>
100    where
101        Rule: 'a + Eq + std::hash::Hash + Clone,
102    {
103        match res {
104            crate::SMTSolution::UNSAT => Err(super::SMTSolverError::ExtractionFromUnsat),
105            crate::SMTSolution::SAT => {
106                let expr_vec = self
107                    .get_exprs()
108                    .into_iter()
109                    .map(|t| self.get_expr_for(t))
110                    .collect::<Result<Vec<_>, _>>()?;
111
112                let solution = solver.get_value(expr_vec)?;
113
114                solution
115                    .into_iter()
116                    .zip(self.get_exprs())
117                    .map(|((_, sol), t)| {
118                        let solution_value = solver.get_u32(sol).ok_or_else(|| {
119                            let sol = solver.display(sol);
120                            super::SMTSolverError::SolutionExtractionParseIntError(sol.to_string())
121                        })?;
122
123                        Ok((t.clone(), solution_value))
124                    })
125                    .collect::<Result<HashMap<Rule, u32>, super::SMTSolverError>>()
126            }
127        }
128    }
129}
130
131impl<TA, C> LazyStepContext<TA, C>
132where
133    TA: ThresholdAutomaton,
134    C: SMTVariableContext<Parameter> + SMTVariableContext<Location> + SMTVariableContext<Variable>,
135{
136    /// Create a new step context for encoding a step between two configurations
137    /// σ and σ'
138    ///
139    /// A step context stores the rule variables that are needed to specify the
140    /// transition between two configurations of a threshold automaton.
141    pub fn new(
142        rules_to_appl: impl Iterator<Item = Rule>,
143        ta: Rc<TA>,
144        prev_config: Rc<C>,
145        curr_config: Rc<C>,
146        index: usize,
147        solver: &mut SMTSolver,
148    ) -> LazyStepContext<TA, C> {
149        let rule_vars = rules_to_appl
150            .into_iter()
151            .map(|r| {
152                let rule = r
153                    .declare_variable(solver, index as u32)
154                    .expect("Failed to declare rule variable");
155                solver
156                    .assert(solver.gte(rule, solver.numeral(0)))
157                    .expect("Failed to assert rule application >= 0");
158
159                (r.clone(), rule)
160            })
161            .collect();
162
163        Self {
164            index,
165            ta,
166            rule_vars,
167            prev_config,
168            curr_config,
169        }
170    }
171
172    /// Encodes the ϕ_{base} of the step context
173    ///
174    /// The encoding of phi base is slightly modified to require less
175    /// parameters, the encoding does not include:
176    /// - the constraint σ.p = σ'.p for all parameters p
177    /// - the constraint RC(σ.p)
178    /// - the constraint N(σ.p) = N(σ'.p)
179    ///
180    /// This is because we assume that the valuation of the parameters does not
181    /// change during the execution. Therefore we instead only need to encode
182    /// the resilience conditions once, which is done in the context manager.
183    /// Afterwards, we can simply use the global parameter variables.
184    fn encode_phi_base(&self, solver: &SMTSolver) -> SMTExpr {
185        if self.rule_vars.is_empty() {
186            return solver.true_();
187        }
188
189        // ω(σ) = ω(σ')
190        solver.and_many(
191            self.rule_vars
192                .keys()
193                .map(|rule| rule.guard().as_boolean_expr())
194                .map(|guard| {
195                    let prev_sat_guard = guard
196                        .encode_to_smt_with_ctx(solver, self.prev_config.as_ref())
197                        .expect("Failed to encode guard with previous configuration");
198                    let curr_sat_guard = guard
199                        .encode_to_smt_with_ctx(solver, self.curr_config.as_ref())
200                        .expect("Failed to encode guard with current configuration");
201
202                    solver.eq(prev_sat_guard, curr_sat_guard)
203                }),
204        )
205    }
206
207    /// Encodes the ϕ_L of the step context
208    fn encode_phi_l(&self, solver: &SMTSolver) -> SMTExpr {
209        if self.ta.locations().count() == 0 {
210            return solver.true_();
211        }
212
213        solver.and_many(self.ta.locations().map(|loc| {
214            // σ.κ(ℓ)
215            let prev_loc = self
216                .prev_config
217                .get_expr_for(loc)
218                .expect("Failed to get location for ϕ_L");
219            // σ′.κ(ℓ)
220            let curr_loc = self
221                .curr_config
222                .get_expr_for(loc)
223                .expect("Failed to get location for ϕ_L");
224
225            // σ′.κ(ℓ) - σ.κ(ℓ)
226            let diff_loc_state = solver.sub(curr_loc, prev_loc);
227
228            // ∑_{i=1}^{a_l} x_{in_i^l}
229            let in_sum = solver.plus_many(
230                self.rule_vars
231                    .iter()
232                    .filter(|(r, _)| r.target() == loc) // incoming rules
233                    .map(|(_, x_r)| *x_r)
234                    .chain([solver.numeral(0)]), // required to prevent panic if there are no incoming rules
235            );
236
237            // ∑_{j=1}^{b_l} x_{out_j^l}
238            let out_sum = solver.plus_many(
239                self.rule_vars
240                    .iter()
241                    .filter(|(r, _)| r.source() == loc) // outgoing rules
242                    .map(|(_, x_r)| *x_r)
243                    .chain([solver.numeral(0)]), // required to prevent panic if there are no outgoing rules
244            );
245
246            // ∑_{i=1}^{a_l} x_{in_i^l} - ∑_{j=1}^{b_l} x_{out_j^l}
247            let diff_rule_appl = solver.sub(in_sum, out_sum);
248
249            solver.eq(diff_loc_state, diff_rule_appl)
250        }))
251    }
252
253    /// Encodes the ϕ_Γ of the step context
254    fn encode_phi_gamma(&self, solver: &SMTSolver) -> SMTExpr {
255        if self.ta.variables().count() == 0 {
256            return solver.true_();
257        }
258
259        // z ∈ Γ
260        solver.and_many(self.ta.variables().map(|z| {
261            // σ.g[z]
262            let prev_var = self
263                .prev_config
264                .get_expr_for(z)
265                .expect("Failed to get variable for ϕ_Γ");
266            // σ'.g[z]
267            let curr_var = self
268                .curr_config
269                .get_expr_for(z)
270                .expect("Failed to get variable for ϕ_Γ");
271
272            // σ'.g[z] - σ.g[z]
273            let diff_var_state = solver.sub(curr_var, prev_var);
274
275            // ∑_{r ∈ R} x_r * r.u[z]
276            let update_acc = solver.plus_many(
277                self.rule_vars
278                    .iter()
279                    .map(|(r, x_r)| {
280                        let mut acts = r.actions().filter(|a| a.variable() == z && !a.is_reset());
281
282                        let act = acts.next();
283                        if act.is_none() {
284                            return solver.numeral(0);
285                        }
286
287                        debug_assert!(
288                            acts.next().is_none(),
289                            "Multiple actions on same variable should have been rejected !"
290                        );
291
292                        let act = act.unwrap();
293
294                        debug_assert!(
295                            !act.is_reset(),
296                            "This model checker does not support resets !"
297                        );
298
299                        // r.u[z]
300                        let update = Self::update_vec_to_smt(act.update(), solver);
301
302                        // x_r * r.u[z]
303                        solver.times(*x_r, update)
304                    })
305                    .chain([solver.numeral(0)]), // prevent panics in case there are 0 rules
306            );
307
308            // (∑_{r ∈ R} x_r * r.u[z]) = σ'.g[z] - σ.g[z]
309            let mut update = solver.eq(diff_var_state, update_acc);
310
311            // Modification to the original encoding to support resets:
312            // For any rule x_R resetting z:  x_R > 0 && σ'.g[z] = 0
313            let reset_effect = self
314                .rule_vars
315                .iter()
316                .filter(|(r, _)| r.actions().any(|a| a.variable() == z && a.is_reset()))
317                .map(|(_, x_r)| {
318                    let x_r_gt_0 = solver.gt(*x_r, solver.numeral(0));
319                    let curr_var_eq_0 = solver.eq(curr_var, solver.numeral(0));
320
321                    solver.and(x_r_gt_0, curr_var_eq_0)
322                })
323                .collect::<Vec<_>>();
324
325            // either the variable supports the update, or the variable has been reset
326            if !reset_effect.is_empty() {
327                // Sum over all reseting rules = 0 => no reset applied
328                let no_reset_applied = solver.eq(
329                    solver.plus_many(
330                        self.rule_vars
331                            .iter()
332                            .filter(|(r, _)| r.actions().any(|a| a.variable() == z && a.is_reset()))
333                            .map(|(_, x_r)| *x_r),
334                    ),
335                    solver.numeral(0),
336                );
337
338                update = solver.or(
339                    solver.and(update, no_reset_applied), // if there is no reset: update must hold
340                    solver.or_many(reset_effect),         // if a reset is applied, = 0
341                );
342            }
343
344            update
345        }))
346    }
347
348    /// Encodes the ϕ_R of the step context
349    fn encode_phi_r(&self, solver: &SMTSolver) -> SMTExpr {
350        if self.rule_vars.is_empty() {
351            return solver.true_();
352        }
353
354        solver.and_many(self.rule_vars.iter().map(|(rule, x_r)| {
355            // x_r > 0
356            let rule_applied = solver.gt(*x_r, solver.numeral(0));
357
358            // σ ⊧ r.ϕ
359            let guard_sat = rule
360                .guard()
361                .encode_to_smt_with_ctx(solver, self.prev_config.as_ref())
362                .expect("Failed to encode guard with previous configuration");
363
364            // x_r > 0 => (σ ⊧ r.ϕ)
365            solver.imp(rule_applied, guard_sat)
366        }))
367    }
368
369    /// (Exponential) Encode ϕ_{appl}
370    ///
371    /// This function encodes an exponential version of the formula ϕ_{appl},
372    /// which includes all possibilities to fire a rule.
373    ///
374    /// Compared to the version described in the main section of the paper, we
375    /// statically filter the set S, to only include chains where for all i it
376    /// holds that
377    ///     r_i.to = r_{i+1}.from and r_s = r
378    /// therefore reducing the size of the set S and eliminating the need to
379    /// encode
380    ///     ∧_{1 < i ≤ s} r_{i-1}.to = r_i.from ∧ r_s = r
381    /// into the formula.
382    ///
383    /// Note that we can therefore still have an exponentially big formula. To
384    /// avoid this, non-deterministic guessing would need to be implemented.
385    fn exponential_encode_phi_appl(&self, solver: &SMTSolver) -> SMTExpr {
386        if self.rule_vars.is_empty() {
387            return solver.true_();
388        }
389
390        solver.and_many(self.rule_vars.iter().map(|(rule, x_r)| {
391            // x_r > 0
392            let xr_gt_0 = solver.gt(*x_r, solver.numeral(0));
393
394            // S ∈ 2^R
395            let all_possible_s = self.compute_s_for_r(rule);
396
397            // prevent panics if there is no applicable chain
398            if all_possible_s.is_empty() {
399                return solver.false_();
400            }
401
402            // ∨_S ∈ 2^R ф_{chain}
403            let disj_s = solver.or_many(
404                all_possible_s
405                    .iter()
406                    .map(|s| self.encode_phi_chain(solver, s)),
407            );
408
409            //  x_r > 0 => ∨_S ∈ 2^R ф_{chain}
410            let mut res = solver.imp(xr_gt_0, disj_s);
411
412            // Modification to the original encoding to support resets:
413            // if a reset rule is applied during a steady step, we must already
414            // be in a zero interval
415            if rule.actions().any(|a| a.is_reset()) {
416                let reset_var_already_zero =
417                    solver.and_many(rule.actions().filter(|a| a.is_reset()).map(|a| {
418                        let v = self.prev_config.get_expr_for(a.variable()).unwrap();
419                        solver.eq(v, solver.numeral(0))
420                    }));
421                let reset_applied_imp_0 = solver.imp(xr_gt_0, reset_var_already_zero);
422
423                res = solver.and(res, reset_applied_imp_0)
424            }
425
426            // Modification to support decrements:
427            // In a steady step either only increments or only decrements can be
428            // applied
429            if self
430                .rule_vars
431                .iter()
432                .any(|(r, _)| r.actions().any(|a| a.is_decrement()))
433                && self
434                    .rule_vars
435                    .iter()
436                    .any(|(r, _)| r.actions().any(|a| a.is_increment()))
437            {
438                // sum over all rules with decrements
439                let r_dec = solver.plus_many(
440                    self.rule_vars
441                        .iter()
442                        .filter(|(r, _)| r.actions().any(|a| a.is_decrement()))
443                        .map(|(_, x_r)| *x_r),
444                );
445                // sum over all rules with increments
446                let r_inc = solver.plus_many(
447                    self.rule_vars
448                        .iter()
449                        .filter(|(r, _)| r.actions().any(|a| a.is_increment()))
450                        .map(|(_, x_r)| *x_r),
451                );
452
453                // an increment rule is applied
454                let r_inc_gt_0 = solver.gt(r_inc, solver.numeral(0));
455                // a decrement rule is applied
456                let r_dec_gt_0 = solver.gt(r_dec, solver.numeral(0));
457
458                // only increments or decrements
459                let dec_xor_inc = solver.and(
460                    solver.imp(r_inc_gt_0, solver.not(r_dec_gt_0)),
461                    solver.imp(r_dec_gt_0, solver.not(r_inc_gt_0)),
462                );
463
464                res = solver.and(res, dec_xor_inc);
465            }
466
467            res
468        }))
469    }
470
471    /// Encode ϕ_chain for a single non-deterministic guess
472    ///
473    /// This function encodes the ϕ_{chain} for set of rules s
474    /// However, this function only encodes ∧_{r ∈ s} x_r > 0 ∧ σ.k(r_1.from) > 0
475    /// as all other constraints can be statically checked during the
476    /// computation of S
477    ///
478    /// Note: This function expects the sequence of rules to be reversed !
479    fn encode_phi_chain(&self, solver: &SMTSolver, s: &[&Rule]) -> SMTExpr {
480        if s.is_empty() {
481            return solver.true_();
482        }
483
484        // σ.k(r_1.from) > 0
485        let loc = s.last().unwrap().source(); // change if not reversed
486        let loc_var = self
487            .prev_config
488            .get_expr_for(loc)
489            .expect("Failed to get location for ϕ_chain");
490        let loc_gt_0 = solver.gt(loc_var, solver.numeral(0));
491
492        solver.and_many(
493            s.iter()
494                .map(|rule| {
495                    let x_r = self.get_expr_for(rule).expect("Rule variable not found");
496
497                    solver.gt(x_r, solver.numeral(0))
498                })
499                .chain([loc_gt_0]),
500        )
501    }
502
503    /// Compute all sets possible values of S for rule r
504    ///
505    /// This function computes all possible sets S for all rules r, where S is
506    /// the set of rules that can lead up to r.
507    /// Additionally, this function ensures that these sets are chainable,
508    /// meaning they satisfy the formula:
509    ///     ∧_{1< i ≤ s} r_{i-1}.to = r_i.from ∧ r_s = r
510    ///
511    /// This function uses the helper `compute_s_with_backtracking_recursive`,
512    /// to directly compute only chainable rule sequences.
513    ///
514    /// Note: **This function returns the sequences to apply in reversed
515    /// order!**. You can use `.rev()` to iterate in reversed order.
516    fn compute_s_for_r<'a>(&'a self, r: &'a Rule) -> Vec<Vec<&'a Rule>> {
517        let loc = r.source();
518        self.compute_s_with_backtracking_recursive(loc, vec![r])
519    }
520
521    /// Recursive helper function that returns a vector that
522    /// contains:
523    ///  - the current sequence of chainable rules
524    ///  - the current sequence of rules extended with the incoming rules into
525    ///    `loc`
526    ///  - the chainable extensions for these rule sequences
527    ///
528    /// Note: Chains will be returned in reverse order !
529    fn compute_s_with_backtracking_recursive<'a>(
530        &'a self,
531        loc: &'a Location,
532        rules_applied: Vec<&'a Rule>,
533    ) -> Vec<Vec<&'a Rule>> {
534        // get incoming rules into loc, these will be chainable
535        let mut s = self
536            .rule_vars
537            .iter()
538            .filter(|(r, _)| r.target() == loc) //incoming rules
539            .filter(|(r, _)| !rules_applied.contains(r))
540            .flat_map(|(r, _)| {
541                let loc = r.source();
542                let mut rules_applied: Vec<_> = rules_applied.clone();
543                rules_applied.push(r);
544
545                self.compute_s_with_backtracking_recursive(loc, rules_applied)
546            })
547            .collect::<Vec<_>>();
548
549        // current sequence is chainable, so add it
550        s.push(rules_applied);
551
552        s
553    }
554
555    /// Encode ϕ_{steady}
556    pub fn encode_phi_steady(&self, solver: &SMTSolver) -> SMTExpr {
557        let phi_base = self.encode_phi_base(solver);
558        let phi_l = self.encode_phi_l(solver);
559        let phi_gamma = self.encode_phi_gamma(solver);
560        let phi_r = self.encode_phi_r(solver);
561        let phi_appl = self.exponential_encode_phi_appl(solver);
562
563        solver.and_many([phi_base, phi_l, phi_gamma, phi_r, phi_appl])
564    }
565
566    /// Encode reduced version of ϕ_{appl}
567    ///
568    /// That only requires that σ.k(r.source) > 0 for every self-loop if x_r > 0
569    /// For other rules, correctness is ensured by the encoding of ϕ_Γ
570    fn encode_appl_for_step_step(&self, solver: &SMTSolver) -> SMTExpr {
571        if self
572            .rule_vars
573            .iter()
574            .filter(|(r, _)| r.source() == r.target())
575            .count()
576            == 0
577        {
578            return solver.true_();
579        }
580
581        solver.and_many(
582            self.rule_vars
583                .iter()
584                .filter(|(r, _)| r.source() == r.target())
585                .map(|(rule, x_r)| {
586                    // x_r > 0
587                    let xr_gt_0 = solver.gt(*x_r, solver.numeral(0));
588
589                    let src_loc_procs = self
590                        .prev_config
591                        .get_expr_for(rule.source())
592                        .expect("Failed to get SMT variable for source location");
593
594                    // σ.k(r.source) > 0
595                    let src_loc_has_procs = solver.gt(src_loc_procs, solver.numeral(0));
596
597                    // x_r > 0 => σ.k(r.source) > 0
598                    solver.imp(xr_gt_0, src_loc_has_procs)
599                }),
600        )
601    }
602
603    /// Encode ϕ_{step}
604    ///
605    /// Encodes the ϕ_{step} of the step context
606    ///
607    /// # Implementation
608    ///
609    /// The paper does not (fully) specify the encoding of ϕ_{step}.
610    ///
611    /// We decided to use the following formula:
612    ///  - Introduce a new set of rule variables x_r' for each rule r.
613    ///  - Since we only want one rule to be applied when executing a step, we
614    ///    require that the sum of the rule variables is equal to 1, i.e.:
615    ///    ∑_{r ∈ R} x_r' ≤ 1
616    ///  - Then we can encode the validity of the rule application by simply
617    ///    using ϕ_R, ϕ_Γ and ϕ_L, with the new variables
618    ///  - **Important**: Instead of using ϕ_{appl} with ϕ_{chain}, we use the
619    ///    encoding provided by `encode_appl_for_step_step`, which is
620    ///    specifically designed for step contexts and ensures the correct
621    ///    application of rules in this setting.
622    ///
623    pub fn encode_phi_step(&self, solver: &SMTSolver) -> SMTExpr {
624        let phi_l = self.encode_phi_l(solver);
625        let phi_gamma = self.encode_phi_gamma(solver);
626        let phi_r = self.encode_phi_r(solver);
627        let phi_appl = self.encode_appl_for_step_step(solver);
628
629        // ∑_{r ∈ R} x_r' ≤ 1
630        let at_most_one_rule_applied = solver.lte(
631            solver.plus_many(self.rule_vars.values().cloned().chain([solver.numeral(0)])),
632            solver.numeral(1),
633        );
634
635        solver.and_many([phi_l, phi_gamma, phi_r, at_most_one_rule_applied, phi_appl])
636    }
637
638    /// Encode the effect of an UpdateExpression into an SMT expression
639    ///
640    /// This function encodes the effect of the update `upd` to a variable
641    /// into an SMT expression.
642    fn update_vec_to_smt(upd: &UpdateExpression, solver: &SMTSolver) -> SMTExpr {
643        match upd {
644            UpdateExpression::Inc(i) => solver.numeral(*i),
645            UpdateExpression::Dec(d) => {
646                let dec = solver.numeral(*d);
647                solver.negate(dec)
648            }
649            UpdateExpression::Reset => unreachable!("Resets should be handled before"),
650            UpdateExpression::Unchanged => solver.numeral(0),
651        }
652    }
653}
654
655impl<TA, C> StepSMT for LazyStepContext<TA, C>
656where
657    TA: ThresholdAutomaton,
658    C: SMTVariableContext<Parameter> + SMTVariableContext<Location> + SMTVariableContext<Variable>,
659{
660}
661
662impl<TA, C> fmt::Display for LazyStepContext<TA, C>
663where
664    TA: ThresholdAutomaton,
665    C: SMTVariableContext<Parameter> + SMTVariableContext<Location> + SMTVariableContext<Variable>,
666{
667    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
668        write!(f, "StepContext: {}", self.index)
669    }
670}
671
672#[cfg(test)]
673mod tests {
674    use std::{collections::HashMap, rc::Rc};
675
676    use taco_threshold_automaton::{
677        ThresholdAutomaton,
678        expressions::{
679            BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, Location,
680            Variable,
681        },
682        general_threshold_automaton::{
683            Action, UpdateExpression,
684            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
685        },
686    };
687
688    use crate::{
689        SMTSolverBuilder, SMTSolverContext,
690        expression_encoding::{
691            SMTVariableContext,
692            config_ctx::ConfigCtx,
693            step_ctx::{LazyStepContext, StepSMT},
694        },
695    };
696
697    #[test]
698    fn test_encode_phi_base() {
699        let mut solver = SMTSolverBuilder::default().new_solver();
700
701        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
702            .with_guard(BooleanExpression::ComparisonExpression(
703                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
704                ComparisonOp::Gt,
705                Box::new(IntegerExpression::Const(1)),
706            ))
707            .build();
708        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
709            .with_guard(BooleanExpression::BinaryExpression(
710                Box::new(BooleanExpression::False),
711                BooleanConnective::Or,
712                Box::new(BooleanExpression::BinaryExpression(
713                    Box::new(BooleanExpression::True),
714                    BooleanConnective::And,
715                    Box::new(BooleanExpression::ComparisonExpression(
716                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
717                        ComparisonOp::Leq,
718                        Box::new(IntegerExpression::Const(5)),
719                    )),
720                )),
721            ))
722            .build();
723
724        // rule without any action
725        let r3 = RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3")).build();
726
727        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
728            .with_locations([
729                Location::new("loc1"),
730                Location::new("loc2"),
731                Location::new("loc3"),
732            ])
733            .unwrap()
734            .with_variables([
735                Variable::new("var1"),
736                Variable::new("var2"),
737                Variable::new("var3"),
738            ])
739            .unwrap()
740            .initialize()
741            .with_rules([r1.clone(), r2.clone(), r3.clone()])
742            .unwrap()
743            .build();
744
745        let config_0 = Rc::new(ConfigCtx::new(
746            &mut solver,
747            &test_ta,
748            Rc::new(HashMap::new()),
749            0,
750        ));
751        let config_1 = Rc::new(ConfigCtx::new(
752            &mut solver,
753            &test_ta,
754            Rc::new(HashMap::new()),
755            1,
756        ));
757
758        let step_context = LazyStepContext::new(
759            test_ta.rules().cloned(),
760            Rc::new(test_ta.clone()),
761            config_0.clone(),
762            config_1.clone(),
763            0,
764            &mut solver,
765        );
766
767        let got_smt_expr = step_context.encode_phi_base(&solver);
768
769        let expected_smt = solver.and_many([
770            // r1
771            solver.imp(
772                solver.gt(
773                    config_0.get_expr_for(&Variable::new("var1")).unwrap(),
774                    solver.numeral(1),
775                ),
776                solver.gt(
777                    config_1.get_expr_for(&Variable::new("var1")).unwrap(),
778                    solver.numeral(1),
779                ),
780            ),
781            // r1
782            solver.imp(
783                solver.gt(
784                    config_1.get_expr_for(&Variable::new("var1")).unwrap(),
785                    solver.numeral(1),
786                ),
787                solver.gt(
788                    config_0.get_expr_for(&Variable::new("var1")).unwrap(),
789                    solver.numeral(1),
790                ),
791            ),
792            // r2
793            solver.imp(
794                solver.lte(
795                    config_0.get_expr_for(&Variable::new("var2")).unwrap(),
796                    solver.numeral(5),
797                ),
798                solver.lte(
799                    config_1.get_expr_for(&Variable::new("var2")).unwrap(),
800                    solver.numeral(5),
801                ),
802            ),
803            // r2
804            solver.imp(
805                solver.lte(
806                    config_1.get_expr_for(&Variable::new("var2")).unwrap(),
807                    solver.numeral(5),
808                ),
809                solver.lte(
810                    config_0.get_expr_for(&Variable::new("var2")).unwrap(),
811                    solver.numeral(5),
812                ),
813            ),
814        ]);
815
816        // the order of expressions might change so instead we check whether
817        // there exists an assignment such that they are not equivalent
818        let check_equivalent = solver.not(solver.and(
819            solver.imp(got_smt_expr, expected_smt),
820            solver.imp(expected_smt, got_smt_expr),
821        ));
822
823        assert!(
824            !solver
825                .assert_and_check_expr(check_equivalent)
826                .unwrap()
827                .is_sat(),
828            "got     : {}\nexpected: {}",
829            solver.display(got_smt_expr),
830            solver.display(expected_smt)
831        );
832    }
833
834    #[test]
835    fn test_encode_phi_base_no_panics() {
836        let mut solver = SMTSolverBuilder::default().new_solver();
837
838        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build();
839        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3")).build();
840
841        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
842            .with_locations([
843                Location::new("loc1"),
844                Location::new("loc2"),
845                Location::new("loc3"),
846            ])
847            .unwrap()
848            .initialize()
849            .with_rules([r1.clone(), r2.clone()])
850            .unwrap()
851            .build();
852
853        let config_0 = Rc::new(ConfigCtx::new(
854            &mut solver,
855            &test_ta,
856            Rc::new(HashMap::new()),
857            0,
858        ));
859        let config_1 = Rc::new(ConfigCtx::new(
860            &mut solver,
861            &test_ta,
862            Rc::new(HashMap::new()),
863            1,
864        ));
865
866        let step_context = LazyStepContext::new(
867            test_ta.rules().cloned(),
868            Rc::new(test_ta.clone()),
869            config_0.clone(),
870            config_1.clone(),
871            0,
872            &mut solver,
873        );
874
875        let got_smt_expr = step_context.encode_phi_base(&solver);
876
877        let expected_smt = solver.true_();
878
879        // the order of expressions might change so instead we check whether
880        // there exists an assignment such that they are not equivalent
881        let check_equivalent = solver.not(solver.and(
882            solver.imp(got_smt_expr, expected_smt),
883            solver.imp(expected_smt, got_smt_expr),
884        ));
885
886        assert!(
887            !solver
888                .assert_and_check_expr(check_equivalent)
889                .unwrap()
890                .is_sat(),
891            "got     : {}\nexpected: {}",
892            solver.display(got_smt_expr),
893            solver.display(expected_smt)
894        );
895    }
896
897    #[test]
898    fn test_encode_phi_l() {
899        let mut solver = SMTSolverBuilder::default().new_solver();
900
901        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build();
902        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3")).build();
903
904        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
905            .with_locations([
906                Location::new("loc1"),
907                Location::new("loc2"),
908                Location::new("loc3"),
909            ])
910            .unwrap()
911            .initialize()
912            .with_rules([r1.clone(), r2.clone()])
913            .unwrap()
914            .build();
915
916        let config_0 = Rc::new(ConfigCtx::new(
917            &mut solver,
918            &test_ta,
919            Rc::new(HashMap::new()),
920            0,
921        ));
922        let config_1 = Rc::new(ConfigCtx::new(
923            &mut solver,
924            &test_ta,
925            Rc::new(HashMap::new()),
926            1,
927        ));
928
929        let step_context = LazyStepContext::new(
930            test_ta.rules().cloned(),
931            Rc::new(test_ta.clone()),
932            config_0.clone(),
933            config_1.clone(),
934            0,
935            &mut solver,
936        );
937
938        let got_smt_expr = step_context.encode_phi_l(&solver);
939
940        let expected_smt = solver.and_many([
941            // loc1
942            solver.eq(
943                solver.sub(
944                    config_1.get_expr_for(&Location::new("loc1")).unwrap(),
945                    config_0.get_expr_for(&Location::new("loc1")).unwrap(),
946                ),
947                solver.sub(solver.numeral(0), step_context.get_expr_for(&r1).unwrap()),
948            ),
949            //loc2
950            solver.eq(
951                solver.sub(
952                    config_1.get_expr_for(&Location::new("loc2")).unwrap(),
953                    config_0.get_expr_for(&Location::new("loc2")).unwrap(),
954                ),
955                solver.sub(
956                    step_context.get_expr_for(&r1).unwrap(),
957                    step_context.get_expr_for(&r2).unwrap(),
958                ),
959            ),
960            //loc3
961            solver.eq(
962                solver.sub(
963                    config_1.get_expr_for(&Location::new("loc3")).unwrap(),
964                    config_0.get_expr_for(&Location::new("loc3")).unwrap(),
965                ),
966                solver.sub(step_context.get_expr_for(&r2).unwrap(), solver.numeral(0)),
967            ),
968        ]);
969
970        // the order of expressions might change so instead we check whether
971        // there exists an assignment such that they are not equivalent
972        let check_equivalent = solver.not(solver.and(
973            solver.imp(got_smt_expr, expected_smt),
974            solver.imp(expected_smt, got_smt_expr),
975        ));
976
977        assert!(
978            !solver
979                .assert_and_check_expr(check_equivalent)
980                .unwrap()
981                .is_sat(),
982            "got     : {}\nexpected: {}",
983            solver.display(got_smt_expr),
984            solver.display(expected_smt)
985        );
986    }
987
988    #[test]
989    fn test_phi_l_no_locations_does_not_panic() {
990        let mut solver = SMTSolverBuilder::default().new_solver();
991
992        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
993            .initialize()
994            .build();
995
996        let config_0 = Rc::new(ConfigCtx::new(
997            &mut solver,
998            &test_ta,
999            Rc::new(HashMap::new()),
1000            0,
1001        ));
1002        let config_1 = Rc::new(ConfigCtx::new(
1003            &mut solver,
1004            &test_ta,
1005            Rc::new(HashMap::new()),
1006            1,
1007        ));
1008
1009        let step_context = LazyStepContext::new(
1010            test_ta.rules().cloned(),
1011            Rc::new(test_ta.clone()),
1012            config_0.clone(),
1013            config_1.clone(),
1014            0,
1015            &mut solver,
1016        );
1017        let got_smt_expr = step_context.encode_phi_l(&solver);
1018
1019        let expected_smt = solver.true_();
1020
1021        // the order of expressions might change so instead we check whether
1022        // there exists an assignment such that they are not equivalent
1023        let check_equivalent = solver.not(solver.and(
1024            solver.imp(got_smt_expr, expected_smt),
1025            solver.imp(expected_smt, got_smt_expr),
1026        ));
1027
1028        assert!(
1029            !solver
1030                .assert_and_check_expr(check_equivalent)
1031                .unwrap()
1032                .is_sat(),
1033            "got     : {}\nexpected: {}",
1034            solver.display(got_smt_expr),
1035            solver.display(expected_smt)
1036        );
1037    }
1038
1039    #[test]
1040    fn test_encode_phi_gamma() {
1041        let mut solver = SMTSolverBuilder::default().new_solver();
1042
1043        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1044            .with_actions([Action::new_with_update(
1045                Variable::new("var1"),
1046                UpdateExpression::Inc(1),
1047            )])
1048            .build();
1049        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1050            .with_actions([
1051                Action::new_with_update(Variable::new("var1"), UpdateExpression::Dec(1)),
1052                Action::new_with_update(Variable::new("var2"), UpdateExpression::Inc(3)),
1053                Action::new_with_update(Variable::new("var3"), UpdateExpression::Unchanged),
1054            ])
1055            .build();
1056
1057        // rule without any action
1058        let r3 = RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3")).build();
1059
1060        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1061            .with_locations([
1062                Location::new("loc1"),
1063                Location::new("loc2"),
1064                Location::new("loc3"),
1065            ])
1066            .unwrap()
1067            .with_variables([
1068                Variable::new("var1"),
1069                Variable::new("var2"),
1070                Variable::new("var3"),
1071            ])
1072            .unwrap()
1073            .initialize()
1074            .with_rules([r1.clone(), r2.clone(), r3.clone()])
1075            .unwrap()
1076            .build();
1077
1078        let config_0 = Rc::new(ConfigCtx::new(
1079            &mut solver,
1080            &test_ta,
1081            Rc::new(HashMap::new()),
1082            0,
1083        ));
1084        let config_1 = Rc::new(ConfigCtx::new(
1085            &mut solver,
1086            &test_ta,
1087            Rc::new(HashMap::new()),
1088            1,
1089        ));
1090
1091        let step_context = LazyStepContext::new(
1092            test_ta.rules().cloned(),
1093            Rc::new(test_ta.clone()),
1094            config_0.clone(),
1095            config_1.clone(),
1096            0,
1097            &mut solver,
1098        );
1099
1100        let got_smt_expr = step_context.encode_phi_gamma(&solver);
1101
1102        let expected_smt = solver.and_many([
1103            //var1
1104            solver.eq(
1105                solver.plus_many([
1106                    // update r1
1107                    step_context.get_expr_for(&r1).unwrap(),
1108                    // update r2
1109                    solver.times(
1110                        solver.negate(solver.numeral(1)),
1111                        step_context.get_expr_for(&r2).unwrap(),
1112                    ),
1113                ]),
1114                solver.sub(
1115                    config_1.get_expr_for(&Variable::new("var1")).unwrap(),
1116                    config_0.get_expr_for(&Variable::new("var1")).unwrap(),
1117                ),
1118            ),
1119            //var2
1120            solver.eq(
1121                solver.plus_many([
1122                    // update r2
1123                    solver.times(solver.numeral(3), step_context.get_expr_for(&r2).unwrap()),
1124                ]),
1125                solver.sub(
1126                    config_1.get_expr_for(&Variable::new("var2")).unwrap(),
1127                    config_0.get_expr_for(&Variable::new("var2")).unwrap(),
1128                ),
1129            ),
1130            //var3
1131            solver.eq(
1132                solver.numeral(0),
1133                solver.sub(
1134                    config_1.get_expr_for(&Variable::new("var3")).unwrap(),
1135                    config_0.get_expr_for(&Variable::new("var3")).unwrap(),
1136                ),
1137            ),
1138        ]);
1139
1140        // the order of expressions might change so instead we check whether
1141        // there exists an assignment such that they are not equivalent
1142        let check_equivalent = solver.not(solver.and(
1143            solver.imp(got_smt_expr, expected_smt),
1144            solver.imp(expected_smt, got_smt_expr),
1145        ));
1146
1147        assert!(
1148            !solver
1149                .assert_and_check_expr(check_equivalent)
1150                .unwrap()
1151                .is_sat(),
1152            "got     : {}\nexpected: {}",
1153            solver.display(got_smt_expr),
1154            solver.display(expected_smt)
1155        );
1156    }
1157
1158    #[test]
1159    fn test_encode_phi_gamma_no_variables_does_not_panic() {
1160        let mut solver = SMTSolverBuilder::default().new_solver();
1161
1162        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1163            .initialize()
1164            .build();
1165
1166        let config_0 = Rc::new(ConfigCtx::new(
1167            &mut solver,
1168            &test_ta,
1169            Rc::new(HashMap::new()),
1170            0,
1171        ));
1172        let config_1 = Rc::new(ConfigCtx::new(
1173            &mut solver,
1174            &test_ta,
1175            Rc::new(HashMap::new()),
1176            1,
1177        ));
1178
1179        let step_context = LazyStepContext::new(
1180            test_ta.rules().cloned(),
1181            Rc::new(test_ta.clone()),
1182            config_0.clone(),
1183            config_1.clone(),
1184            0,
1185            &mut solver,
1186        );
1187
1188        let got_smt_expr = step_context.encode_phi_gamma(&solver);
1189
1190        let expected_smt = solver.true_();
1191
1192        // the order of expressions might change so instead we check whether
1193        // there exists an assignment such that they are not equivalent
1194        let check_equivalent = solver.not(solver.and(
1195            solver.imp(got_smt_expr, expected_smt),
1196            solver.imp(expected_smt, got_smt_expr),
1197        ));
1198
1199        assert!(
1200            !solver
1201                .assert_and_check_expr(check_equivalent)
1202                .unwrap()
1203                .is_sat(),
1204            "got     : {}\nexpected: {}",
1205            solver.display(got_smt_expr),
1206            solver.display(expected_smt)
1207        );
1208    }
1209
1210    #[test]
1211    fn test_encode_phi_r() {
1212        let mut solver = SMTSolverBuilder::default().new_solver();
1213
1214        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1215            .with_guard(BooleanExpression::ComparisonExpression(
1216                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1217                ComparisonOp::Gt,
1218                Box::new(IntegerExpression::Const(1)),
1219            ))
1220            .build();
1221        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1222            .with_guard(BooleanExpression::BinaryExpression(
1223                Box::new(BooleanExpression::False),
1224                BooleanConnective::Or,
1225                Box::new(BooleanExpression::BinaryExpression(
1226                    Box::new(BooleanExpression::True),
1227                    BooleanConnective::And,
1228                    Box::new(BooleanExpression::ComparisonExpression(
1229                        Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1230                        ComparisonOp::Leq,
1231                        Box::new(IntegerExpression::Const(5)),
1232                    )),
1233                )),
1234            ))
1235            .build();
1236
1237        // rule without any action
1238        let r3 = RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3")).build();
1239
1240        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1241            .with_locations([
1242                Location::new("loc1"),
1243                Location::new("loc2"),
1244                Location::new("loc3"),
1245            ])
1246            .unwrap()
1247            .with_variables([
1248                Variable::new("var1"),
1249                Variable::new("var2"),
1250                Variable::new("var3"),
1251            ])
1252            .unwrap()
1253            .initialize()
1254            .with_rules([r1.clone(), r2.clone(), r3.clone()])
1255            .unwrap()
1256            .build();
1257
1258        let config_0 = Rc::new(ConfigCtx::new(
1259            &mut solver,
1260            &test_ta,
1261            Rc::new(HashMap::new()),
1262            0,
1263        ));
1264        let config_1 = Rc::new(ConfigCtx::new(
1265            &mut solver,
1266            &test_ta,
1267            Rc::new(HashMap::new()),
1268            1,
1269        ));
1270
1271        let step_context = LazyStepContext::new(
1272            test_ta.rules().cloned(),
1273            Rc::new(test_ta.clone()),
1274            config_0.clone(),
1275            config_1.clone(),
1276            0,
1277            &mut solver,
1278        );
1279
1280        let got_smt_expr = step_context.encode_phi_r(&solver);
1281
1282        let expected_smt = solver.and_many([
1283            // r1
1284            solver.imp(
1285                solver.gte(step_context.get_expr_for(&r1).unwrap(), solver.numeral(1)),
1286                solver.gt(
1287                    config_0.get_expr_for(&Variable::new("var1")).unwrap(),
1288                    solver.numeral(1),
1289                ),
1290            ),
1291            // r2
1292            solver.imp(
1293                solver.gt(step_context.get_expr_for(&r2).unwrap(), solver.numeral(0)),
1294                solver.lte(
1295                    config_0.get_expr_for(&Variable::new("var2")).unwrap(),
1296                    solver.numeral(5),
1297                ),
1298            ),
1299        ]);
1300
1301        // the order of expressions might change so instead we check whether
1302        // there exists an assignment such that they are not equivalent
1303        let check_equivalent = solver.not(solver.and(
1304            solver.imp(got_smt_expr, expected_smt),
1305            solver.imp(expected_smt, got_smt_expr),
1306        ));
1307
1308        assert!(
1309            !solver
1310                .assert_and_check_expr(check_equivalent)
1311                .unwrap()
1312                .is_sat(),
1313            "got     : {}\nexpected: {}",
1314            solver.display(got_smt_expr),
1315            solver.display(expected_smt)
1316        );
1317    }
1318
1319    #[test]
1320    fn test_encode_phi_r_does_not_panic() {
1321        let mut solver = SMTSolverBuilder::default().new_solver();
1322
1323        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1324            .initialize()
1325            .build();
1326
1327        let config_0 = Rc::new(ConfigCtx::new(
1328            &mut solver,
1329            &test_ta,
1330            Rc::new(HashMap::new()),
1331            0,
1332        ));
1333        let config_1 = Rc::new(ConfigCtx::new(
1334            &mut solver,
1335            &test_ta,
1336            Rc::new(HashMap::new()),
1337            1,
1338        ));
1339
1340        let step_context = LazyStepContext::new(
1341            test_ta.rules().cloned(),
1342            Rc::new(test_ta.clone()),
1343            config_0.clone(),
1344            config_1.clone(),
1345            0,
1346            &mut solver,
1347        );
1348
1349        let got_smt_expr = step_context.encode_phi_gamma(&solver);
1350
1351        let expected_smt = solver.true_();
1352
1353        // the order of expressions might change so instead we check whether
1354        // there exists an assignment such that they are not equivalent
1355        let check_equivalent = solver.not(solver.and(
1356            solver.imp(got_smt_expr, expected_smt),
1357            solver.imp(expected_smt, got_smt_expr),
1358        ));
1359
1360        assert!(
1361            !solver
1362                .assert_and_check_expr(check_equivalent)
1363                .unwrap()
1364                .is_sat(),
1365            "got     : {}\nexpected: {}",
1366            solver.display(got_smt_expr),
1367            solver.display(expected_smt)
1368        );
1369    }
1370
1371    #[test]
1372    fn test_encode_phi_appl() {
1373        let mut solver = SMTSolverBuilder::default().new_solver();
1374
1375        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build();
1376        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3")).build();
1377
1378        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1379            .with_locations([
1380                Location::new("loc1"),
1381                Location::new("loc2"),
1382                Location::new("loc3"),
1383            ])
1384            .unwrap()
1385            .initialize()
1386            .with_rules([r1.clone(), r2.clone()])
1387            .unwrap()
1388            .build();
1389
1390        let config_0 = Rc::new(ConfigCtx::new(
1391            &mut solver,
1392            &test_ta,
1393            Rc::new(HashMap::new()),
1394            0,
1395        ));
1396        let config_1 = Rc::new(ConfigCtx::new(
1397            &mut solver,
1398            &test_ta,
1399            Rc::new(HashMap::new()),
1400            1,
1401        ));
1402
1403        let step_context = LazyStepContext::new(
1404            test_ta.rules().cloned(),
1405            Rc::new(test_ta.clone()),
1406            config_0.clone(),
1407            config_1.clone(),
1408            0,
1409            &mut solver,
1410        );
1411
1412        let got_smt_expr = step_context.exponential_encode_phi_appl(&solver);
1413
1414        let r1 = step_context.get_expr_for(&r1).unwrap();
1415        let r2 = step_context.get_expr_for(&r2).unwrap();
1416
1417        let expected_smt = solver.and_many([
1418            // r1
1419            solver.imp(
1420                solver.gt(r1, solver.numeral(0)),
1421                solver.gt(
1422                    config_0.get_expr_for(&Location::new("loc1")).unwrap(),
1423                    solver.numeral(0),
1424                ),
1425            ),
1426            // r2
1427            solver.imp(
1428                solver.gt(r2, solver.numeral(0)),
1429                solver.or_many([
1430                    solver.gt(
1431                        config_0.get_expr_for(&Location::new("loc2")).unwrap(),
1432                        solver.numeral(0),
1433                    ),
1434                    solver.and_many([
1435                        solver.gt(r1, solver.numeral(0)),
1436                        solver.gt(
1437                            config_0.get_expr_for(&Location::new("loc1")).unwrap(),
1438                            solver.numeral(0),
1439                        ),
1440                    ]),
1441                ]),
1442            ),
1443        ]);
1444
1445        // the order of expressions might change so instead we check whether
1446        // there exists an assignment such that they are not equivalent
1447        let check_equivalent = solver.not(solver.and(
1448            solver.imp(got_smt_expr, expected_smt),
1449            solver.imp(expected_smt, got_smt_expr),
1450        ));
1451
1452        assert!(
1453            !solver
1454                .assert_and_check_expr(check_equivalent)
1455                .unwrap()
1456                .is_sat(),
1457            "got     : {}\nexpected: {}",
1458            solver.display(got_smt_expr),
1459            solver.display(expected_smt)
1460        );
1461    }
1462
1463    #[test]
1464    fn test_compute_s() {
1465        let mut solver = SMTSolverBuilder::default().new_solver();
1466
1467        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build();
1468        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3")).build();
1469        let r3 = RuleBuilder::new(2, Location::new("loc3"), Location::new("loc4")).build();
1470
1471        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1472            .with_locations([
1473                Location::new("loc1"),
1474                Location::new("loc2"),
1475                Location::new("loc3"),
1476                Location::new("loc4"),
1477            ])
1478            .unwrap()
1479            .initialize()
1480            .with_rules([r1.clone(), r2.clone(), r3.clone()])
1481            .unwrap()
1482            .build();
1483
1484        let config_0 = Rc::new(ConfigCtx::new(
1485            &mut solver,
1486            &test_ta,
1487            Rc::new(HashMap::new()),
1488            0,
1489        ));
1490        let config_1 = Rc::new(ConfigCtx::new(
1491            &mut solver,
1492            &test_ta,
1493            Rc::new(HashMap::new()),
1494            1,
1495        ));
1496
1497        let step_context = LazyStepContext::new(
1498            test_ta.rules().cloned(),
1499            Rc::new(test_ta.clone()),
1500            config_0.clone(),
1501            config_1.clone(),
1502            0,
1503            &mut solver,
1504        );
1505
1506        let s = step_context.compute_s_for_r(&r3);
1507        assert_eq!(s.len(), 3, "{s:?}");
1508        assert!(s.contains(&vec![&r3]), "{s:?}");
1509        assert!(s.contains(&vec![&r3, &r2,]), "{s:?}");
1510        assert!(s.contains(&vec![&r3, &r2, &r1]), "{s:?}");
1511    }
1512
1513    #[test]
1514    fn test_encode_phi_step() {
1515        let mut solver = SMTSolverBuilder::default().new_solver();
1516
1517        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1518            .with_guard(BooleanExpression::ComparisonExpression(
1519                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1520                ComparisonOp::Gt,
1521                Box::new(IntegerExpression::Const(1)),
1522            ))
1523            .build();
1524        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1525            .with_guard(BooleanExpression::ComparisonExpression(
1526                Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1527                ComparisonOp::Lt,
1528                Box::new(IntegerExpression::Const(42)),
1529            ))
1530            .build();
1531
1532        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1533            .with_locations([
1534                Location::new("loc1"),
1535                Location::new("loc2"),
1536                Location::new("loc3"),
1537            ])
1538            .unwrap()
1539            .with_variables([Variable::new("var1"), Variable::new("var2")])
1540            .unwrap()
1541            .initialize()
1542            .with_rules([r1.clone(), r2.clone()])
1543            .unwrap()
1544            .build();
1545
1546        let config_0 = Rc::new(ConfigCtx::new(
1547            &mut solver,
1548            &test_ta,
1549            Rc::new(HashMap::new()),
1550            0,
1551        ));
1552        let config_1 = Rc::new(ConfigCtx::new(
1553            &mut solver,
1554            &test_ta,
1555            Rc::new(HashMap::new()),
1556            1,
1557        ));
1558
1559        let step_context = LazyStepContext::new(
1560            test_ta.rules().cloned(),
1561            Rc::new(test_ta.clone()),
1562            config_0.clone(),
1563            config_1.clone(),
1564            0,
1565            &mut solver,
1566        );
1567
1568        let got_smt_expr = step_context.encode_phi_step(&solver);
1569
1570        // tested before
1571        let phi_l = step_context.encode_phi_l(&solver);
1572        let phi_gamma = step_context.encode_phi_gamma(&solver);
1573        let phi_r = step_context.encode_phi_r(&solver);
1574
1575        let r1 = step_context.get_expr_for(&r1).unwrap();
1576        let r2 = step_context.get_expr_for(&r2).unwrap();
1577
1578        let expected_smt = solver.and_many([
1579            phi_l,
1580            phi_gamma,
1581            phi_r,
1582            // at most one rule applied
1583            solver.lte(solver.plus(r1, r2), solver.numeral(1)),
1584        ]);
1585
1586        // the order of expressions might change so instead we check whether
1587        // there exists an assignment such that they are not equivalent
1588        let check_equivalent = solver.not(solver.and(
1589            solver.imp(got_smt_expr, expected_smt),
1590            solver.imp(expected_smt, got_smt_expr),
1591        ));
1592
1593        assert!(
1594            !solver
1595                .assert_and_check_expr(check_equivalent)
1596                .unwrap()
1597                .is_sat(),
1598            "got     : {}\nexpected: {}",
1599            solver.display(got_smt_expr),
1600            solver.display(expected_smt)
1601        );
1602    }
1603
1604    #[test]
1605    fn test_get_rules_applied() {
1606        let mut solver = SMTSolverBuilder::default().new_solver();
1607
1608        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1609            .with_guard(BooleanExpression::ComparisonExpression(
1610                Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1611                ComparisonOp::Gt,
1612                Box::new(IntegerExpression::Const(1)),
1613            ))
1614            .build();
1615        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1616            .with_guard(BooleanExpression::ComparisonExpression(
1617                Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1618                ComparisonOp::Lt,
1619                Box::new(IntegerExpression::Const(42)),
1620            ))
1621            .build();
1622
1623        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1624            .with_locations([
1625                Location::new("loc1"),
1626                Location::new("loc2"),
1627                Location::new("loc3"),
1628            ])
1629            .unwrap()
1630            .with_variables([Variable::new("var1"), Variable::new("var2")])
1631            .unwrap()
1632            .initialize()
1633            .with_rules([r1.clone(), r2.clone()])
1634            .unwrap()
1635            .build();
1636
1637        let config_0 = Rc::new(ConfigCtx::new(
1638            &mut solver,
1639            &test_ta,
1640            Rc::new(HashMap::new()),
1641            0,
1642        ));
1643        let config_1 = Rc::new(ConfigCtx::new(
1644            &mut solver,
1645            &test_ta,
1646            Rc::new(HashMap::new()),
1647            1,
1648        ));
1649
1650        let step_context = LazyStepContext::new(
1651            test_ta.rules().cloned(),
1652            Rc::new(test_ta.clone()),
1653            config_0.clone(),
1654            config_1.clone(),
1655            0,
1656            &mut solver,
1657        );
1658
1659        let smt_expr = solver.and_many([
1660            solver.eq(step_context.get_expr_for(&r1).unwrap(), solver.numeral(4)),
1661            solver.eq(step_context.get_expr_for(&r2).unwrap(), solver.numeral(42)),
1662        ]);
1663
1664        let res = solver.assert_and_check_expr(smt_expr).unwrap();
1665        let rules = step_context
1666            .get_rules_applied(&mut solver, res)
1667            .unwrap()
1668            .collect::<Vec<_>>();
1669
1670        assert!(rules.contains(&(r1.clone(), 4)));
1671        assert!(rules.contains(&(r2.clone(), 42)));
1672    }
1673
1674    #[test]
1675    fn test_encode_phi_gamma_with_reset() {
1676        let mut solver = SMTSolverBuilder::default().new_solver();
1677
1678        let r1 = RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1679            .with_actions([Action::new_with_update(
1680                Variable::new("var1"),
1681                UpdateExpression::Inc(1),
1682            )])
1683            .build();
1684        let r2 = RuleBuilder::new(1, Location::new("loc2"), Location::new("loc3"))
1685            .with_actions([
1686                Action::new_with_update(Variable::new("var1"), UpdateExpression::Dec(1)),
1687                Action::new_with_update(Variable::new("var2"), UpdateExpression::Inc(3)),
1688                Action::new_with_update(Variable::new("var3"), UpdateExpression::Reset),
1689            ])
1690            .build();
1691
1692        // rule without any action
1693        let r3 = RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3")).build();
1694
1695        let test_ta = GeneralThresholdAutomatonBuilder::new("test_ta")
1696            .with_locations([
1697                Location::new("loc1"),
1698                Location::new("loc2"),
1699                Location::new("loc3"),
1700            ])
1701            .unwrap()
1702            .with_variables([
1703                Variable::new("var1"),
1704                Variable::new("var2"),
1705                Variable::new("var3"),
1706            ])
1707            .unwrap()
1708            .initialize()
1709            .with_rules([r1.clone(), r2.clone(), r3.clone()])
1710            .unwrap()
1711            .build();
1712
1713        let config_0 = Rc::new(ConfigCtx::new(
1714            &mut solver,
1715            &test_ta,
1716            Rc::new(HashMap::new()),
1717            0,
1718        ));
1719        let config_1 = Rc::new(ConfigCtx::new(
1720            &mut solver,
1721            &test_ta,
1722            Rc::new(HashMap::new()),
1723            1,
1724        ));
1725
1726        let step_context = LazyStepContext::new(
1727            test_ta.rules().cloned(),
1728            Rc::new(test_ta.clone()),
1729            config_0.clone(),
1730            config_1.clone(),
1731            0,
1732            &mut solver,
1733        );
1734
1735        let got_smt_expr = step_context.encode_phi_gamma(&solver);
1736
1737        let expected_smt = solver.and_many([
1738            //var1
1739            solver.eq(
1740                solver.plus_many([
1741                    // update r1
1742                    step_context.get_expr_for(&r1).unwrap(),
1743                    // update r2
1744                    solver.times(
1745                        solver.negate(solver.numeral(1)),
1746                        step_context.get_expr_for(&r2).unwrap(),
1747                    ),
1748                ]),
1749                solver.sub(
1750                    config_1.get_expr_for(&Variable::new("var1")).unwrap(),
1751                    config_0.get_expr_for(&Variable::new("var1")).unwrap(),
1752                ),
1753            ),
1754            //var2
1755            solver.eq(
1756                solver.plus_many([
1757                    // update r2
1758                    solver.times(solver.numeral(3), step_context.get_expr_for(&r2).unwrap()),
1759                ]),
1760                solver.sub(
1761                    config_1.get_expr_for(&Variable::new("var2")).unwrap(),
1762                    config_0.get_expr_for(&Variable::new("var2")).unwrap(),
1763                ),
1764            ),
1765            //var3
1766            solver.or(
1767                solver.and(
1768                    solver.eq(step_context.get_expr_for(&r2).unwrap(), solver.numeral(0)),
1769                    solver.eq(
1770                        solver.numeral(0),
1771                        solver.sub(
1772                            config_1.get_expr_for(&Variable::new("var3")).unwrap(),
1773                            config_0.get_expr_for(&Variable::new("var3")).unwrap(),
1774                        ),
1775                    ),
1776                ),
1777                solver.and(
1778                    solver.gt(step_context.get_expr_for(&r2).unwrap(), solver.numeral(0)),
1779                    solver.eq(
1780                        config_1.get_expr_for(&Variable::new("var3")).unwrap(),
1781                        solver.numeral(0),
1782                    ),
1783                ),
1784            ),
1785        ]);
1786
1787        // the order of expressions might change so instead we check whether
1788        // there exists an assignment such that they are not equivalent
1789        let check_equivalent = solver.not(solver.and(
1790            solver.imp(got_smt_expr, expected_smt),
1791            solver.imp(expected_smt, got_smt_expr),
1792        ));
1793
1794        assert!(
1795            !solver
1796                .assert_and_check_expr(check_equivalent)
1797                .unwrap()
1798                .is_sat(),
1799            "got     : {}\nexpected: {}",
1800            solver.display(got_smt_expr),
1801            solver.display(expected_smt)
1802        );
1803    }
1804
1805    // TODO: tests for resets and decrements
1806}