1mod paths;
5pub mod smt_encoder_steady;
6pub mod zcs;
7pub mod zcs_error_graph;
8
9use taco_interval_ta::IntervalThresholdAutomaton;
10
11use log::info;
12
13use taco_model_checker::ModelCheckerContext;
14use taco_model_checker::reachability_specification::{
15 DisjunctionTargetConfig, ReachabilityProperty, TargetConfig,
16};
17use taco_threshold_automaton::lia_threshold_automaton::LIAVariableConstraint;
18
19use std::fmt::Display;
20use taco_model_checker::{ModelChecker, ModelCheckerResult, SMTBddContext};
21use taco_threshold_automaton::ThresholdAutomaton;
22use zcs::ZCS;
23use zcs::ZCSStates;
24use zcs::builder::ZCSBuilder;
25use zcs_error_graph::ZCSErrorGraph;
26use zcs_error_graph::builder::ZCSErrorGraphBuilder;
27
28#[derive(Debug, Clone)]
34pub struct ZCSModelChecker {
35 ctx: SMTBddContext,
37 ta_spec: Vec<(DisjunctionTargetConfig, Vec<IntervalThresholdAutomaton>)>,
39 heuristics: ZCSModelCheckerHeuristics,
41}
42
43impl ZCSModelChecker {
44 fn compute_zcs<'a>(&'a self, ta: &'a IntervalThresholdAutomaton) -> ZCS<'a> {
46 ZCSBuilder::new(self.ctx.bdd_manager(), ta).build()
47 }
48
49 fn compute_enabled_shared_variable_states(
52 cs: &ZCS,
53 ta: &IntervalThresholdAutomaton,
54 target_config: &TargetConfig,
55 ) -> ZCSStates {
56 let var_constr = target_config.get_variable_constraint();
57 let interval_constr = ta
58 .get_interval_constraint(var_constr)
59 .expect("Failed to get target interval_constr");
60
61 ta.get_variables_constrained(&interval_constr)
62 .into_iter()
63 .map(|var| {
64 ta.get_enabled_intervals(&interval_constr, var)
66 .map(|interval| cs.get_sym_state_for_shared_interval(var, interval))
67 .fold(cs.new_empty_sym_state(), |acc, x| acc.union(&x))
68 })
69 .fold(cs.new_full_sym_state(), |acc, x| acc.intersection(&x))
70 }
71
72 fn compute_error_states(
74 cs: &ZCS,
75 ta: &IntervalThresholdAutomaton,
76 specification: &DisjunctionTargetConfig,
77 ) -> Result<ZCSStates, ZCSModelCheckerError> {
78 let mut error_states = cs.new_empty_sym_state();
79
80 for target_config in specification.get_target_configs() {
81 let mut err_state = cs.new_full_sym_state();
82
83 for (loc, _) in target_config.get_locations_to_cover() {
84 err_state = err_state.intersection(&cs.get_sym_state_for_loc(loc));
85 }
86
87 for uncov in target_config.get_locations_to_uncover() {
88 err_state = err_state.intersection(&cs.get_sym_state_for_loc(uncov).complement());
89 }
90
91 if target_config.get_variable_constraint() != &LIAVariableConstraint::True {
93 let interval_err_state =
94 Self::compute_enabled_shared_variable_states(cs, ta, target_config);
95 err_state = err_state.intersection(&interval_err_state);
96 }
97
98 error_states = error_states.union(&err_state);
99 }
100
101 Ok(error_states)
102 }
103
104 fn compute_symbolic_error_graph<'a>(
106 &'a self,
107 ta: &'a IntervalThresholdAutomaton,
108 spec: &DisjunctionTargetConfig,
109 ) -> ZCSErrorGraph<'a> {
110 let cs = self.compute_zcs(ta);
111
112 let error_states = Self::compute_error_states(&cs, ta, spec)
113 .expect("The specification {} is not supported by the symbolic model checker");
114
115 ZCSErrorGraphBuilder::new(cs.clone(), error_states).build()
116 }
117}
118
119impl ModelChecker for ZCSModelChecker {
120 type ModelCheckerContext = SMTBddContext;
121
122 type ModelCheckerOptions = Option<ZCSModelCheckerHeuristics>;
123
124 type SpecType = ReachabilityProperty;
125
126 type ThresholdAutomatonType = IntervalThresholdAutomaton;
127
128 type InitializationError = ZCSModelCheckerInitializationError;
129
130 type ModelCheckingError = ZCSModelCheckerError;
131
132 fn initialize(
133 opts: Self::ModelCheckerOptions,
134 ta_spec: Vec<(DisjunctionTargetConfig, Vec<Self::ThresholdAutomatonType>)>,
135 ctx: Self::ModelCheckerContext,
136 ) -> Result<Self, Self::InitializationError> {
137 let heuristics;
138
139 match opts.clone() {
140 None => {
142 if ta_spec
143 .iter()
144 .any(|(_, tas)| tas.iter().any(|ta| ta.has_decrements_or_resets()))
145 {
146 if ta_spec
147 .iter()
148 .any(|(_, tas)| tas.iter().any(|ta| ta.has_decrements()))
149 {
150 heuristics = ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics;
151 } else {
152 heuristics = ZCSModelCheckerHeuristics::ResetHeuristics;
153 }
154 } else {
155 heuristics = ZCSModelCheckerHeuristics::CanonicalHeuristics;
156 }
157 }
158 Some(h) => {
159 heuristics = h;
160 }
161 }
162
163 if opts == Some(ZCSModelCheckerHeuristics::CanonicalHeuristics)
164 && ta_spec
165 .iter()
166 .any(|(_, tas)| tas.iter().any(|ta| ta.has_decrements_or_resets()))
167 {
168 return Err(ZCSModelCheckerInitializationError::HeuristicsNotSuitable(
169 "Canonical heuristics".to_string(),
170 ));
171 }
172
173 Ok(Self {
174 ctx,
175 ta_spec,
176 heuristics,
177 })
178 }
179
180 fn verify(
181 self,
182 abort_on_violation: bool,
183 ) -> Result<ModelCheckerResult, Self::ModelCheckingError> {
184 let mut unsafe_prop = Vec::new();
185 let mut unknown_prop = Vec::new();
186
187 for (target, tas_to_check) in self.ta_spec.iter() {
188 info!(
189 "Starting to check property '{}', which requires {} model checker run(s).",
190 target.name(),
191 tas_to_check.len()
192 );
193 for ta in tas_to_check.iter() {
194 let error_graph = self.compute_symbolic_error_graph(ta, target);
195 if !error_graph.is_empty() {
196 if self.heuristics == ZCSModelCheckerHeuristics::EmptyErrorGraphHeuristics {
197 info!(
198 "The error graph is not empty, but the chosen heuristics only checks for emptiness. Therefore, it is unknown whether the property holds."
199 );
200 unknown_prop.push(target.name().to_string());
201 continue;
202 }
203 info!("The error graph is not empty, checking for non-spurious error paths.");
204 let res =
205 error_graph.contains_non_spurious_error_path(&ZCSModelCheckerContext::new(
206 &self.ctx,
207 self.heuristics.clone(),
208 ta,
209 target,
210 ));
211
212 if let Some(p) = res.non_spurious_path() {
213 info!("Property {} is not satisfied!", target.name());
214
215 unsafe_prop.push((target.name().to_string(), Box::new(p.clone())));
216
217 if abort_on_violation {
218 return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
219 }
220
221 break;
222 }
223
224 info!("All error paths are spurious.");
225 } else {
226 info!("The error graph is empty.");
227 }
228 }
229 }
230
231 if !unsafe_prop.is_empty() {
232 return Ok(ModelCheckerResult::UNSAFE(unsafe_prop));
233 }
234
235 if self.heuristics == ZCSModelCheckerHeuristics::EmptyErrorGraphHeuristics {
236 if unknown_prop.is_empty() {
237 return Ok(ModelCheckerResult::SAFE);
238 } else {
239 return Ok(ModelCheckerResult::UNKNOWN(unknown_prop));
240 }
241 }
242
243 Ok(ModelCheckerResult::SAFE)
244 }
245}
246
247impl Default for ZCSModelChecker {
248 fn default() -> Self {
249 Self::new_mc(SMTBddContext::try_new(None).expect("Failed to create SMTBddContext"))
250 }
251}
252
253impl ZCSModelChecker {
254 pub fn new_mc(ctx: SMTBddContext) -> Self {
256 Self {
257 ctx,
258 ta_spec: Vec::new(),
259 heuristics: ZCSModelCheckerHeuristics::CanonicalHeuristics,
260 }
261 }
262}
263
264#[derive(Debug)]
267pub enum ZCSModelCheckerError {
268 SpecNotSupported(String),
270}
271
272impl std::error::Error for ZCSModelCheckerError {}
273
274impl Display for ZCSModelCheckerError {
275 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
276 match self {
277 ZCSModelCheckerError::SpecNotSupported(spec) => write!(
278 f,
279 "This initialized symbolic model checker does not support the specification {spec}"
280 ),
281 }
282 }
283}
284
285#[derive(Debug)]
288pub enum ZCSModelCheckerInitializationError {
289 HeuristicsNotSuitable(String),
291}
292
293impl std::error::Error for ZCSModelCheckerInitializationError {}
294
295impl Display for ZCSModelCheckerInitializationError {
296 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
297 match self {
298 ZCSModelCheckerInitializationError::HeuristicsNotSuitable(heur) => write!(
299 f,
300 "The chosen heuristics {heur} is not suitable for the given threshold automaton",
301 ),
302 }
303 }
304}
305
306pub struct ZCSModelCheckerContext<'a> {
308 ctx: &'a SMTBddContext,
310 heuristics: ZCSModelCheckerHeuristics,
312 ta: &'a IntervalThresholdAutomaton,
314 spec: &'a DisjunctionTargetConfig,
316 print_spurious_ce: bool,
318}
319impl<'a> ZCSModelCheckerContext<'a> {
320 fn new(
322 ctx: &'a SMTBddContext,
323 heuristics: ZCSModelCheckerHeuristics,
324 ta: &'a IntervalThresholdAutomaton,
325 spec: &'a DisjunctionTargetConfig,
326 ) -> Self {
327 ZCSModelCheckerContext {
328 ctx,
329 heuristics,
330 ta,
331 spec,
332 print_spurious_ce: false,
333 }
334 }
335 fn smt_bdd_context(&self) -> &SMTBddContext {
337 self.ctx
338 }
339 fn heuristics(&self) -> ZCSModelCheckerHeuristics {
341 self.heuristics.clone()
342 }
343 fn ta(&self) -> &IntervalThresholdAutomaton {
345 self.ta
346 }
347 fn spec(&self) -> &DisjunctionTargetConfig {
349 self.spec
350 }
351 fn print_spurious_ce(&self) -> bool {
353 self.print_spurious_ce
354 }
355}
356
357#[derive(PartialEq, Clone, Debug)]
363pub enum ZCSModelCheckerHeuristics {
364 ResetHeuristics,
369 DecrementAndIncrementHeuristics,
374 CanonicalHeuristics,
379 EmptyErrorGraphHeuristics,
383}
384impl ZCSModelCheckerHeuristics {
385 pub fn new_reset_heuristics() -> Self {
387 ZCSModelCheckerHeuristics::ResetHeuristics
388 }
389 pub fn new_decrement_and_increment_heuristics() -> Self {
391 ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics
392 }
393 pub fn new_canonical_heuristics() -> Self {
395 ZCSModelCheckerHeuristics::CanonicalHeuristics
396 }
397 pub fn new_empty_error_graph_heuristics() -> Self {
399 ZCSModelCheckerHeuristics::EmptyErrorGraphHeuristics
400 }
401 pub fn is_monotonic(&self) -> bool {
403 match self {
404 ZCSModelCheckerHeuristics::ResetHeuristics => true,
405 ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics => false,
406 ZCSModelCheckerHeuristics::CanonicalHeuristics => true,
407 ZCSModelCheckerHeuristics::EmptyErrorGraphHeuristics => false,
408 }
409 }
410}
411
412#[cfg(test)]
413mod tests {
414 use crate::ZCSModelChecker;
415 use crate::ZCSModelCheckerHeuristics;
416 use std::collections::HashMap;
417 use std::collections::HashSet;
418 use taco_bdd::BDDManagerConfig;
419 use taco_interval_ta::IntervalThresholdAutomaton;
420 use taco_interval_ta::builder::IntervalTABuilder;
421 use taco_interval_ta::interval::Interval;
422 use taco_model_checker::ModelChecker;
423 use taco_model_checker::ModelCheckerContext;
424 use taco_model_checker::ModelCheckerResult;
425 use taco_model_checker::SMTBddContext;
426 use taco_model_checker::reachability_specification::DisjunctionTargetConfig;
427 use taco_model_checker::reachability_specification::TargetConfig;
428 use taco_parser::ParseTAWithLTL;
429 use taco_parser::bymc::ByMCParser;
430 use taco_smt_encoder::SMTSolverBuilder;
431 use taco_smt_encoder::SMTSolverBuilderCfg;
432 use taco_threshold_automaton::ModifiableThresholdAutomaton;
433 use taco_threshold_automaton::expressions::BooleanExpression;
434 use taco_threshold_automaton::expressions::ComparisonOp;
435 use taco_threshold_automaton::expressions::IntegerExpression;
436 use taco_threshold_automaton::expressions::fraction::Fraction;
437 use taco_threshold_automaton::expressions::{Location, Parameter, Variable};
438 use taco_threshold_automaton::general_threshold_automaton::Action;
439 use taco_threshold_automaton::general_threshold_automaton::UpdateExpression;
440 use taco_threshold_automaton::general_threshold_automaton::builder::RuleBuilder;
441
442 use taco_threshold_automaton::BooleanVarConstraint;
443 use taco_threshold_automaton::LocationConstraint;
444 use taco_threshold_automaton::ParameterConstraint;
445 use taco_threshold_automaton::path::Configuration;
446 use taco_threshold_automaton::path::PathBuilder;
447 use taco_threshold_automaton::path::Transition;
448
449 impl ZCSModelChecker {
450 fn new_test(ctx: SMTBddContext) -> Self {
452 ZCSModelChecker {
453 ctx,
454 ta_spec: Vec::new(),
455 heuristics: ZCSModelCheckerHeuristics::CanonicalHeuristics,
456 }
457 }
458 }
459
460 fn get_test_ata() -> IntervalThresholdAutomaton {
481 let ta_builder =
482 taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
483 .with_locations(vec![
484 Location::new("l0"),
485 Location::new("l1"),
486 Location::new("l2"),
487 ])
488 .unwrap()
489 .with_variables(vec![Variable::new("x")])
490 .unwrap()
491 .with_parameters(vec![
492 Parameter::new("n"),
493 Parameter::new("t"),
494 Parameter::new("f"),
495 ])
496 .unwrap()
497 .initialize()
498 .with_resilience_conditions(vec![
499 ParameterConstraint::ComparisonExpression(
500 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
501 ComparisonOp::Geq,
502 Box::new(IntegerExpression::Const(0)),
503 ),
504 ParameterConstraint::ComparisonExpression(
505 Box::new(IntegerExpression::Atom(Parameter::new("t"))),
506 ComparisonOp::Geq,
507 Box::new(IntegerExpression::Const(0)),
508 ),
509 ParameterConstraint::ComparisonExpression(
510 Box::new(IntegerExpression::Atom(Parameter::new("f"))),
511 ComparisonOp::Geq,
512 Box::new(IntegerExpression::Const(0)),
513 ),
514 ParameterConstraint::ComparisonExpression(
515 Box::new(IntegerExpression::Atom(Parameter::new("n")) - IntegerExpression::Atom(Parameter::new("t"))),
516 ComparisonOp::Gt,
517 Box::new(IntegerExpression::Const(1)),
518 )
519 ]).unwrap()
520 .with_initial_location_constraints(vec![
521 LocationConstraint::ComparisonExpression(
522 Box::new(IntegerExpression::Atom(Location::new("l0"))),
523 ComparisonOp::Eq,
524 Box::new(
525 IntegerExpression::Param(Parameter::new("n"))
526 - IntegerExpression::Param(Parameter::new("t")),
527 ),
528 ),
529 LocationConstraint::ComparisonExpression(
530 Box::new(IntegerExpression::Atom(Location::new("l1"))),
531 ComparisonOp::Eq,
532 Box::new(IntegerExpression::Const(0)),
533 ),
534 LocationConstraint::ComparisonExpression(
535 Box::new(IntegerExpression::Atom(Location::new("l2"))),
536 ComparisonOp::Eq,
537 Box::new(IntegerExpression::Const(0)),
538 ),
539 ])
540 .unwrap()
541 .with_initial_variable_constraints(vec![BooleanVarConstraint::ComparisonExpression(
542 Box::new(IntegerExpression::Atom(Variable::new("x"))),
543 ComparisonOp::Eq,
544 Box::new(IntegerExpression::Const(0)),
545 )])
546 .unwrap()
547 .with_rules(vec![
548 RuleBuilder::new(0, Location::new("l0"), Location::new("l1"))
549 .with_action(
550 Action::new(
551 Variable::new("x"),
552 IntegerExpression::Atom(Variable::new("x"))
553 + IntegerExpression::Const(1),
554 )
555 .unwrap(),
556 )
557 .build(),
558 RuleBuilder::new(1, Location::new("l0"), Location::new("l2"))
559 .with_guard(BooleanVarConstraint::ComparisonExpression(
560 Box::new(IntegerExpression::Atom(Variable::new("x"))),
561 ComparisonOp::Geq,
562 Box::new(
563 IntegerExpression::Param(Parameter::new("n"))
564 - IntegerExpression::Param(Parameter::new("t")),
565 ),
566 ))
567 .build(),
568 RuleBuilder::new(2, Location::new("l1"), Location::new("l2"))
569 .with_guard(BooleanVarConstraint::ComparisonExpression(
570 Box::new(IntegerExpression::Atom(Variable::new("x"))),
571 ComparisonOp::Geq,
572 Box::new(
573 IntegerExpression::Param(Parameter::new("n"))
574 - IntegerExpression::Param(Parameter::new("t")),
575 ),
576 ))
577 .build(),
578 ])
579 .unwrap();
580 let ta = ta_builder.build();
581 let lia =
582 taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
583 ta.clone(),
584 )
585 .unwrap();
586
587 let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
588 .build()
589 .unwrap();
590 let ata = interval_tas.next().unwrap();
591
592 let nxt = interval_tas.next();
593 assert!(nxt.is_none(), "Got additional ta {}", nxt.unwrap());
594 ata
595 }
596
597 #[test]
598 fn test_compute_symbolic_01_cs_no_bdd_mgr() {
599 let ctx = SMTBddContext::try_new(None).unwrap();
600
601 let smc = ZCSModelChecker::new_test(ctx);
602
603 let ata = get_test_ata();
604 let cs = smc.compute_zcs(&ata);
605
606 let initial_states = cs.initial_states();
607
608 let l_1 = cs.get_sym_state_for_loc(&Location::new("l1"));
609 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
610
611 let interval_0 =
613 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
614 let x_i_0 = cs.get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0.clone());
615
616 let correct_initial_states = l_1
617 .complement()
618 .intersection(&l_2.complement())
619 .intersection(&x_i_0);
620
621 assert!(initial_states.equal(&correct_initial_states));
622 }
623
624 #[test]
625 fn test_compute_symbolic_01_cs_some_bdd_mgr() {
626 let ctx = SMTBddContext::try_new(None).unwrap();
627
628 let smc = ZCSModelChecker::new_test(ctx);
629
630 let ata = get_test_ata();
631 let cs = smc.compute_zcs(&ata);
632
633 let initial_states = cs.initial_states();
634
635 let l_1 = cs.get_sym_state_for_loc(&Location::new("l1"));
636 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
637
638 let interval_0 =
640 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
641 let x_i_0 = cs.get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0.clone());
642
643 let correct_initial_states = l_1
644 .complement()
645 .intersection(&l_2.complement())
646 .intersection(&x_i_0);
647
648 assert!(initial_states.equal(&correct_initial_states));
649 }
650
651 #[test]
652 fn test_compute_error_states_cover() {
653 let ctx = SMTBddContext::try_new(None).unwrap();
654
655 let smc = ZCSModelChecker::new_test(ctx);
656
657 let spec = TargetConfig::new_cover([Location::new("l2")])
658 .unwrap()
659 .into_disjunct_with_name("test");
660
661 let ata = get_test_ata();
662 let cs = smc.compute_zcs(&ata);
663
664 let error_states = ZCSModelChecker::compute_error_states(&cs, &ata, &spec);
665
666 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
667
668 assert!(error_states.is_ok());
669 assert!(error_states.unwrap().equal(&l_2));
670 }
671
672 #[test]
673 fn test_compute_error_states_reachability() {
674 let ctx = SMTBddContext::try_new(None).unwrap();
675
676 let smc = ZCSModelChecker::new_test(ctx);
677
678 let spec = TargetConfig::new_reach(
679 HashSet::from([Location::new("l2")]),
680 HashSet::from([Location::new("l1")]),
681 )
682 .unwrap()
683 .into_disjunct_with_name("test");
684
685 let ata = get_test_ata();
686 let cs = smc.compute_zcs(&ata);
687
688 let error_states = ZCSModelChecker::compute_error_states(&cs, &ata, &spec);
689
690 let l_1 = cs.get_sym_state_for_loc(&Location::new("l1"));
691 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
692
693 assert!(error_states.is_ok());
694 assert!(
695 error_states
696 .unwrap()
697 .equal(&l_2.intersection(&l_1.complement()))
698 );
699 }
700
701 #[test]
702 fn test_compute_error_states_general_cover() {
703 let ctx = SMTBddContext::try_new(None).unwrap();
704
705 let smc = ZCSModelChecker::new_test(ctx);
706
707 let spec = TargetConfig::new_general_cover([(Location::new("l2"), 2)])
708 .unwrap()
709 .into_disjunct_with_name("test");
710
711 let ata = get_test_ata();
712 let cs = smc.compute_zcs(&ata);
713
714 let error_states = ZCSModelChecker::compute_error_states(&cs, &ata, &spec);
715
716 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
717
718 assert!(error_states.is_ok());
719 assert!(error_states.unwrap().equal(&l_2));
720 }
721
722 #[test]
723 fn test_compute_enabled_shared_variable_states() {
724 let ctx = SMTBddContext::try_new(None).unwrap();
725
726 let smc = ZCSModelChecker::new_test(ctx);
727
728 let spec = TargetConfig::new_reach_with_var_constr(
729 [(Location::new("l2"), 2)],
730 [],
731 BooleanExpression::ComparisonExpression(
732 Box::new(IntegerExpression::Atom(Variable::new("x"))),
733 ComparisonOp::Eq,
734 Box::new(IntegerExpression::Const(0)),
735 ),
736 )
737 .unwrap();
738
739 let ata = get_test_ata();
740 let cs = smc.compute_zcs(&ata);
741
742 let error_interval_state =
743 ZCSModelChecker::compute_enabled_shared_variable_states(&cs, &ata, &spec);
744
745 let i_1 = cs
746 .get_sym_state_for_shared_interval(&Variable::new("x"), &Interval::new_constant(0, 1));
747
748 assert!(error_interval_state.equal(&i_1));
749 }
750
751 #[test]
752 fn test_compute_error_states_reach_with_var_constr() {
753 let ctx = SMTBddContext::try_new(None).unwrap();
754
755 let smc = ZCSModelChecker::new_test(ctx);
756
757 let spec = TargetConfig::new_reach_with_var_constr(
758 [(Location::new("l2"), 2)],
759 [],
760 BooleanExpression::ComparisonExpression(
761 Box::new(IntegerExpression::Atom(Variable::new("x"))),
762 ComparisonOp::Eq,
763 Box::new(IntegerExpression::Const(0)),
764 ),
765 )
766 .unwrap()
767 .into_disjunct_with_name("test");
768
769 let ata = get_test_ata();
770 let cs = smc.compute_zcs(&ata);
771
772 let error_states = ZCSModelChecker::compute_error_states(&cs, &ata, &spec);
773
774 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
775 let i_1 = cs
776 .get_sym_state_for_shared_interval(&Variable::new("x"), &Interval::new_constant(0, 1));
777 let expected_err_state = l_2.intersection(&i_1);
778
779 assert!(error_states.is_ok());
780 assert!(error_states.unwrap().equal(&expected_err_state));
781 }
782
783 #[test]
784 fn test_compute_error_states_all_spec_types() {
785 let ctx = SMTBddContext::try_new(None).unwrap();
786
787 let smc = ZCSModelChecker::new_test(ctx);
788
789 let cover = TargetConfig::new_cover([Location::new("l2")]).unwrap();
790
791 let general_cover = TargetConfig::new_general_cover([(Location::new("l2"), 2)]).unwrap();
792
793 let reach = TargetConfig::new_general_reach(
794 HashMap::from([(Location::new("l2"), 2)]),
795 HashSet::new(),
796 )
797 .unwrap();
798
799 let spec = DisjunctionTargetConfig::new_from_targets(
800 "test".to_string(),
801 [cover, general_cover, reach],
802 );
803
804 let ata = get_test_ata();
805 let cs = smc.compute_zcs(&ata);
806
807 let error_states = ZCSModelChecker::compute_error_states(&cs, &ata, &spec);
808
809 let l_2 = cs.get_sym_state_for_loc(&Location::new("l2"));
810
811 assert!(error_states.is_ok());
812 assert!(error_states.unwrap().equal(&l_2));
813 }
814
815 #[test]
816 fn test_compute_symbolic_error_graph() {
817 let ctx = SMTBddContext::try_new(None).unwrap();
818
819 let smc = ZCSModelChecker::new_test(ctx);
820
821 let ata = get_test_ata();
822
823 let spec = TargetConfig::new_reach(
824 HashSet::from([Location::new("l2")]),
825 HashSet::from([Location::new("l0"), Location::new("l1")]),
826 )
827 .unwrap()
828 .into_disjunct_with_name("test");
829
830 let sym_err_graph = smc.compute_symbolic_error_graph(&ata, &spec);
831
832 assert!(!sym_err_graph.is_empty());
833 }
834
835 #[test]
836 fn test_full_model_checker_reach_location_three_rules_one_guard_self_loop() {
837 let test_spec = "
838 skel test_ta1 {
839 shared var1, var2, var3;
840 parameters n, f;
841
842 assumptions (1) {
843 n > 3 * f;
844 n == 1;
845 f == 0;
846 }
847
848 locations (2) {
849 loc1 : [0];
850 loc2 : [1];
851 loc3 : [2];
852 }
853
854 inits (1) {
855 loc1 == n - f;
856 loc2 == 0;
857 loc3 == 0;
858 var1 == 0;
859 var2 == 0;
860 var3 == 0;
861 }
862
863 rules (4) {
864 0: loc1 -> loc1
865 when(true)
866 do {var1' == var1 + 1};
867 1: loc1 -> loc2
868 when(true)
869 do {};
870 2: loc2 -> loc3
871 when(var1 > 3 && var2 <= 0 && var1 <= 4)
872 do {};
873
874 }
875
876 specifications (1) {
877 test1:
878 [](loc3 == 0)
879 }
880 }
881 ";
882
883 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
884
885 let mc = ZCSModelChecker::new(
886 Some((
887 Some(SMTSolverBuilderCfg::new_z3()),
888 Some(BDDManagerConfig::new_cudd()),
889 )),
890 Some(ZCSModelCheckerHeuristics::CanonicalHeuristics),
891 Vec::new(),
892 ta.clone(),
893 spec.into_iter(),
894 );
895 let mc = mc.unwrap();
896 let res = mc.verify(true).unwrap();
897
898 let mut spec_ta = ta.clone();
900 spec_ta.set_name("test_ta1-test1".into());
901
902 let path = PathBuilder::new(spec_ta)
905 .add_parameter_assignment(HashMap::from([
906 (Parameter::new("n"), 1),
907 (Parameter::new("f"), 0),
908 ]))
909 .unwrap()
910 .add_configuration(Configuration::new(
911 HashMap::from([
912 (Variable::new("var1"), 0),
913 (Variable::new("var2"), 0),
914 (Variable::new("var3"), 0),
915 ]),
916 HashMap::from([
917 (Location::new("loc1"), 1),
918 (Location::new("loc2"), 0),
919 (Location::new("loc3"), 0),
920 ]),
921 ))
922 .unwrap()
923 .add_transition(Transition::new(
924 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
925 .with_action(Action::new_with_update(
926 Variable::new("var1"),
927 UpdateExpression::Inc(1),
928 ))
929 .build(),
930 1,
931 ))
932 .unwrap()
933 .add_configuration(Configuration::new(
934 HashMap::from([
935 (Variable::new("var1"), 1),
936 (Variable::new("var2"), 0),
937 (Variable::new("var3"), 0),
938 ]),
939 HashMap::from([
940 (Location::new("loc1"), 1),
941 (Location::new("loc2"), 0),
942 (Location::new("loc3"), 0),
943 ]),
944 ))
945 .unwrap()
946 .add_transition(Transition::new(
947 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
948 .with_action(Action::new_with_update(
949 Variable::new("var1"),
950 UpdateExpression::Inc(1),
951 ))
952 .build(),
953 2,
954 ))
955 .unwrap()
956 .add_configuration(Configuration::new(
957 HashMap::from([
958 (Variable::new("var1"), 3),
959 (Variable::new("var2"), 0),
960 (Variable::new("var3"), 0),
961 ]),
962 HashMap::from([
963 (Location::new("loc1"), 1),
964 (Location::new("loc2"), 0),
965 (Location::new("loc3"), 0),
966 ]),
967 ))
968 .unwrap()
969 .add_transition(Transition::new(
970 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
971 .with_action(Action::new_with_update(
972 Variable::new("var1"),
973 UpdateExpression::Inc(1),
974 ))
975 .build(),
976 1,
977 ))
978 .unwrap()
979 .add_configuration(Configuration::new(
980 HashMap::from([
981 (Variable::new("var1"), 4),
982 (Variable::new("var2"), 0),
983 (Variable::new("var3"), 0),
984 ]),
985 HashMap::from([
986 (Location::new("loc1"), 1),
987 (Location::new("loc2"), 0),
988 (Location::new("loc3"), 0),
989 ]),
990 ))
991 .unwrap()
992 .add_transition(Transition::new(
993 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
994 1,
995 ))
996 .unwrap()
997 .add_configuration(Configuration::new(
998 HashMap::from([
999 (Variable::new("var1"), 4),
1000 (Variable::new("var2"), 0),
1001 (Variable::new("var3"), 0),
1002 ]),
1003 HashMap::from([
1004 (Location::new("loc1"), 0),
1005 (Location::new("loc2"), 1),
1006 (Location::new("loc3"), 0),
1007 ]),
1008 ))
1009 .unwrap()
1010 .add_transition(Transition::new(
1011 RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
1012 .with_guard(
1013 BooleanExpression::ComparisonExpression(
1014 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1015 ComparisonOp::Gt,
1016 Box::new(IntegerExpression::Const(3)),
1017 ) & BooleanExpression::ComparisonExpression(
1018 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1019 ComparisonOp::Leq,
1020 Box::new(IntegerExpression::Const(0)),
1021 ) & BooleanExpression::ComparisonExpression(
1022 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1023 ComparisonOp::Leq,
1024 Box::new(IntegerExpression::Const(4)),
1025 ),
1026 )
1027 .build(),
1028 1,
1029 ))
1030 .unwrap()
1031 .add_configuration(Configuration::new(
1032 HashMap::from([
1033 (Variable::new("var1"), 4),
1034 (Variable::new("var2"), 0),
1035 (Variable::new("var3"), 0),
1036 ]),
1037 HashMap::from([
1038 (Location::new("loc1"), 0),
1039 (Location::new("loc2"), 0),
1040 (Location::new("loc3"), 1),
1041 ]),
1042 ))
1043 .unwrap()
1044 .build()
1045 .unwrap();
1046
1047 let res = match res {
1048 ModelCheckerResult::SAFE => unreachable!("checked above"),
1049 ModelCheckerResult::UNSAFE(v) => {
1050 assert_eq!(v.len(), 1);
1051 *v[0].1.clone()
1052 }
1053 ModelCheckerResult::UNKNOWN(_) => todo!(),
1054 };
1055
1056 assert_eq!(
1057 res,
1058 path.clone(),
1059 "Got:\n{}\n\nExpected:\n{}\n\n",
1060 res,
1061 path
1062 );
1063 }
1064
1065 #[test]
1066 fn test_full_model_checker_reach_location_three_rules_one_guard_self_loop_with_var_constr() {
1067 let test_spec = "
1068 skel test_ta1 {
1069 shared var1, var2, var3;
1070 parameters n, f;
1071
1072 assumptions (1) {
1073 n > 3 * f;
1074 n == 1;
1075 f == 0;
1076 }
1077
1078 locations (2) {
1079 loc1 : [0];
1080 loc2 : [1];
1081 loc3 : [2];
1082 }
1083
1084 inits (1) {
1085 loc1 == n - f;
1086 loc2 == 0;
1087 loc3 == 0;
1088 var1 == 0;
1089 var2 == 0;
1090 var3 == 0;
1091 }
1092
1093 rules (4) {
1094 0: loc1 -> loc1
1095 when(true)
1096 do {var1' == var1 + 1};
1097 1: loc1 -> loc2
1098 when(true)
1099 do {};
1100 2: loc2 -> loc3
1101 when(var1 > 3 && var2 <= 0 && var1 < 6 )
1102 do {};
1103
1104 }
1105
1106 specifications (1) {
1107 test1:
1108 []((loc3 == 0) || (var1 < 5))
1109 }
1110 }
1111 ";
1112
1113 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
1114
1115 let mc = ZCSModelChecker::new(
1116 Some((
1117 Some(SMTSolverBuilderCfg::new_z3()),
1118 Some(BDDManagerConfig::new_cudd()),
1119 )),
1120 Some(ZCSModelCheckerHeuristics::CanonicalHeuristics),
1121 Vec::new(),
1122 ta.clone(),
1123 spec.into_iter(),
1124 );
1125 let mc = mc.unwrap();
1126 let res = mc.verify(true).unwrap();
1127
1128 let mut spec_ta = ta.clone();
1130 spec_ta.set_name("test_ta1-test1".into());
1131
1132 let path = PathBuilder::new(spec_ta)
1135 .add_parameter_assignment(HashMap::from([
1136 (Parameter::new("n"), 1),
1137 (Parameter::new("f"), 0),
1138 ]))
1139 .unwrap()
1140 .add_configuration(Configuration::new(
1141 HashMap::from([
1142 (Variable::new("var1"), 0),
1143 (Variable::new("var2"), 0),
1144 (Variable::new("var3"), 0),
1145 ]),
1146 HashMap::from([
1147 (Location::new("loc1"), 1),
1148 (Location::new("loc2"), 0),
1149 (Location::new("loc3"), 0),
1150 ]),
1151 ))
1152 .unwrap()
1153 .add_transition(Transition::new(
1154 RuleBuilder::new(0, Location::new("loc1"), Location::new("loc1"))
1155 .with_action(Action::new_with_update(
1156 Variable::new("var1"),
1157 UpdateExpression::Inc(1),
1158 ))
1159 .build(),
1160 5,
1161 ))
1162 .unwrap()
1163 .add_configuration(Configuration::new(
1164 HashMap::from([
1165 (Variable::new("var1"), 5),
1166 (Variable::new("var2"), 0),
1167 (Variable::new("var3"), 0),
1168 ]),
1169 HashMap::from([
1170 (Location::new("loc1"), 1),
1171 (Location::new("loc2"), 0),
1172 (Location::new("loc3"), 0),
1173 ]),
1174 ))
1175 .unwrap()
1176 .add_transition(Transition::new(
1177 RuleBuilder::new(1, Location::new("loc1"), Location::new("loc2")).build(),
1178 1,
1179 ))
1180 .unwrap()
1181 .add_configuration(Configuration::new(
1182 HashMap::from([
1183 (Variable::new("var1"), 5),
1184 (Variable::new("var2"), 0),
1185 (Variable::new("var3"), 0),
1186 ]),
1187 HashMap::from([
1188 (Location::new("loc1"), 0),
1189 (Location::new("loc2"), 1),
1190 (Location::new("loc3"), 0),
1191 ]),
1192 ))
1193 .unwrap()
1194 .add_transition(Transition::new(
1195 RuleBuilder::new(2, Location::new("loc2"), Location::new("loc3"))
1196 .with_guard(
1197 BooleanExpression::ComparisonExpression(
1198 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1199 ComparisonOp::Gt,
1200 Box::new(IntegerExpression::Const(3)),
1201 ) & BooleanExpression::ComparisonExpression(
1202 Box::new(IntegerExpression::Atom(Variable::new("var2"))),
1203 ComparisonOp::Leq,
1204 Box::new(IntegerExpression::Const(0)),
1205 ) & BooleanExpression::ComparisonExpression(
1206 Box::new(IntegerExpression::Atom(Variable::new("var1"))),
1207 ComparisonOp::Lt,
1208 Box::new(IntegerExpression::Const(6)),
1209 ),
1210 )
1211 .build(),
1212 1,
1213 ))
1214 .unwrap()
1215 .add_configuration(Configuration::new(
1216 HashMap::from([
1217 (Variable::new("var1"), 5),
1218 (Variable::new("var2"), 0),
1219 (Variable::new("var3"), 0),
1220 ]),
1221 HashMap::from([
1222 (Location::new("loc1"), 0),
1223 (Location::new("loc2"), 0),
1224 (Location::new("loc3"), 1),
1225 ]),
1226 ))
1227 .unwrap()
1228 .build()
1229 .unwrap();
1230
1231 let res = match res {
1232 ModelCheckerResult::SAFE => unreachable!("checked above"),
1233 ModelCheckerResult::UNSAFE(v) => {
1234 assert_eq!(v.len(), 1);
1235 *v[0].1.clone()
1236 }
1237 ModelCheckerResult::UNKNOWN(_) => todo!(),
1238 };
1239
1240 assert_eq!(
1241 res,
1242 path.clone(),
1243 "Got:\n{}\n\nExpected:\n{}\n\n",
1244 res,
1245 path
1246 );
1247 }
1248}