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::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
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: &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 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: &UpwardsClosedSet) {
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
316 );
317 }
318
319 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 ) .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 #[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 = 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}