taco_smt_model_checker/
lib.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::error;
17
18use log::info;
19use smt_encoding::{ContextMgrError, EContextMgr};
20use taco_model_checker::{
21    ModelChecker, ModelCheckerResult, TargetSpec,
22    reachability_specification::{DisjunctionTargetConfig, ReachabilityProperty},
23};
24use taco_smt_encoder::SMTSolverBuilder;
25use taco_threshold_automaton::{
26    ThresholdAutomaton, general_threshold_automaton::GeneralThresholdAutomaton,
27};
28
29#[cfg(feature = "parallel")]
30use futures::StreamExt;
31#[cfg(feature = "parallel")]
32use futures::stream::FuturesUnordered;
33#[cfg(feature = "parallel")]
34use std::sync::{Arc, atomic::AtomicUsize};
35#[cfg(feature = "parallel")]
36use std::{collections::HashMap, sync::atomic::Ordering};
37#[cfg(feature = "parallel")]
38use taco_threshold_automaton::path::Path;
39#[cfg(feature = "parallel")]
40use tokio::runtime::Builder;
41#[cfg(feature = "parallel")]
42use tokio::task::JoinHandle;
43
44pub mod smt_encoding;
45
46/// Options to the SMT model checker
47#[derive(Debug, Clone, PartialEq, Default)]
48pub struct SMTModelCheckerOptions {
49    /// Enable concurrent model checking (default: false / off)
50    #[cfg(feature = "parallel")]
51    parallel: bool,
52}
53
54impl SMTModelCheckerOptions {
55    #[cfg(feature = "parallel")]
56    pub fn new_parallel() -> Self {
57        Self { parallel: true }
58    }
59
60    #[cfg(feature = "parallel")]
61    pub fn new(parallel: bool) -> Self {
62        Self { parallel }
63    }
64}
65
66/// Errors that can occur during the initialization of the SMT model checker
67#[derive(Debug, Clone, PartialEq, Eq)]
68pub enum SMTModelCheckerInitializationError {
69    /// Threshold automaton has decrements or resets
70    ResetsOrDecrements,
71}
72
73impl fmt::Display for SMTModelCheckerInitializationError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            SMTModelCheckerInitializationError::ResetsOrDecrements => {
77                write!(f, "Threshold automaton has decrements or resets")
78            }
79        }
80    }
81}
82
83impl error::Error for SMTModelCheckerInitializationError {}
84
85#[derive(Debug, Clone)]
86pub struct SMTModelChecker {
87    _opts: SMTModelCheckerOptions,
88    solver_builder: SMTSolverBuilder,
89    ta_spec: Vec<(DisjunctionTargetConfig, Vec<GeneralThresholdAutomaton>)>,
90}
91
92impl ModelChecker for SMTModelChecker {
93    type ModelCheckerContext = SMTSolverBuilder;
94
95    type ModelCheckerOptions = SMTModelCheckerOptions;
96
97    type SpecType = ReachabilityProperty;
98
99    type ThresholdAutomatonType = GeneralThresholdAutomaton;
100
101    type InitializationError = SMTModelCheckerInitializationError;
102
103    type ModelCheckingError = ContextMgrError;
104
105    fn initialize(
106        opts: Self::ModelCheckerOptions,
107        ta_spec: Vec<(DisjunctionTargetConfig, Vec<Self::ThresholdAutomatonType>)>,
108        ctx: Self::ModelCheckerContext,
109    ) -> Result<Self, Self::InitializationError> {
110        if ta_spec
111            .iter()
112            .any(|(_, tas)| tas.iter().any(|ta| ta.has_decrements_or_resets()))
113        {
114            return Err(SMTModelCheckerInitializationError::ResetsOrDecrements);
115        }
116
117        Ok(Self {
118            _opts: opts,
119            solver_builder: ctx,
120            ta_spec,
121        })
122    }
123
124    fn verify(
125        self,
126        abort_on_violation: bool,
127    ) -> Result<taco_model_checker::ModelCheckerResult, Self::ModelCheckingError> {
128        #[cfg(feature = "parallel")]
129        if self._opts.parallel {
130            return self.model_check_concurrent(abort_on_violation);
131        }
132
133        self.model_check_non_concurrent(abort_on_violation)
134    }
135}
136
137impl SMTModelChecker {
138    /// Execute the model checker sequentially
139    fn model_check_non_concurrent(
140        self,
141        abort_on_violation: bool,
142    ) -> Result<ModelCheckerResult, ContextMgrError> {
143        let mut unsafe_prop = Vec::new();
144
145        for (target, tas_to_check) in self.ta_spec.into_iter() {
146            info!(
147                "Starting to check property '{}', which requires {} model checker run(s).",
148                target.name(),
149                tas_to_check.len()
150            );
151            let mut found_counter_ex = false;
152            for ta in tas_to_check.into_iter() {
153                let target_var_constr = target
154                    .get_variable_constraint()
155                    .into_iter()
156                    .collect::<Vec<_>>();
157
158                let ctx_mgr = EContextMgr::new(ta, &target_var_constr, &self.solver_builder)?;
159                let res = ctx_mgr.check_spec(&target);
160                if let Some(p) = res {
161                    info!("Property {} is not satisfied!", target.name());
162
163                    unsafe_prop.push((target.name().to_string(), Box::new(p)));
164
165                    if abort_on_violation {
166                        return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
167                    }
168
169                    found_counter_ex = true;
170                    break;
171                }
172            }
173
174            if !found_counter_ex {
175                info!(
176                    "Finished verifying property '{}'. The property holds!",
177                    target.name()
178                );
179            }
180        }
181
182        if !unsafe_prop.is_empty() {
183            return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
184        }
185
186        Ok(ModelCheckerResult::SAFE)
187    }
188
189    #[cfg(feature = "parallel")]
190    /// Verify all properties concurrently
191    ///
192    /// This method will build a multi-threaded runtime and will attempt to
193    /// check each property concurrently to attempt to reduce the overall
194    /// runtime.
195    ///
196    /// TODO: The setup is still very rudimentary and could be fine tuned in the
197    /// future
198    fn model_check_concurrent(
199        self,
200        abort_on_violation: bool,
201    ) -> Result<ModelCheckerResult, ContextMgrError> {
202        use std::collections::HashMap;
203
204        let runtime = Builder::new_multi_thread()
205            .thread_name("taco-smt-mc")
206            .build()
207            .unwrap();
208
209        let mut completed_map = HashMap::new();
210
211        let futures = FuturesUnordered::new();
212        for (target, tas_to_check) in self.ta_spec.into_iter() {
213            info!(
214                "Queuing checks for property '{}', which requires {} model checker run(s).",
215                target.name(),
216                tas_to_check.len()
217            );
218
219            let completed_safe_counter = Arc::new(AtomicUsize::new(0));
220            completed_map.insert(
221                target.name().to_string(),
222                (tas_to_check.len(), completed_safe_counter.clone()),
223            );
224
225            for ta in tas_to_check.into_iter() {
226                let ft = runtime.spawn(Self::check_single_target(
227                    ta,
228                    target.clone(),
229                    self.solver_builder.clone(),
230                    completed_safe_counter.clone(),
231                ));
232                futures.push(ft.into_future());
233            }
234        }
235
236        let res = runtime.spawn(Self::wait_for_mc_results(
237            futures,
238            abort_on_violation,
239            completed_map,
240        ));
241        runtime
242            .block_on(res)
243            .expect("Task panicked or runtime error")
244    }
245
246    #[cfg(feature = "parallel")]
247    /// Helper method to collect the results and abort early if desired
248    ///
249    // TODO: early abort is currently not working. It would require rewriting
250    // the model checker in an asynchronous fashion
251    async fn wait_for_mc_results(
252        mut futures: FuturesUnordered<JoinHandle<Result<ModelCheckerResult, ContextMgrError>>>,
253        abort_on_violation: bool,
254        completed_map: HashMap<String, (usize, Arc<AtomicUsize>)>,
255    ) -> Result<ModelCheckerResult, ContextMgrError> {
256        let mut found_violations: Vec<(String, Box<Path>)> = Vec::new();
257
258        while let Some(result) = futures.next().await {
259            let res = result.expect("Task panicked or runtime error")?;
260
261            if let ModelCheckerResult::UNSAFE(mut violations) = res {
262                if abort_on_violation {
263                    futures.into_iter().for_each(|f| {
264                        f.abort();
265                    });
266                    return Ok(ModelCheckerResult::UNSAFE(violations));
267                }
268                found_violations.append(&mut violations);
269            }
270        }
271
272        for (property_name, (expected_safe, terminated_safe)) in completed_map {
273            if expected_safe == terminated_safe.load(Ordering::SeqCst) {
274                info!("Finished verifying property '{property_name}'. The property holds!")
275            }
276        }
277
278        if !found_violations.is_empty() {
279            return Ok(ModelCheckerResult::UNSAFE(found_violations));
280        }
281
282        Ok(ModelCheckerResult::SAFE)
283    }
284
285    #[cfg(feature = "parallel")]
286    /// Check reachability of a single [`DisjunctionTargetConfig`]
287    async fn check_single_target(
288        ta: GeneralThresholdAutomaton,
289        target: DisjunctionTargetConfig,
290        solver_builder: SMTSolverBuilder,
291        safe_counter: Arc<AtomicUsize>,
292    ) -> Result<ModelCheckerResult, ContextMgrError> {
293        let target_var_constr = target
294            .get_variable_constraint()
295            .into_iter()
296            .collect::<Vec<_>>();
297
298        let ctx_mgr = EContextMgr::new(ta, &target_var_constr, &solver_builder)?;
299        let res = ctx_mgr.check_spec(&target);
300
301        if let Some(p) = res {
302            info!("Property {} is not satisfied!", target.name());
303
304            return Ok(ModelCheckerResult::UNSAFE(vec![(
305                target.name().to_string(),
306                Box::new(p),
307            )]));
308        }
309
310        safe_counter.fetch_add(1, Ordering::SeqCst);
311        Ok(ModelCheckerResult::SAFE)
312    }
313}
314
315impl Default for SMTModelChecker {
316    fn default() -> Self {
317        Self::new_mc()
318    }
319}
320
321impl SMTModelChecker {
322    pub fn new_mc() -> Self {
323        Self {
324            _opts: SMTModelCheckerOptions::default(),
325            solver_builder: SMTSolverBuilder::default(),
326            ta_spec: Vec::new(),
327        }
328    }
329
330    pub fn new_mc_with_opts(opts: SMTModelCheckerOptions) -> Self {
331        Self {
332            _opts: opts,
333            solver_builder: SMTSolverBuilder::default(),
334            ta_spec: Vec::new(),
335        }
336    }
337}
338
339#[cfg(test)]
340mod tests {
341
342    use super::*;
343    use taco_model_checker::{ModelChecker, reachability_specification::TargetConfig};
344    use taco_parser::{ParseTA, ParseTAWithLTL, bymc::ByMCParser};
345    use taco_smt_encoder::SMTSolverBuilderCfg;
346    use taco_threshold_automaton::{
347        ModifiableThresholdAutomaton,
348        expressions::{
349            BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
350        },
351        general_threshold_automaton::{Action, UpdateExpression, builder::RuleBuilder},
352        path::{Configuration, PathBuilder, Transition},
353    };
354
355    #[test]
356    fn test_can_initialize_true() {
357        let test_spec = "
358            skel test_ta1 {
359                shared var1, var2, var3;
360                parameters n, f;
361                
362                assumptions (1) {
363                    n > 3 * f;
364                    n == 1;
365                }
366
367                locations (2) {
368                    loc1 : [0];
369                    loc2 : [1];
370                    loc3 : [2];
371                }
372
373                inits (1) {
374                    loc1 == n - f;
375                    loc2 == 0;
376                    loc3 == 0;
377                    var1 == 0;
378                    var2 == 0;
379                    var3 == 0;
380                }
381
382                rules (4) {
383                    0: loc1 -> loc2
384                        when(true)
385                        do {};
386                }
387            }
388            ";
389
390        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
391        let spec = DisjunctionTargetConfig::new_from_targets(
392            "test".into(),
393            [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
394        );
395
396        let mc = SMTModelChecker::initialize(
397            SMTModelCheckerOptions::default(),
398            vec![(spec, vec![ta])],
399            SMTSolverBuilder::default(),
400        );
401        assert!(mc.is_ok());
402    }
403
404    #[test]
405    fn test_can_initialize_false() {
406        let test_spec = "
407            skel test_ta1 {
408                shared var1, var2, var3;
409                parameters n, f;
410                
411                assumptions (1) {
412                    n > 3 * f;
413                    n == 1;
414                }
415
416                locations (2) {
417                    loc1 : [0];
418                    loc2 : [1];
419                    loc3 : [2];
420                }
421
422                inits (1) {
423                    loc1 == n - f;
424                    loc2 == 0;
425                    loc3 == 0;
426                    var1 == 0;
427                    var2 == 0;
428                    var3 == 0;
429                }
430
431                rules (4) {
432                    0: loc1 -> loc2
433                        when(true)
434                        do { var1' == 0; };
435                }
436            }
437            ";
438
439        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
440        let spec = DisjunctionTargetConfig::new_from_targets(
441            "test".into(),
442            [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
443        );
444
445        let mc = SMTModelChecker::initialize(
446            SMTModelCheckerOptions::default(),
447            vec![(spec, vec![ta])],
448            SMTSolverBuilder::default(),
449        );
450
451        assert!(mc.is_err());
452    }
453
454    #[test]
455    fn test_smt_model_checker_simple_unsafe() {
456        let test_spec = "
457            skel test_ta1 {
458                shared var1, var2, var3;
459                parameters n, f;
460                
461                assumptions (1) {
462                    n > 3 * f;
463                    n == 1;
464                }
465
466                locations (2) {
467                    loc1 : [0];
468                    loc2 : [1];
469                    loc3 : [2];
470                }
471
472                inits (1) {
473                    loc1 == n - f;
474                    loc2 == 0;
475                    loc3 == 0;
476                    var1 == 0;
477                    var2 == 0;
478                    var3 == 0;
479                }
480
481                rules (4) {
482                    0: loc1 -> loc2
483                        when(true)
484                        do {};
485                }
486
487                specifications (1) {
488                    test1:
489                        [](loc1 == 0)
490                }
491            }
492            ";
493
494        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
495        let spec = DisjunctionTargetConfig::new_from_targets(
496            "test".into(),
497            [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
498        );
499
500        let mc = SMTModelChecker::initialize(
501            SMTModelCheckerOptions::default(),
502            vec![(spec, vec![ta])],
503            SMTSolverBuilder::default(),
504        )
505        .unwrap();
506        let res = mc.verify(true);
507        assert!(res.is_ok());
508        assert!(matches!(res.unwrap(), ModelCheckerResult::UNSAFE(_)));
509    }
510
511    #[cfg(feature = "parallel")]
512    #[test]
513    fn test_smt_model_checker_simple_unsafe_parallel() {
514        let test_spec = "
515            skel test_ta1 {
516                shared var1, var2, var3;
517                parameters n, f;
518                
519                assumptions (1) {
520                    n > 3 * f;
521                    n == 1;
522                }
523
524                locations (2) {
525                    loc1 : [0];
526                    loc2 : [1];
527                    loc3 : [2];
528                }
529
530                inits (1) {
531                    loc1 == n - f;
532                    loc2 == 0;
533                    loc3 == 0;
534                    var1 == 0;
535                    var2 == 0;
536                    var3 == 0;
537                }
538
539                rules (4) {
540                    0: loc1 -> loc2
541                        when(true)
542                        do {};
543                }
544
545                specifications (1) {
546                    test1:
547                        [](loc1 == 0)
548                }
549            }
550            ";
551
552        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
553        let spec = DisjunctionTargetConfig::new_from_targets(
554            "test".into(),
555            [TargetConfig::new_cover([Location::new("loc1")]).unwrap()],
556        );
557
558        let mc = SMTModelChecker::initialize(
559            SMTModelCheckerOptions::new_parallel(),
560            vec![(spec, vec![ta])],
561            SMTSolverBuilder::default(),
562        )
563        .unwrap();
564        let res = mc.verify(true);
565        assert!(res.is_ok());
566        assert!(matches!(res.unwrap(), ModelCheckerResult::UNSAFE(_)));
567    }
568
569    #[test]
570    fn test_smt_model_checker_simple_safe() {
571        let test_spec = "
572            skel test_ta1 {
573                shared var1, var2, var3;
574                parameters n, f;
575                
576                assumptions (1) {
577                    n > 3 * f;
578                    n == 1;
579                }
580
581                locations (2) {
582                    loc1 : [0];
583                    loc2 : [1];
584                    loc3 : [2];
585                }
586
587                inits (1) {
588                    loc1 == n - f;
589                    loc2 == 0;
590                    loc3 == 0;
591                    var1 == 0;
592                    var2 == 0;
593                    var3 == 0;
594                }
595
596                rules (4) {
597                    0: loc1 -> loc2
598                        when(true)
599                        do {};
600                }
601
602                specifications (1) {
603                    test1:
604                        [](loc3 == 0)
605                }
606            }
607            ";
608
609        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
610        let spec = DisjunctionTargetConfig::new_from_targets(
611            "test".into(),
612            [TargetConfig::new_cover([Location::new("loc3")]).unwrap()],
613        );
614
615        let mc = SMTModelChecker::initialize(
616            SMTModelCheckerOptions::default(),
617            vec![(spec, vec![ta])],
618            SMTSolverBuilder::default(),
619        )
620        .unwrap();
621        let res = mc.verify(false);
622        assert!(res.is_ok());
623        assert!(matches!(res.unwrap(), ModelCheckerResult::SAFE));
624    }
625
626    #[cfg(feature = "parallel")]
627    #[test]
628    fn test_smt_model_checker_simple_safe_parallel() {
629        let test_spec = "
630            skel test_ta1 {
631                shared var1, var2, var3;
632                parameters n, f;
633                
634                assumptions (1) {
635                    n > 3 * f;
636                    n == 1;
637                }
638
639                locations (2) {
640                    loc1 : [0];
641                    loc2 : [1];
642                    loc3 : [2];
643                }
644
645                inits (1) {
646                    loc1 == n - f;
647                    loc2 == 0;
648                    loc3 == 0;
649                    var1 == 0;
650                    var2 == 0;
651                    var3 == 0;
652                }
653
654                rules (4) {
655                    0: loc1 -> loc2
656                        when(true)
657                        do {};
658                }
659
660                specifications (1) {
661                    test1:
662                        [](loc3 == 0)
663                }
664            }
665            ";
666
667        let ta = ByMCParser::new().parse_ta(test_spec).unwrap();
668        let spec = DisjunctionTargetConfig::new_from_targets(
669            "test".into(),
670            [TargetConfig::new_cover([Location::new("loc3")]).unwrap()],
671        );
672
673        let mc = SMTModelChecker::initialize(
674            SMTModelCheckerOptions::new_parallel(),
675            vec![(spec, vec![ta])],
676            SMTSolverBuilder::default(),
677        )
678        .unwrap();
679        let res = mc.verify(false);
680        assert!(res.is_ok());
681        assert!(matches!(res.unwrap(), ModelCheckerResult::SAFE));
682    }
683
684    #[test]
685    fn test_smt_model_checker_simple_safe_new_mc() {
686        let test_spec = "
687            skel test_ta1 {
688                shared var1, var2, var3;
689                parameters n, f;
690                
691                assumptions (1) {
692                    n > 3 * f;
693                    n == 1;
694                }
695
696                locations (2) {
697                    loc1 : [0];
698                    loc2 : [1];
699                    loc3 : [2];
700                }
701
702                inits (1) {
703                    loc1 == n - f;
704                    loc2 == 0;
705                    loc3 == 0;
706                    var1 == 0;
707                    var2 == 0;
708                    var3 == 0;
709                }
710
711                rules (4) {
712                    0: loc1 -> loc2
713                        when(true)
714                        do {};
715                }
716
717                specifications (1) {
718                    test1:
719                        [](loc3 == 0)
720                }
721            }
722          ";
723
724        let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
725
726        let mc = SMTModelChecker::new(
727            Some(SMTSolverBuilderCfg::new_z3()),
728            SMTModelCheckerOptions::default(),
729            Vec::new(),
730            ta,
731            spec.expressions().iter().cloned(),
732        )
733        .expect("Failed to create SMT model checker");
734
735        let res = mc.verify(false).expect("Failed to model check");
736        assert!(matches!(res, ModelCheckerResult::SAFE));
737    }
738
739    #[cfg(feature = "parallel")]
740    #[test]
741    fn test_smt_model_checker_simple_safe_new_mc_parallel() {
742        let test_spec = "
743            skel test_ta1 {
744                shared var1, var2, var3;
745                parameters n, f;
746                
747                assumptions (1) {
748                    n > 3 * f;
749                    n == 1;
750                }
751
752                locations (2) {
753                    loc1 : [0];
754                    loc2 : [1];
755                    loc3 : [2];
756                }
757
758                inits (1) {
759                    loc1 == n - f;
760                    loc2 == 0;
761                    loc3 == 0;
762                    var1 == 0;
763                    var2 == 0;
764                    var3 == 0;
765                }
766
767                rules (4) {
768                    0: loc1 -> loc2
769                        when(true)
770                        do {};
771                }
772
773                specifications (1) {
774                    test1:
775                        [](loc3 == 0)
776                }
777            }
778          ";
779
780        let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
781
782        let mc = SMTModelChecker::new(
783            Some(SMTSolverBuilderCfg::new_z3()),
784            SMTModelCheckerOptions::new_parallel(),
785            Vec::new(),
786            ta,
787            spec.expressions().iter().cloned(),
788        )
789        .expect("Failed to create SMT model checker");
790
791        let res = mc.verify(false).expect("Failed to model check");
792        assert!(matches!(res, ModelCheckerResult::SAFE));
793    }
794
795    #[test]
796    fn test_display_initialization_error() {
797        let error = SMTModelCheckerInitializationError::ResetsOrDecrements;
798        assert_eq!(
799            format!("{error}"),
800            "Threshold automaton has decrements or resets"
801        );
802    }
803
804    #[test]
805    fn test_full_model_checker_reach_location_three_rules_one_guard_self_loop_with_var_constr() {
806        let test_spec = "
807            skel test_ta1 {
808                shared var1, var2, var3;
809                parameters n, f;
810                
811                assumptions (1) {
812                    n > 3 * f;
813                    n == 1;
814                    f == 0;
815                }
816
817                locations (2) {
818                    loc1 : [0];
819                    loc2 : [1];
820                    loc3 : [2];
821                }
822
823                inits (1) {
824                    loc1 == n - f;
825                    loc2 == 0;
826                    loc3 == 0;
827                    var1 == 0;
828                    var2 == 0;
829                    var3 == 0;
830                }
831
832                rules (4) {
833                    0: loc1 -> loc1
834                        when(true)
835                        do {var1' == var1 + 1};
836                    1: loc1 -> loc2
837                        when(true)
838                        do {};
839                    2: loc2 -> loc3
840                        when(var1 > 3 && var2 <= 0 && var1 < 6 )
841                        do {};
842                    
843                }
844
845                specifications (1) {
846                    test1:
847                        []((loc3 == 0) || (var1 < 5))
848                }
849            }
850            ";
851
852        let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
853
854        let mc = SMTModelChecker::new(
855            Some(SMTSolverBuilderCfg::new_z3()),
856            SMTModelCheckerOptions::new(false),
857            Vec::new(),
858            ta.clone(),
859            spec.into_iter(),
860        );
861        let mc = mc.unwrap();
862        let res = mc.verify(true).unwrap();
863
864        // Replicate spec ta that is created for ta builder
865        let mut spec_ta = ta.clone();
866        spec_ta.set_name("test_ta1-test1".into());
867
868        // Replicate interval ta for path builder
869
870        let path = PathBuilder::new(spec_ta)
871            .add_parameter_assignment(HashMap::from([
872                (Parameter::new("n"), 1),
873                (Parameter::new("f"), 0),
874            ]))
875            .unwrap()
876            .add_configuration(Configuration::new(
877                HashMap::from([
878                    (Variable::new("var1"), 0),
879                    (Variable::new("var2"), 0),
880                    (Variable::new("var3"), 0),
881                ]),
882                HashMap::from([
883                    (Location::new("loc1"), 1),
884                    (Location::new("loc2"), 0),
885                    (Location::new("loc3"), 0),
886                ]),
887            ))
888            .unwrap()
889            .add_transition(Transition::new(
890                RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
891                    .with_action(Action::new_with_update(
892                        Variable::new("var1"),
893                        UpdateExpression::Inc(1),
894                    ))
895                    .build(),
896                5,
897            ))
898            .unwrap()
899            .add_configuration(Configuration::new(
900                HashMap::from([
901                    (Variable::new("var1"), 5),
902                    (Variable::new("var2"), 0),
903                    (Variable::new("var3"), 0),
904                ]),
905                HashMap::from([
906                    (Location::new("loc1"), 1),
907                    (Location::new("loc2"), 0),
908                    (Location::new("loc3"), 0),
909                ]),
910            ))
911            .unwrap()
912            .add_transition(Transition::new(
913                RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
914                1,
915            ))
916            .unwrap()
917            .add_configuration(Configuration::new(
918                HashMap::from([
919                    (Variable::new("var1"), 5),
920                    (Variable::new("var2"), 0),
921                    (Variable::new("var3"), 0),
922                ]),
923                HashMap::from([
924                    (Location::new("loc1"), 0),
925                    (Location::new("loc2"), 1),
926                    (Location::new("loc3"), 0),
927                ]),
928            ))
929            .unwrap()
930            .add_transition(Transition::new(
931                RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
932                    .with_guard(
933                        BooleanExpression::ComparisonExpression(
934                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
935                            ComparisonOp::Gt,
936                            Box::new(IntegerExpression::Const(3)),
937                        ) & BooleanExpression::ComparisonExpression(
938                            Box::new(IntegerExpression::Atom(Variable::new("var2"))),
939                            ComparisonOp::Leq,
940                            Box::new(IntegerExpression::Const(0)),
941                        ) & BooleanExpression::ComparisonExpression(
942                            Box::new(IntegerExpression::Atom(Variable::new("var1"))),
943                            ComparisonOp::Lt,
944                            Box::new(IntegerExpression::Const(6)),
945                        ),
946                    )
947                    .build(),
948                1,
949            ))
950            .unwrap()
951            .add_configuration(Configuration::new(
952                HashMap::from([
953                    (Variable::new("var1"), 5),
954                    (Variable::new("var2"), 0),
955                    (Variable::new("var3"), 0),
956                ]),
957                HashMap::from([
958                    (Location::new("loc1"), 0),
959                    (Location::new("loc2"), 0),
960                    (Location::new("loc3"), 1),
961                ]),
962            ))
963            .unwrap()
964            .build()
965            .unwrap();
966
967        let res = match res {
968            ModelCheckerResult::SAFE => unreachable!("checked above"),
969            ModelCheckerResult::UNSAFE(v) => {
970                assert_eq!(v.len(), 1);
971                *v[0].1.clone()
972            }
973            ModelCheckerResult::UNKNOWN(_) => todo!(),
974        };
975
976        assert_eq!(
977            res,
978            path.clone(),
979            "Got:\n{}\n\nExpected:\n{}\n\n",
980            res,
981            path
982        );
983    }
984}