Skip to main content

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::internal_spec::upwards_closed_set::UpwardsClosedSet;
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 the upwards closed set `spec` is reachable
172    ///
173    /// If the set is not reachable `None` is returned. Otherwise an error path is returned
174    pub fn check_spec(mut self, spec: &UpwardsClosedSet) -> 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: &UpwardsClosedSet) {
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
316        );
317    }
318
319    /// Encodes the goal condition of the threshold automaton into an SMT
320    fn encode_final_error_condition(&self, spec: &UpwardsClosedSet) -> 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::internal_spec::upwards_closed_set::UpwardsClosedSet;
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 = UpwardsClosedSet::new_cover([Location::new("loc1")]);
715
716        let path = PathBuilder::new(ta)
717            .add_parameter_assignment(HashMap::from([
718                (Parameter::new("n"), 1),
719                (Parameter::new("f"), 0),
720            ]))
721            .unwrap()
722            .add_configuration(Configuration::new(
723                HashMap::from([
724                    (Variable::new("var1"), 0),
725                    (Variable::new("var2"), 0),
726                    (Variable::new("var3"), 0),
727                ]),
728                HashMap::from([
729                    (Location::new("loc1"), 1),
730                    (Location::new("loc2"), 0),
731                    (Location::new("loc3"), 0),
732                ]),
733            ))
734            .unwrap()
735            .build()
736            .unwrap();
737
738        let res = ctx_mgr.check_spec(&spec);
739
740        assert_eq!(res, Some(path));
741    }
742
743    #[test]
744    fn test_spec_reach_location_one_rule_one_process_no_guards() {
745        let test_spec = "
746            skel test_ta1 {
747                shared var1, var2, var3;
748                parameters n, f;
749                
750                assumptions (1) {
751                    n > 3 * f;
752                    n == 1;
753                }
754
755                locations (2) {
756                    loc1 : [0];
757                    loc2 : [1];
758                    loc3 : [2];
759                }
760
761                inits (1) {
762                    loc1 == n - f;
763                    loc2 == 0;
764                    loc3 == 0;
765                    var1 == 0;
766                    var2 == 0;
767                    var3 == 0;
768                }
769
770                rules (4) {
771                    0: loc1 -> loc2
772                        when(true)
773                        do {};
774                }
775
776                specifications (1) {
777                    test1:
778                        [](loc2 == 0)
779                }
780            }
781            ";
782
783        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
784
785        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
786            .expect("Failed to create context manager");
787        let spec = UpwardsClosedSet::new_cover([Location::new("loc2")]);
788
789        let path = PathBuilder::new(ta)
790            .add_parameter_assignment(HashMap::from([
791                (Parameter::new("n"), 1),
792                (Parameter::new("f"), 0),
793            ]))
794            .unwrap()
795            .add_configuration(Configuration::new(
796                HashMap::from([
797                    (Variable::new("var1"), 0),
798                    (Variable::new("var2"), 0),
799                    (Variable::new("var3"), 0),
800                ]),
801                HashMap::from([
802                    (Location::new("loc1"), 1),
803                    (Location::new("loc2"), 0),
804                    (Location::new("loc3"), 0),
805                ]),
806            ))
807            .unwrap()
808            .add_transition(Transition::new(
809                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
810                1,
811            ))
812            .unwrap()
813            .add_configuration(Configuration::new(
814                HashMap::from([
815                    (Variable::new("var1"), 0),
816                    (Variable::new("var2"), 0),
817                    (Variable::new("var3"), 0),
818                ]),
819                HashMap::from([
820                    (Location::new("loc1"), 0),
821                    (Location::new("loc2"), 1),
822                    (Location::new("loc3"), 0),
823                ]),
824            ))
825            .unwrap()
826            .build()
827            .unwrap();
828
829        let res = ctx_mgr.check_spec(&spec);
830
831        assert_eq!(res, Some(path));
832    }
833
834    #[test]
835    fn test_spec_reach_location_one_rule_5_process_no_guards() {
836        let test_spec = "
837            skel test_ta1 {
838                shared var1, var2, var3;
839                parameters n, f;
840                
841                assumptions (1) {
842                    n > 3 * f;
843                    n == 5;
844                    f == 0;
845                }
846
847                locations (2) {
848                    loc1 : [0];
849                    loc2 : [1];
850                    loc3 : [2];
851                }
852
853                inits (1) {
854                    loc1 == n - f;
855                    loc2 == 0;
856                    loc3 == 0;
857                    var1 == 0;
858                    var2 == 0;
859                    var3 == 0;
860                }
861
862                rules (4) {
863                    0: loc1 -> loc2
864                        when(true)
865                        do {};
866                }
867
868                specifications (1) {
869                    test1:
870                        [](loc2 == 0)
871                }
872            }
873            ";
874
875        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
876
877        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
878            .expect("Failed to create context manager");
879        let spec =
880            UpwardsClosedSet::new_reach([(Location::new("loc2"), 1)], [Location::new("loc1")]);
881
882        let path = PathBuilder::new(ta)
883            .add_parameter_assignment(HashMap::from([
884                (Parameter::new("n"), 5),
885                (Parameter::new("f"), 0),
886            ]))
887            .unwrap()
888            .add_configuration(Configuration::new(
889                HashMap::from([
890                    (Variable::new("var1"), 0),
891                    (Variable::new("var2"), 0),
892                    (Variable::new("var3"), 0),
893                ]),
894                HashMap::from([
895                    (Location::new("loc1"), 5),
896                    (Location::new("loc2"), 0),
897                    (Location::new("loc3"), 0),
898                ]),
899            ))
900            .unwrap()
901            .add_transition(Transition::new(
902                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2")).build(),
903                5,
904            ))
905            .unwrap()
906            .add_configuration(Configuration::new(
907                HashMap::from([
908                    (Variable::new("var1"), 0),
909                    (Variable::new("var2"), 0),
910                    (Variable::new("var3"), 0),
911                ]),
912                HashMap::from([
913                    (Location::new("loc1"), 0),
914                    (Location::new("loc2"), 5),
915                    (Location::new("loc3"), 0),
916                ]),
917            ))
918            .unwrap()
919            .build()
920            .unwrap();
921
922        let res = ctx_mgr.check_spec(&spec);
923
924        assert_eq!(res, Some(path));
925    }
926
927    #[test]
928    fn test_spec_unreachable_location_graph() {
929        let test_spec = "
930            skel test_ta1 {
931                shared var1, var2, var3;
932                parameters n, t, f;
933                
934                assumptions (1) {
935                    n > 3 * f;
936                }
937
938                locations (2) {
939                    loc1 : [0];
940                    loc2 : [1];
941                    loc3 : [2];
942                }
943
944                inits (1) {
945                    loc1 == n - f;
946                    loc2 == 0;
947                    loc3 == 0;
948                    var1 == 0;
949                    var2 == 0;
950                    var3 == 0;
951                }
952
953                rules (4) {
954                    0: loc1 -> loc2
955                        when(true)
956                        do {};
957                }
958
959                specifications (1) {
960                    test1:
961                        [](loc3 == 0)
962                }
963            }
964            ";
965
966        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
967
968        let ctx_mgr = EContextMgr::new(ta, &[], &SMTSolverBuilder::default())
969            .expect("Failed to create context manager");
970        let spec = UpwardsClosedSet::new_cover([Location::new("loc3")]);
971
972        let res = ctx_mgr.check_spec(&spec);
973
974        assert_eq!(res.clone(), None, "Error Path: {}", res.unwrap());
975    }
976
977    #[test]
978    fn test_spec_reach_location_two_rules_one_guard() {
979        let test_spec = "
980            skel test_ta1 {
981                shared var1, var2, var3;
982                parameters n, f;
983                
984                assumptions (1) {
985                    n > 3 * f;
986                    n == 5;
987                    f == 0;
988                }
989
990                locations (2) {
991                    loc1 : [0];
992                    loc2 : [1];
993                    loc3 : [2];
994                }
995
996                inits (1) {
997                    loc1 == n - f;
998                    loc2 == 0;
999                    loc3 == 0;
1000                    var1 == 0;
1001                    var2 == 0;
1002                    var3 == 0;
1003                }
1004
1005                rules (4) {
1006                    0: loc1 -> loc2
1007                        when(var1 > 3 && var2 <= 0)
1008                        do {};
1009                    1: loc1 -> loc3
1010                        when(true)
1011                        do {var1' == var1 + 1};
1012                }
1013
1014                specifications (1) {
1015                    test1:
1016                        [](loc2 == 0)
1017                }
1018            }
1019            ";
1020
1021        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
1022
1023        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
1024            .expect("Failed to create context manager");
1025        let spec = UpwardsClosedSet::new_cover([Location::new("loc2")]);
1026
1027        let path = PathBuilder::new(ta)
1028            .add_parameter_assignment(HashMap::from([
1029                (Parameter::new("n"), 5),
1030                (Parameter::new("f"), 0),
1031            ]))
1032            .unwrap()
1033            .add_configuration(Configuration::new(
1034                HashMap::from([
1035                    (Variable::new("var1"), 0),
1036                    (Variable::new("var2"), 0),
1037                    (Variable::new("var3"), 0),
1038                ]),
1039                HashMap::from([
1040                    (Location::new("loc1"), 5),
1041                    (Location::new("loc2"), 0),
1042                    (Location::new("loc3"), 0),
1043                ]),
1044            ))
1045            .unwrap()
1046            .add_transition(Transition::new(
1047                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
1048                    .with_action(Action::new_with_update(
1049                        Variable::new("var1"),
1050                        UpdateExpression::Inc(1),
1051                    ))
1052                    .build(),
1053                3,
1054            ))
1055            .unwrap()
1056            .add_configuration(Configuration::new(
1057                HashMap::from([
1058                    (Variable::new("var1"), 3),
1059                    (Variable::new("var2"), 0),
1060                    (Variable::new("var3"), 0),
1061                ]),
1062                HashMap::from([
1063                    (Location::new("loc1"), 2),
1064                    (Location::new("loc2"), 0),
1065                    (Location::new("loc3"), 3),
1066                ]),
1067            ))
1068            .unwrap()
1069            .add_transition(Transition::new(
1070                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc3"))
1071                    .with_action(Action::new_with_update(
1072                        Variable::new("var1"),
1073                        UpdateExpression::Inc(1),
1074                    ))
1075                    .build(),
1076                1,
1077            ))
1078            .unwrap()
1079            .add_configuration(Configuration::new(
1080                HashMap::from([
1081                    (Variable::new("var1"), 4),
1082                    (Variable::new("var2"), 0),
1083                    (Variable::new("var3"), 0),
1084                ]),
1085                HashMap::from([
1086                    (Location::new("loc1"), 1),
1087                    (Location::new("loc2"), 0),
1088                    (Location::new("loc3"), 4),
1089                ]),
1090            ))
1091            .unwrap()
1092            .add_transition(Transition::new(
1093                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc2"))
1094                    .with_guard(BooleanExpression::BinaryExpression(
1095                        Box::new(BooleanExpression::ComparisonExpression(
1096                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1097                            ComparisonOp::Gt,
1098                            Box::new(IntegerExpression::Const(3)),
1099                        )),
1100                        BooleanConnective::And,
1101                        Box::new(BooleanExpression::ComparisonExpression(
1102                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1103                            ComparisonOp::Leq,
1104                            Box::new(IntegerExpression::Const(0)),
1105                        )),
1106                    ))
1107                    .build(),
1108                1,
1109            ))
1110            .unwrap()
1111            .add_configuration(Configuration::new(
1112                HashMap::from([
1113                    (Variable::new("var1"), 4),
1114                    (Variable::new("var2"), 0),
1115                    (Variable::new("var3"), 0),
1116                ]),
1117                HashMap::from([
1118                    (Location::new("loc1"), 0),
1119                    (Location::new("loc2"), 1),
1120                    (Location::new("loc3"), 4),
1121                ]),
1122            ))
1123            .unwrap()
1124            .build()
1125            .unwrap();
1126
1127        let res = ctx_mgr.check_spec(&spec);
1128
1129        assert!(res.is_some());
1130        assert_eq!(
1131            res.clone(),
1132            Some(path.clone()),
1133            "Got:\n{}\n\nExpected:\n{}\n\n",
1134            res.unwrap(),
1135            path
1136        );
1137    }
1138
1139    #[test]
1140    fn test_spec_reach_location_three_rules_one_guard_self_loop() {
1141        let test_spec = "
1142            skel test_ta1 {
1143                shared var1, var2, var3;
1144                parameters n, f;
1145                
1146                assumptions (1) {
1147                    n > 3 * f;
1148                    n == 1;
1149                    f == 0;
1150                }
1151
1152                locations (2) {
1153                    loc1 : [0];
1154                    loc2 : [1];
1155                    loc3 : [2];
1156                }
1157
1158                inits (1) {
1159                    loc1 == n - f;
1160                    loc2 == 0;
1161                    loc3 == 0;
1162                    var1 == 0;
1163                    var2 == 0;
1164                    var3 == 0;
1165                }
1166
1167                rules (4) {
1168                    0: loc1 -> loc1
1169                        when(true)
1170                        do {var1' == var1 + 1};
1171                    1: loc1 -> loc2
1172                        when(true)
1173                        do {};
1174                    2: loc2 -> loc3
1175                        when(var1 > 3 && var2 <= 0 && var1 <= 4)
1176                        do {};
1177                    
1178                }
1179
1180                specifications (1) {
1181                    test1:
1182                        [](loc2 == 0 || var2 > 6)
1183                }
1184            }
1185            ";
1186
1187        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
1188
1189        let ctx_mgr = EContextMgr::new(ta.clone(), &[], &SMTSolverBuilder::default())
1190            .expect("Failed to create context manager");
1191        let spec = UpwardsClosedSet::new_cover([Location::new("loc3")]);
1192
1193        let path = PathBuilder::new(ta)
1194            .add_parameter_assignment(HashMap::from([
1195                (Parameter::new("n"), 1),
1196                (Parameter::new("f"), 0),
1197            ]))
1198            .unwrap()
1199            .add_configuration(Configuration::new(
1200                HashMap::from([
1201                    (Variable::new("var1"), 0),
1202                    (Variable::new("var2"), 0),
1203                    (Variable::new("var3"), 0),
1204                ]),
1205                HashMap::from([
1206                    (Location::new("loc1"), 1),
1207                    (Location::new("loc2"), 0),
1208                    (Location::new("loc3"), 0),
1209                ]),
1210            ))
1211            .unwrap()
1212            .add_transition(Transition::new(
1213                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1214                    .with_action(Action::new_with_update(
1215                        Variable::new("var1"),
1216                        UpdateExpression::Inc(1),
1217                    ))
1218                    .build(),
1219                3,
1220            ))
1221            .unwrap()
1222            .add_configuration(Configuration::new(
1223                HashMap::from([
1224                    (Variable::new("var1"), 3),
1225                    (Variable::new("var2"), 0),
1226                    (Variable::new("var3"), 0),
1227                ]),
1228                HashMap::from([
1229                    (Location::new("loc1"), 1),
1230                    (Location::new("loc2"), 0),
1231                    (Location::new("loc3"), 0),
1232                ]),
1233            ))
1234            .unwrap()
1235            .add_transition(Transition::new(
1236                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1237                    .with_action(Action::new_with_update(
1238                        Variable::new("var1"),
1239                        UpdateExpression::Inc(1),
1240                    ))
1241                    .build(),
1242                1,
1243            ))
1244            .unwrap()
1245            .add_configuration(Configuration::new(
1246                HashMap::from([
1247                    (Variable::new("var1"), 4),
1248                    (Variable::new("var2"), 0),
1249                    (Variable::new("var3"), 0),
1250                ]),
1251                HashMap::from([
1252                    (Location::new("loc1"), 1),
1253                    (Location::new("loc2"), 0),
1254                    (Location::new("loc3"), 0),
1255                ]),
1256            ))
1257            .unwrap()
1258            .add_transition(Transition::new(
1259                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
1260                1,
1261            ))
1262            .unwrap()
1263            .add_configuration(Configuration::new(
1264                HashMap::from([
1265                    (Variable::new("var1"), 4),
1266                    (Variable::new("var2"), 0),
1267                    (Variable::new("var3"), 0),
1268                ]),
1269                HashMap::from([
1270                    (Location::new("loc1"), 0),
1271                    (Location::new("loc2"), 1),
1272                    (Location::new("loc3"), 0),
1273                ]),
1274            ))
1275            .unwrap()
1276            .add_transition(Transition::new(
1277                RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
1278                    .with_guard(
1279                        BooleanExpression::ComparisonExpression(
1280                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1281                            ComparisonOp::Gt,
1282                            Box::new(IntegerExpression::Const(3)),
1283                        ) & BooleanExpression::ComparisonExpression(
1284                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1285                            ComparisonOp::Leq,
1286                            Box::new(IntegerExpression::Const(0)),
1287                        ) & BooleanExpression::ComparisonExpression(
1288                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1289                            ComparisonOp::Leq,
1290                            Box::new(IntegerExpression::Const(4)),
1291                        ),
1292                    )
1293                    .build(),
1294                1,
1295            ))
1296            .unwrap()
1297            .add_configuration(Configuration::new(
1298                HashMap::from([
1299                    (Variable::new("var1"), 4),
1300                    (Variable::new("var2"), 0),
1301                    (Variable::new("var3"), 0),
1302                ]),
1303                HashMap::from([
1304                    (Location::new("loc1"), 0),
1305                    (Location::new("loc2"), 0),
1306                    (Location::new("loc3"), 1),
1307                ]),
1308            ))
1309            .unwrap()
1310            .build()
1311            .unwrap();
1312
1313        let res = ctx_mgr.check_spec(&spec);
1314
1315        assert!(res.is_some());
1316        assert_eq!(
1317            res.clone(),
1318            Some(path.clone()),
1319            "Got:\n{}\n\nExpected:\n{}\n\n",
1320            res.unwrap(),
1321            path
1322        );
1323    }
1324}