taco_smt_model_checker/
smt_encoding.rs

1//! This module implements the encoding of query on threshold automata into SMT
2//! expressions. The algorithm is based on the encoding described in the paper
3//! ["Complexity of Verification and Synthesis of Threshold Automata"](https://arxiv.org/abs/2002.08507)
4//! by A. R. Balasubramanian, Javier Esparza, Marijana Lazic.
5//!
6//! The paper does not provide a complete encoding of the threshold automata,
7//! instead many implementation details are not provided. As we were not
8//! provided with the source code (nor an executable) of the implementation used
9//! in the paper, we  had to make some assumptions about the implementation
10//! details.
11//!
12//! We will explicitly mark functions that are not directly described in the
13//! paper.
14
15use core::fmt;
16use std::{collections::HashMap, ops::Deref, rc::Rc};
17
18use log::{debug, info};
19use taco_display_utils::join_iterator;
20
21use taco_model_checker::reachability_specification::DisjunctionTargetConfig;
22use taco_smt_encoder::{
23    SMTExpr, SMTSolver, SMTSolverBuilder, SMTSolverContext,
24    expression_encoding::{
25        DeclaresVariable, EncodeToSMT,
26        config_ctx::ConfigCtx,
27        ctx_mgr::SMTConfigMgr,
28        step_ctx::LazyStepContext,
29        ta_encoding::{encode_initial_constraints, encode_resilience_condition},
30    },
31};
32
33use taco_threshold_automaton::{
34    ThresholdAutomaton,
35    expressions::Parameter,
36    general_threshold_automaton::GeneralThresholdAutomaton,
37    lia_threshold_automaton::{
38        LIAThresholdAutomaton, LIATransformationError, LIAVariableConstraint,
39    },
40    path::Path,
41};
42
43// Type alias for the concrete step context used for this model checker
44type StepCtx = LazyStepContext<GeneralThresholdAutomaton, ConfigCtx>;
45
46#[derive(Debug)]
47pub enum ContextMgrError {
48    /// Threshold automaton is not a LIA threshold automaton
49    LIATransformationError(LIATransformationError),
50}
51
52impl fmt::Display for ContextMgrError {
53    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54        match self {
55            ContextMgrError::LIATransformationError(e) => {
56                write!(f, "Automaton is not using linear arithmetic: {e}")
57            }
58        }
59    }
60}
61
62impl std::error::Error for ContextMgrError {}
63
64pub struct EContextMgr {
65    solver: SMTSolver,
66    ta: Rc<GeneralThresholdAutomaton>,
67    k: usize,
68    ctx_mgr: SMTConfigMgr<StepCtx, ConfigCtx>,
69}
70
71impl fmt::Debug for EContextMgr {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        write!(f, "ContextMgr {{ ta: {}, k: {} }}", self.ta, self.k)
74    }
75}
76
77impl EContextMgr {
78    pub fn new(
79        ta: GeneralThresholdAutomaton,
80        target_constr: &[&LIAVariableConstraint],
81        smt_builder: &SMTSolverBuilder,
82    ) -> Result<Self, ContextMgrError> {
83        let mut solver = smt_builder.new_solver();
84
85        let ta = Rc::new(ta);
86
87        let params: Rc<HashMap<Parameter, SMTExpr>> = Rc::new(
88            ta.parameters()
89                .map(|p| {
90                    let param = p
91                        .declare_variable(&mut solver, 0)
92                        .expect("Failed to declare parameter");
93
94                    solver
95                        .assert(solver.gte(param, solver.numeral(0)))
96                        .expect("Failed to assert parameter >= 0");
97
98                    (p.clone(), param)
99                })
100                .collect(),
101        );
102
103        let k = Self::compute_k_for_ta(&ta, target_constr)?;
104        info!(
105            "Determined number of steady steps k for threshold automaton '{}' to be {k}",
106            ta.name()
107        );
108
109        let mut ctx_mgr = SMTConfigMgr::new(params.clone());
110
111        let mut prev_primed_config = None;
112
113        for i in 0..k {
114            let config_index = 2 * i;
115            let config_primed_index = 2 * i + 1;
116
117            // Create config context σ_i
118            let config = Rc::new(ConfigCtx::new(
119                &mut solver,
120                ta.as_ref(),
121                params.clone(),
122                config_index,
123            ));
124            ctx_mgr.push_cfg(config.clone());
125
126            // Create config context σ'_i
127            let config_primed = Rc::new(ConfigCtx::new(
128                &mut solver,
129                ta.as_ref(),
130                params.clone(),
131                config_primed_index,
132            ));
133            ctx_mgr.push_cfg_primed(config_primed.clone());
134
135            // Create step context for ϕ_steady (σ_i,σ'_i)
136            let steady_step = LazyStepContext::new(
137                ta.rules().cloned(),
138                ta.clone(),
139                config.clone(),
140                config_primed.clone(),
141                config_index,
142                &mut solver,
143            );
144            ctx_mgr.push_steady(steady_step);
145
146            // Create step context ϕ_step (σ'_{i-1}, σ_i)
147            if let Some(prev_primed_config) = prev_primed_config {
148                let step_step = LazyStepContext::new(
149                    ta.rules().cloned(),
150                    ta.clone(),
151                    prev_primed_config,
152                    config,
153                    2 * (i - 1) + 1,
154                    &mut solver,
155                );
156
157                ctx_mgr.push_step(step_step);
158            }
159
160            prev_primed_config = Some(config_primed.clone());
161        }
162
163        Ok(Self {
164            solver,
165            ta,
166            k,
167            ctx_mgr,
168        })
169    }
170
171    /// Check whether spec holds
172    ///
173    /// If the specification holds `None` is returned. Otherwise an error path is returned
174    pub fn check_spec(mut self, spec: &DisjunctionTargetConfig) -> Option<Path> {
175        self.encode_phi_reach(spec);
176
177        debug!("Start check of ϕ_{{reach}}");
178        let res = self
179            .solver
180            .assert_and_check_expr(self.solver.true_())
181            .expect("Failed to check the encoding of the threshold automaton");
182        debug!("SMT solver successfully checked ϕ_{{reach}} !");
183
184        if !res.is_sat() {
185            return None;
186        }
187
188        let err_path =
189            self.ctx_mgr
190                .extract_error_path(res, &mut self.solver, self.ta.deref().clone());
191        Some(err_path)
192    }
193
194    /// Compute the number K for the threshold automaton
195    ///
196    /// In the paper, the number of guards is simply given as the size of the
197    /// set of guards ϕ plus one, i.e. K = |ϕ| + 1.
198    ///
199    /// However, this assumes that all guards are in their strict upper and
200    /// lower guard form. Therefore we need to convert the threshold automaton
201    /// first into a `LIAThresholdAutomaton` and then compute the number K.
202    ///
203    fn compute_k_for_ta(
204        ta: &GeneralThresholdAutomaton,
205        target_constr: &[&LIAVariableConstraint],
206    ) -> Result<usize, ContextMgrError> {
207        //
208        let lia_ta: LIAThresholdAutomaton = ta
209            .clone()
210            .try_into()
211            .map_err(ContextMgrError::LIATransformationError)?;
212
213        let mut s_guards = lia_ta.get_single_var_constraints_rules();
214
215        // Add additional guards potentially arising from the target constraint
216        s_guards.extend(
217            target_constr
218                .iter()
219                .flat_map(|c| c.get_single_var_constraints()),
220        );
221
222        debug!(
223            "Single variable guards: {}",
224            join_iterator(s_guards.iter(), ", ")
225        );
226        let n_single_var_guards = s_guards.iter().fold(0, |mut acc, guard| {
227            debug_assert!(guard.is_lower_guard() || guard.is_upper_guard());
228
229            if guard.is_lower_guard() {
230                acc += 1;
231            }
232
233            if guard.is_upper_guard() {
234                acc += 1;
235            }
236
237            acc
238        });
239
240        let m_guards = lia_ta.get_sum_var_constraints();
241        debug!(
242            "Multi variable guards: {}",
243            join_iterator(m_guards.iter(), ", ")
244        );
245        let n_multi_var_guards = m_guards.into_iter().fold(0, |mut acc, guard| {
246            debug_assert!(guard.is_lower_guard() || guard.is_upper_guard());
247
248            if guard.is_lower_guard() {
249                acc += 1;
250            }
251
252            if guard.is_upper_guard() {
253                acc += 1;
254            }
255
256            acc
257        });
258
259        debug_assert!(lia_ta.get_comparison_guards().is_empty());
260
261        Ok(n_single_var_guards + n_multi_var_guards + 1)
262    }
263
264    /// Encodes the ϕ_{reach} of the threshold automaton
265    fn encode_phi_reach(&mut self, spec: &DisjunctionTargetConfig) {
266        // RC(p)
267        let rc = encode_resilience_condition(
268            self.ta.as_ref(),
269            &self.solver,
270            self.ctx_mgr.params().deref(),
271        );
272        self.solver
273            .assert(rc)
274            .expect("Failed to assert resilience condition");
275
276        // σ = σ_0
277        let init_config = self
278            .ctx_mgr
279            .configs()
280            .next()
281            .expect("No initial configuration found");
282        let init_constraints =
283            encode_initial_constraints(self.ta.as_ref(), &self.solver, init_config.as_ref());
284
285        self.solver
286            .assert(init_constraints)
287            .expect("Failed to assert initial constraints");
288
289        debug!("Finished encoding initial constraints");
290
291        // σ' = σ_K
292        let goal_constraints = self.encode_final_error_condition(spec);
293        self.solver
294            .assert(goal_constraints)
295            .expect("Failed to assert goal constraints");
296
297        // ∧_{0 ≤ i ≤ K} ϕ_{steady}(σ_i, σ_i')
298        self.ctx_mgr.steady().for_each(|step| {
299            let steady = step.encode_phi_steady(&self.solver);
300            self.solver
301                .assert(steady)
302                .expect("Failed to assert steady step")
303        });
304
305        // ∧_{0 ≤ i ≤ K - 1} ϕ_{step}(σ_i', σ_{i+1})
306        self.ctx_mgr.step().for_each(|step| {
307            let step = step.encode_phi_step(&self.solver);
308            self.solver
309                .assert(step)
310                .expect("Failed to assert step step")
311        });
312
313        info!(
314            "Finished SMT encoding ϕ_{{reach}} for property '{}', start checking in SMT solver",
315            spec.name()
316        );
317    }
318
319    /// Encodes the goal condition of the threshold automaton into an SMT
320    fn encode_final_error_condition(&self, spec: &DisjunctionTargetConfig) -> SMTExpr {
321        spec.encode_to_smt_with_ctx(
322            &self.solver,
323            self.ctx_mgr.configs_primed().last().unwrap().as_ref(),
324        ) // k will always be greater than 1, therefore there should at least be one configuration
325        .expect("Failed to encode the error state condition of the specification")
326    }
327}
328
329#[cfg(test)]
330mod tests {
331
332    use std::collections::HashMap;
333
334    use taco_model_checker::reachability_specification::TargetConfig;
335    use taco_parser::{ParseTA, bymc::ByMCParser};
336    use taco_smt_encoder::SMTSolverBuilder;
337    use taco_threshold_automaton::{
338        expressions::{
339            BooleanConnective, BooleanExpression, ComparisonOp, IntegerExpression, Location,
340            Parameter, Variable,
341        },
342        general_threshold_automaton::{
343            Action, UpdateExpression,
344            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
345        },
346        path::{Configuration, PathBuilder, Transition},
347    };
348
349    use super::EContextMgr;
350
351    // These tests rely on the bymc parser, since constructing complicated
352    // threshold automata by hand is too error prone
353
354    #[test]
355    fn test_compute_k_no_guards() {
356        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
357            .initialize()
358            .build();
359        assert_eq!(EContextMgr::compute_k_for_ta(&ta, &[]).unwrap(), 1);
360    }
361
362    #[test]
363    fn test_compute_k_only_lower_guards() {
364        let test_spec = "
365            skel test_ta1 {
366                shared var1, var2, var3;
367                parameters n, t, f;
368                
369                assumptions (1) {
370                    n > 3 * f;
371                }
372
373                locations (2) {
374                    loc1 : [0];
375                    loc2 : [1];
376                    loc3 : [2];
377                }
378
379                inits (1) {
380                    loc1 == n - f  || loc2 == 0;
381                    var1 == 0;
382                    var2 == 0;
383                    var3 == 0;
384                }
385
386                rules (4) {
387                    0: loc1 -> loc2
388                        when(true)
389                        do {};
390                    1: loc2 -> loc3
391                        when( var1 < 1 && var2 <= n)
392                        do { reset(var3); var1' == var1 + 1  };
393                    2: loc2 -> loc3
394                        when( var1 < 1)
395                        do { reset(var3); var1' == var1 + 1  };
396                    3: loc2 -> loc3
397                        when( var1 < 3 || 2 * var1 < 6)
398                        do { reset(var3); var1' == var1 + 1  };
399                    4: loc2 -> loc3
400                        when( var1 < 3 || var2 <=n )
401                        do { reset(var3); var1' == var1 + 1  };
402                    5: loc3 -> loc2
403                        when ( var1 + var2 <= n + 1 + 5 - 3 - 2)
404                        do { };
405                    6: loc3 -> loc2
406                        when ( var2 + var1 <= n + 1 + 5 - 3 - 2 - 1 + 1 )
407                        do { };
408                    7: loc3 -> loc2 
409                        when ( var2 + var1 <= n + 1 ||  var2 + var1 <= 101 -100 + n) 
410                        do { };
411                }
412
413                specifications (1) {
414                }
415            }
416            ";
417
418        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
419        // var2 < n, var1 < 1, var1 < 3, var2 + var1 <= n + 1
420        assert_eq!(EContextMgr::compute_k_for_ta(&ta, &[]).unwrap(), 5);
421    }
422
423    #[test]
424    fn compute_k_upper_guards() {
425        let test_spec = "
426            skel test_ta1 {
427                shared var1, var2, var3;
428                parameters n, t, f;
429                
430                assumptions (1) {
431                    n > 3 * f;
432                }
433
434                locations (2) {
435                    loc1 : [0];
436                    loc2 : [1];
437                    loc3 : [2];
438                }
439
440                inits (1) {
441                    loc1 == n - f  || loc2 == 0;
442                    var1 == 0;
443                    var2 == 0;
444                    var3 == 0;
445                }
446
447                rules (4) {
448                    0: loc1 -> loc2
449                        when(true)
450                        do {};
451                    1: loc2 -> loc3
452                        when( var1 > 1 && var2 >= n)
453                        do { reset(var3); var1' == var1 + 1  };
454                    2: loc2 -> loc3
455                        when( var1 >= 1)
456                        do { reset(var3); var1' == var1 + 1  };
457                    3: loc2 -> loc3
458                        when( var1 > 1)
459                        do { reset(var3); var1' == var1 + 1  };
460                    4: loc2 -> loc3
461                        when( var1 > 3 || var2 >= n )
462                        do { reset(var3); var1' == var1 + 1  };
463                    5: loc3 -> loc2
464                        when ( var2 + var1 > n + 1 + 5 - 3 - 2)
465                        do { };
466                    6: loc3 -> loc2
467                        when ( var2 + var1 > n + 1 + 5 - 3 - 2 - 1 + 1  ||  var1 + var2  > 101 -100 + n)
468                        do { };
469                    7: loc3 -> loc2 
470                        when ( var2 + var1 > n + 1) 
471                        do { };
472                }
473
474                specifications (1) {
475                }
476            }
477            ";
478
479        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
480        // var2 >= n, var1 >= 1, var1 > 1, var1 > 3, var1 + var2 > n + 1
481        assert_eq!(EContextMgr::compute_k_for_ta(&ta, &[]).unwrap(), 6);
482    }
483
484    #[test]
485    fn test_compute_k_with_additional_var_constr_increases_k() {
486        let test_spec = "
487            skel test_ta1 {
488                shared var1, var2, var3;
489                parameters n, t, f;
490                
491                assumptions (1) {
492                    n > 3 * f;
493                }
494
495                locations (2) {
496                    loc1 : [0];
497                    loc2 : [1];
498                    loc3 : [2];
499                }
500
501                inits (1) {
502                    loc1 == n - f  || loc2 == 0;
503                    var1 == 0;
504                    var2 == 0;
505                    var3 == 0;
506                }
507
508                rules (4) {
509                    0: loc1 -> loc2
510                        when(true)
511                        do {};
512                    1: loc2 -> loc3
513                        when( var1 < 1 && var2 <= n)
514                        do { reset(var3); var1' == var1 + 1  };
515                    2: loc2 -> loc3
516                        when( var1 < 1)
517                        do { reset(var3); var1' == var1 + 1  };
518                    3: loc2 -> loc3
519                        when( var1 < 3 || 2 * var1 < 6)
520                        do { reset(var3); var1' == var1 + 1  };
521                    4: loc2 -> loc3
522                        when( var1 < 3 || var2 <=n )
523                        do { reset(var3); var1' == var1 + 1  };
524                    5: loc3 -> loc2
525                        when ( var1 + var2 <= n + 1 + 5 - 3 - 2)
526                        do { };
527                    6: loc3 -> loc2
528                        when ( var2 + var1 <= n + 1 + 5 - 3 - 2 - 1 + 1 )
529                        do { };
530                    7: loc3 -> loc2 
531                        when ( var2 + var1 <= n + 1 ||  var2 + var1 <= 101 -100 + n) 
532                        do { };
533                }
534
535                specifications (1) {
536                }
537            }
538            ";
539
540        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
541        let lia_constr = BooleanExpression::ComparisonExpression(
542            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
543            ComparisonOp::Lt,
544            Box::new(IntegerExpression::Const(4)),
545        )
546        .try_into()
547        .unwrap();
548        // var2 < n, var1 < 1, var1 < 3, var1 < 4, var2 + var1 <= n + 1
549        assert_eq!(
550            EContextMgr::compute_k_for_ta(&ta, &[&lia_constr]).unwrap(),
551            6
552        );
553    }
554
555    #[test]
556    fn test_compute_k_with_additional_var_constr_already_in_guard() {
557        let test_spec = "
558            skel test_ta1 {
559                shared var1, var2, var3;
560                parameters n, t, f;
561                
562                assumptions (1) {
563                    n > 3 * f;
564                }
565
566                locations (2) {
567                    loc1 : [0];
568                    loc2 : [1];
569                    loc3 : [2];
570                }
571
572                inits (1) {
573                    loc1 == n - f  || loc2 == 0;
574                    var1 == 0;
575                    var2 == 0;
576                    var3 == 0;
577                }
578
579                rules (4) {
580                    0: loc1 -> loc2
581                        when(true)
582                        do {};
583                    1: loc2 -> loc3
584                        when( var1 < 1 && var2 <= n)
585                        do { reset(var3); var1' == var1 + 1  };
586                    2: loc2 -> loc3
587                        when( var1 < 1)
588                        do { reset(var3); var1' == var1 + 1  };
589                    3: loc2 -> loc3
590                        when( var1 < 3 || 2 * var1 < 6)
591                        do { reset(var3); var1' == var1 + 1  };
592                    4: loc2 -> loc3
593                        when( var1 < 3 || var2 <=n )
594                        do { reset(var3); var1' == var1 + 1  };
595                    5: loc3 -> loc2
596                        when ( var1 + var2 <= n + 1 + 5 - 3 - 2)
597                        do { };
598                    6: loc3 -> loc2
599                        when ( var2 + var1 <= n + 1 + 5 - 3 - 2 - 1 + 1 )
600                        do { };
601                    7: loc3 -> loc2 
602                        when ( var2 + var1 <= n + 1 ||  var2 + var1 <= 101 -100 + n) 
603                        do { };
604                }
605
606                specifications (1) {
607                }
608            }
609            ";
610
611        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
612        let lia_constr = BooleanExpression::ComparisonExpression(
613            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
614            ComparisonOp::Lt,
615            Box::new(IntegerExpression::Const(3)),
616        )
617        .try_into()
618        .unwrap();
619        // var2 < n, var1 < 1, var1 < 3, var1 < 4, var2 + var1 <= n + 1
620        assert_eq!(
621            EContextMgr::compute_k_for_ta(&ta, &[&lia_constr]).unwrap(),
622            5
623        );
624    }
625
626    #[test]
627    fn test_lower_and_upper_guards() {
628        // lower and upper guard
629        let test_spec = "
630            skel test_ta1 {
631                shared var1, var2, var3;
632                parameters n, t, f;
633                
634                assumptions (1) {
635                    n > 3 * f;
636                }
637
638                locations (2) {
639                    loc1 : [0];
640                    loc2 : [1];
641                    loc3 : [2];
642                }
643
644                inits (1) {
645                    loc1 == n - f  || loc2 == 0;
646                    var1 == 0;
647                    var2 == 0;
648                    var3 == 0;
649                }
650
651                rules (4) {
652                    0: loc1 -> loc2
653                        when(true)
654                        do {};
655                    1: loc2 -> loc3
656                        when( var1 == 1 && var2 != n)
657                        do { reset(var3); var1' == var1 + 1  };
658                }
659
660                specifications (1) {
661                }
662            }
663            ";
664
665        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
666        // var2 == n, var1 == 1
667        assert_eq!(EContextMgr::compute_k_for_ta(&ta, &[]).unwrap(), 5);
668    }
669
670    #[test]
671    fn test_spec_reach_initial_location() {
672        let test_spec = "
673            skel test_ta1 {
674                shared var1, var2, var3;
675                parameters n, f;
676                
677                assumptions (1) {
678                    n > 3 * f;
679                    n == 1;
680                }
681
682                locations (2) {
683                    loc1 : [0];
684                    loc2 : [1];
685                    loc3 : [2];
686                }
687
688                inits (1) {
689                    loc1 == n - f;
690                    loc2 == 0;
691                    loc3 == 0;
692                    var1 == 0;
693                    var2 == 0;
694                    var3 == 0;
695                }
696
697                rules (4) {
698                    0: loc1 -> loc2
699                        when(true)
700                        do {};
701                }
702
703                specifications (1) {
704                    test1:
705                        [](loc1 == 0)
706                }
707            }
708            ";
709
710        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
711
712        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
713            .expect("Failed to create context manager");
714        let spec = TargetConfig::new_cover([Location::new("loc1")])
715            .unwrap()
716            .into_disjunct_with_name("test");
717
718        let path = PathBuilder::new(ta)
719            .add_parameter_assignment(HashMap::from([
720                (Parameter::new("n"), 1),
721                (Parameter::new("f"), 0),
722            ]))
723            .unwrap()
724            .add_configuration(Configuration::new(
725                HashMap::from([
726                    (Variable::new("var1"), 0),
727                    (Variable::new("var2"), 0),
728                    (Variable::new("var3"), 0),
729                ]),
730                HashMap::from([
731                    (Location::new("loc1"), 1),
732                    (Location::new("loc2"), 0),
733                    (Location::new("loc3"), 0),
734                ]),
735            ))
736            .unwrap()
737            .build()
738            .unwrap();
739
740        let res = ctx_mgr.check_spec(&spec);
741
742        assert_eq!(res, Some(path));
743    }
744
745    #[test]
746    fn test_spec_reach_location_one_rule_one_process_no_guards() {
747        let test_spec = "
748            skel test_ta1 {
749                shared var1, var2, var3;
750                parameters n, f;
751                
752                assumptions (1) {
753                    n > 3 * f;
754                    n == 1;
755                }
756
757                locations (2) {
758                    loc1 : [0];
759                    loc2 : [1];
760                    loc3 : [2];
761                }
762
763                inits (1) {
764                    loc1 == n - f;
765                    loc2 == 0;
766                    loc3 == 0;
767                    var1 == 0;
768                    var2 == 0;
769                    var3 == 0;
770                }
771
772                rules (4) {
773                    0: loc1 -> loc2
774                        when(true)
775                        do {};
776                }
777
778                specifications (1) {
779                    test1:
780                        [](loc2 == 0)
781                }
782            }
783            ";
784
785        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
786
787        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
788            .expect("Failed to create context manager");
789        let spec = TargetConfig::new_cover([Location::new("loc2")])
790            .unwrap()
791            .into_disjunct_with_name("test");
792
793        let path = PathBuilder::new(ta)
794            .add_parameter_assignment(HashMap::from([
795                (Parameter::new("n"), 1),
796                (Parameter::new("f"), 0),
797            ]))
798            .unwrap()
799            .add_configuration(Configuration::new(
800                HashMap::from([
801                    (Variable::new("var1"), 0),
802                    (Variable::new("var2"), 0),
803                    (Variable::new("var3"), 0),
804                ]),
805                HashMap::from([
806                    (Location::new("loc1"), 1),
807                    (Location::new("loc2"), 0),
808                    (Location::new("loc3"), 0),
809                ]),
810            ))
811            .unwrap()
812            .add_transition(Transition::new(
813                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
814                1,
815            ))
816            .unwrap()
817            .add_configuration(Configuration::new(
818                HashMap::from([
819                    (Variable::new("var1"), 0),
820                    (Variable::new("var2"), 0),
821                    (Variable::new("var3"), 0),
822                ]),
823                HashMap::from([
824                    (Location::new("loc1"), 0),
825                    (Location::new("loc2"), 1),
826                    (Location::new("loc3"), 0),
827                ]),
828            ))
829            .unwrap()
830            .build()
831            .unwrap();
832
833        let res = ctx_mgr.check_spec(&spec);
834
835        assert_eq!(res, Some(path));
836    }
837
838    #[test]
839    fn test_spec_reach_location_one_rule_5_process_no_guards() {
840        let test_spec = "
841            skel test_ta1 {
842                shared var1, var2, var3;
843                parameters n, f;
844                
845                assumptions (1) {
846                    n > 3 * f;
847                    n == 5;
848                    f == 0;
849                }
850
851                locations (2) {
852                    loc1 : [0];
853                    loc2 : [1];
854                    loc3 : [2];
855                }
856
857                inits (1) {
858                    loc1 == n - f;
859                    loc2 == 0;
860                    loc3 == 0;
861                    var1 == 0;
862                    var2 == 0;
863                    var3 == 0;
864                }
865
866                rules (4) {
867                    0: loc1 -> loc2
868                        when(true)
869                        do {};
870                }
871
872                specifications (1) {
873                    test1:
874                        [](loc2 == 0)
875                }
876            }
877            ";
878
879        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
880
881        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
882            .expect("Failed to create context manager");
883        let spec = TargetConfig::new_reach([Location::new("loc2")], [Location::new("loc1")])
884            .unwrap()
885            .into_disjunct_with_name("test");
886
887        let path = PathBuilder::new(ta)
888            .add_parameter_assignment(HashMap::from([
889                (Parameter::new("n"), 5),
890                (Parameter::new("f"), 0),
891            ]))
892            .unwrap()
893            .add_configuration(Configuration::new(
894                HashMap::from([
895                    (Variable::new("var1"), 0),
896                    (Variable::new("var2"), 0),
897                    (Variable::new("var3"), 0),
898                ]),
899                HashMap::from([
900                    (Location::new("loc1"), 5),
901                    (Location::new("loc2"), 0),
902                    (Location::new("loc3"), 0),
903                ]),
904            ))
905            .unwrap()
906            .add_transition(Transition::new(
907                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
908                5,
909            ))
910            .unwrap()
911            .add_configuration(Configuration::new(
912                HashMap::from([
913                    (Variable::new("var1"), 0),
914                    (Variable::new("var2"), 0),
915                    (Variable::new("var3"), 0),
916                ]),
917                HashMap::from([
918                    (Location::new("loc1"), 0),
919                    (Location::new("loc2"), 5),
920                    (Location::new("loc3"), 0),
921                ]),
922            ))
923            .unwrap()
924            .build()
925            .unwrap();
926
927        let res = ctx_mgr.check_spec(&spec);
928
929        assert_eq!(res, Some(path));
930    }
931
932    #[test]
933    fn test_spec_unreachable_location_graph() {
934        let test_spec = "
935            skel test_ta1 {
936                shared var1, var2, var3;
937                parameters n, t, f;
938                
939                assumptions (1) {
940                    n > 3 * f;
941                }
942
943                locations (2) {
944                    loc1 : [0];
945                    loc2 : [1];
946                    loc3 : [2];
947                }
948
949                inits (1) {
950                    loc1 == n - f;
951                    loc2 == 0;
952                    loc3 == 0;
953                    var1 == 0;
954                    var2 == 0;
955                    var3 == 0;
956                }
957
958                rules (4) {
959                    0: loc1 -> loc2
960                        when(true)
961                        do {};
962                }
963
964                specifications (1) {
965                    test1:
966                        [](loc3 == 0)
967                }
968            }
969            ";
970
971        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
972
973        let ctx_mgr = EContextMgr::new(ta, &[], &SMTSolverBuilder::default())
974            .expect("Failed to create context manager");
975        let spec = TargetConfig::new_cover([Location::new("loc3")])
976            .unwrap()
977            .into_disjunct_with_name("test");
978
979        let res = ctx_mgr.check_spec(&spec);
980
981        assert_eq!(res.clone(), None, "Error Path: {}", res.unwrap());
982    }
983
984    #[test]
985    fn test_spec_reach_location_two_rules_one_guard() {
986        let test_spec = "
987            skel test_ta1 {
988                shared var1, var2, var3;
989                parameters n, f;
990                
991                assumptions (1) {
992                    n > 3 * f;
993                    n == 5;
994                    f == 0;
995                }
996
997                locations (2) {
998                    loc1 : [0];
999                    loc2 : [1];
1000                    loc3 : [2];
1001                }
1002
1003                inits (1) {
1004                    loc1 == n - f;
1005                    loc2 == 0;
1006                    loc3 == 0;
1007                    var1 == 0;
1008                    var2 == 0;
1009                    var3 == 0;
1010                }
1011
1012                rules (4) {
1013                    0: loc1 -> loc2
1014                        when(var1 > 3 && var2 <= 0)
1015                        do {};
1016                    1: loc1 -> loc3
1017                        when(true)
1018                        do {var1' == var1 + 1};
1019                }
1020
1021                specifications (1) {
1022                    test1:
1023                        [](loc2 == 0)
1024                }
1025            }
1026            ";
1027
1028        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
1029
1030        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
1031            .expect("Failed to create context manager");
1032        let spec = TargetConfig::new_cover([Location::new("loc2")])
1033            .unwrap()
1034            .into_disjunct_with_name("test");
1035
1036        let path = PathBuilder::new(ta)
1037            .add_parameter_assignment(HashMap::from([
1038                (Parameter::new("n"), 5),
1039                (Parameter::new("f"), 0),
1040            ]))
1041            .unwrap()
1042            .add_configuration(Configuration::new(
1043                HashMap::from([
1044                    (Variable::new("var1"), 0),
1045                    (Variable::new("var2"), 0),
1046                    (Variable::new("var3"), 0),
1047                ]),
1048                HashMap::from([
1049                    (Location::new("loc1"), 5),
1050                    (Location::new("loc2"), 0),
1051                    (Location::new("loc3"), 0),
1052                ]),
1053            ))
1054            .unwrap()
1055            .add_transition(Transition::new(
1056                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
1057                    .with_action(Action::new_with_update(
1058                        Variable::new("var1"),
1059                        UpdateExpression::Inc(1),
1060                    ))
1061                    .build(),
1062                3,
1063            ))
1064            .unwrap()
1065            .add_configuration(Configuration::new(
1066                HashMap::from([
1067                    (Variable::new("var1"), 3),
1068                    (Variable::new("var2"), 0),
1069                    (Variable::new("var3"), 0),
1070                ]),
1071                HashMap::from([
1072                    (Location::new("loc1"), 2),
1073                    (Location::new("loc2"), 0),
1074                    (Location::new("loc3"), 3),
1075                ]),
1076            ))
1077            .unwrap()
1078            .add_transition(Transition::new(
1079                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
1080                    .with_action(Action::new_with_update(
1081                        Variable::new("var1"),
1082                        UpdateExpression::Inc(1),
1083                    ))
1084                    .build(),
1085                1,
1086            ))
1087            .unwrap()
1088            .add_configuration(Configuration::new(
1089                HashMap::from([
1090                    (Variable::new("var1"), 4),
1091                    (Variable::new("var2"), 0),
1092                    (Variable::new("var3"), 0),
1093                ]),
1094                HashMap::from([
1095                    (Location::new("loc1"), 1),
1096                    (Location::new("loc2"), 0),
1097                    (Location::new("loc3"), 4),
1098                ]),
1099            ))
1100            .unwrap()
1101            .add_transition(Transition::new(
1102                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1103                    .with_guard(BooleanExpression::BinaryExpression(
1104                        Box::new(BooleanExpression::ComparisonExpression(
1105                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1106                            ComparisonOp::Gt,
1107                            Box::new(IntegerExpression::Const(3)),
1108                        )),
1109                        BooleanConnective::And,
1110                        Box::new(BooleanExpression::ComparisonExpression(
1111                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1112                            ComparisonOp::Leq,
1113                            Box::new(IntegerExpression::Const(0)),
1114                        )),
1115                    ))
1116                    .build(),
1117                1,
1118            ))
1119            .unwrap()
1120            .add_configuration(Configuration::new(
1121                HashMap::from([
1122                    (Variable::new("var1"), 4),
1123                    (Variable::new("var2"), 0),
1124                    (Variable::new("var3"), 0),
1125                ]),
1126                HashMap::from([
1127                    (Location::new("loc1"), 0),
1128                    (Location::new("loc2"), 1),
1129                    (Location::new("loc3"), 4),
1130                ]),
1131            ))
1132            .unwrap()
1133            .build()
1134            .unwrap();
1135
1136        let res = ctx_mgr.check_spec(&spec);
1137
1138        assert!(res.is_some());
1139        assert_eq!(
1140            res.clone(),
1141            Some(path.clone()),
1142            "Got:\n{}\n\nExpected:\n{}\n\n",
1143            res.unwrap(),
1144            path
1145        );
1146    }
1147
1148    #[test]
1149    fn test_spec_reach_location_three_rules_one_guard_self_loop() {
1150        let test_spec = "
1151            skel test_ta1 {
1152                shared var1, var2, var3;
1153                parameters n, f;
1154                
1155                assumptions (1) {
1156                    n > 3 * f;
1157                    n == 1;
1158                    f == 0;
1159                }
1160
1161                locations (2) {
1162                    loc1 : [0];
1163                    loc2 : [1];
1164                    loc3 : [2];
1165                }
1166
1167                inits (1) {
1168                    loc1 == n - f;
1169                    loc2 == 0;
1170                    loc3 == 0;
1171                    var1 == 0;
1172                    var2 == 0;
1173                    var3 == 0;
1174                }
1175
1176                rules (4) {
1177                    0: loc1 -> loc1
1178                        when(true)
1179                        do {var1' == var1 + 1};
1180                    1: loc1 -> loc2
1181                        when(true)
1182                        do {};
1183                    2: loc2 -> loc3
1184                        when(var1 > 3 && var2 <= 0 && var1 <= 4)
1185                        do {};
1186                    
1187                }
1188
1189                specifications (1) {
1190                    test1:
1191                        [](loc2 == 0 || var2 > 6)
1192                }
1193            }
1194            ";
1195
1196        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
1197
1198        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
1199            .expect("Failed to create context manager");
1200        let spec = TargetConfig::new_cover([Location::new("loc3")])
1201            .unwrap()
1202            .into_disjunct_with_name("test");
1203
1204        let path = PathBuilder::new(ta)
1205            .add_parameter_assignment(HashMap::from([
1206                (Parameter::new("n"), 1),
1207                (Parameter::new("f"), 0),
1208            ]))
1209            .unwrap()
1210            .add_configuration(Configuration::new(
1211                HashMap::from([
1212                    (Variable::new("var1"), 0),
1213                    (Variable::new("var2"), 0),
1214                    (Variable::new("var3"), 0),
1215                ]),
1216                HashMap::from([
1217                    (Location::new("loc1"), 1),
1218                    (Location::new("loc2"), 0),
1219                    (Location::new("loc3"), 0),
1220                ]),
1221            ))
1222            .unwrap()
1223            .add_transition(Transition::new(
1224                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1225                    .with_action(Action::new_with_update(
1226                        Variable::new("var1"),
1227                        UpdateExpression::Inc(1),
1228                    ))
1229                    .build(),
1230                3,
1231            ))
1232            .unwrap()
1233            .add_configuration(Configuration::new(
1234                HashMap::from([
1235                    (Variable::new("var1"), 3),
1236                    (Variable::new("var2"), 0),
1237                    (Variable::new("var3"), 0),
1238                ]),
1239                HashMap::from([
1240                    (Location::new("loc1"), 1),
1241                    (Location::new("loc2"), 0),
1242                    (Location::new("loc3"), 0),
1243                ]),
1244            ))
1245            .unwrap()
1246            .add_transition(Transition::new(
1247                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1248                    .with_action(Action::new_with_update(
1249                        Variable::new("var1"),
1250                        UpdateExpression::Inc(1),
1251                    ))
1252                    .build(),
1253                1,
1254            ))
1255            .unwrap()
1256            .add_configuration(Configuration::new(
1257                HashMap::from([
1258                    (Variable::new("var1"), 4),
1259                    (Variable::new("var2"), 0),
1260                    (Variable::new("var3"), 0),
1261                ]),
1262                HashMap::from([
1263                    (Location::new("loc1"), 1),
1264                    (Location::new("loc2"), 0),
1265                    (Location::new("loc3"), 0),
1266                ]),
1267            ))
1268            .unwrap()
1269            .add_transition(Transition::new(
1270                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
1271                1,
1272            ))
1273            .unwrap()
1274            .add_configuration(Configuration::new(
1275                HashMap::from([
1276                    (Variable::new("var1"), 4),
1277                    (Variable::new("var2"), 0),
1278                    (Variable::new("var3"), 0),
1279                ]),
1280                HashMap::from([
1281                    (Location::new("loc1"), 0),
1282                    (Location::new("loc2"), 1),
1283                    (Location::new("loc3"), 0),
1284                ]),
1285            ))
1286            .unwrap()
1287            .add_transition(Transition::new(
1288                RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
1289                    .with_guard(
1290                        BooleanExpression::ComparisonExpression(
1291                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1292                            ComparisonOp::Gt,
1293                            Box::new(IntegerExpression::Const(3)),
1294                        ) & BooleanExpression::ComparisonExpression(
1295                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1296                            ComparisonOp::Leq,
1297                            Box::new(IntegerExpression::Const(0)),
1298                        ) & BooleanExpression::ComparisonExpression(
1299                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1300                            ComparisonOp::Leq,
1301                            Box::new(IntegerExpression::Const(4)),
1302                        ),
1303                    )
1304                    .build(),
1305                1,
1306            ))
1307            .unwrap()
1308            .add_configuration(Configuration::new(
1309                HashMap::from([
1310                    (Variable::new("var1"), 4),
1311                    (Variable::new("var2"), 0),
1312                    (Variable::new("var3"), 0),
1313                ]),
1314                HashMap::from([
1315                    (Location::new("loc1"), 0),
1316                    (Location::new("loc2"), 0),
1317                    (Location::new("loc3"), 1),
1318                ]),
1319            ))
1320            .unwrap()
1321            .build()
1322            .unwrap();
1323
1324        let res = ctx_mgr.check_spec(&spec);
1325
1326        assert!(res.is_some());
1327        assert_eq!(
1328            res.clone(),
1329            Some(path.clone()),
1330            "Got:\n{}\n\nExpected:\n{}\n\n",
1331            res.unwrap(),
1332            path
1333        );
1334    }
1335}