1use 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
43type StepCtx = LazyStepContext<GeneralThresholdAutomaton, ConfigCtx>;
45
46#[derive(Debug)]
47pub enum ContextMgrError {
48 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 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 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 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 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 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 fn compute_k_for_ta(
204 ta: &GeneralThresholdAutomaton,
205 target_constr: &[&LIAVariableConstraint],
206 ) -> Result<usize, ContextMgrError> {
207 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 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 fn encode_phi_reach(&mut self, spec: &DisjunctionTargetConfig) {
266 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 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 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 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 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 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 ) .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 #[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 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 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 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 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 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 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}