1pub mod builder;
5
6use log::info;
7
8use std::collections::VecDeque;
9
10use taco_interval_ta::IntervalTARule;
11use taco_interval_ta::interval::Interval;
12use taco_threshold_automaton::expressions::{Location, Variable};
13use zcs::*;
14
15use crate::paths::{SteadyErrorPath, SteadyPath, VariableAssignment};
16use crate::smt_encoder_steady::SpuriousResult;
17use crate::{ZCSModelCheckerContext, zcs};
18
19#[derive(Debug, Clone)]
21pub struct ZCSErrorGraph<'a> {
22 error_states: ZCSStates,
24 explored_states: Vec<ZCSStates>,
27 initial_states: ZCSStates,
29 cs: ZCS<'a>,
31}
32impl ZCSErrorGraph<'_> {
33 pub fn is_empty(&self) -> bool {
35 self.initial_states.is_empty()
36 }
37
38 pub fn initial_states(&self) -> ZCSStates {
40 self.initial_states.clone()
41 }
42
43 pub fn compute_successors(&self, from: ZCSStates, with: SymbolicTransition) -> ZCSStates {
45 let from = self.cs.abstract_rule_vars(from);
47 let mut succ = self.cs.compute_successor(from, with.clone());
48 succ = self.cs.abstract_rule_vars(succ);
50 let mut intersection = self.cs.new_empty_sym_state();
52 for i in 0..self.explored_states.len() {
53 intersection = intersection.union(&(self.explored_states[i].intersection(&succ)));
54 }
55 intersection
56 }
57
58 pub fn get_non_error_intersection(&self, states: ZCSStates) -> (bool, ZCSStates) {
61 let intersection = self.explored_states[0].complement().intersection(&states);
62 if !intersection.is_empty() {
63 (true, intersection)
64 } else {
65 (false, intersection)
66 }
67 }
68
69 pub fn get_error_intersection(&self, states: ZCSStates) -> (bool, ZCSStates) {
72 let intersection = self.error_states.intersection(&states);
73 if !intersection.is_empty() {
74 (true, intersection)
75 } else {
76 (false, intersection)
77 }
78 }
79
80 pub fn get_sym_state_for_loc(&self, loc: &Location) -> ZCSStates {
82 self.cs.get_sym_state_for_loc(loc)
83 }
84
85 pub fn get_sym_state_for_shared_interval(
87 &self,
88 shared: &Variable,
89 interval: &Interval,
90 ) -> ZCSStates {
91 self.cs.get_sym_state_for_shared_interval(shared, interval)
92 }
93
94 pub fn get_sym_state_for_rule(&self, rule: &IntervalTARule) -> ZCSStates {
96 self.cs.get_sym_state_for_rule(rule)
97 }
98
99 pub fn transitions(&self) -> impl Iterator<Item = &SymbolicTransition> {
101 self.cs.transitions()
102 }
103
104 pub fn new_empty_sym_state(&self) -> ZCSStates {
106 self.cs.new_empty_sym_state()
107 }
108
109 pub fn contains_non_spurious_error_path(
160 &self,
161 smc_ctx: &ZCSModelCheckerContext,
162 ) -> SpuriousResult {
163 let all_possible_assignments = self.get_all_possible_variable_assignments();
165
166 let (non_empty, initial_error_states) = self.get_error_intersection(self.initial_states());
169 if non_empty {
170 for assignment in all_possible_assignments.iter() {
171 if initial_error_states.contains_sym_assignment(assignment) {
172 let error_path = SteadyErrorPath::new(
173 SteadyPath::new(
174 vec![],
175 initial_error_states.intersect_assignment(assignment),
176 assignment.clone(),
177 ),
178 self,
179 );
180
181 let res = error_path.is_non_spurious(true, smc_ctx);
182 if res.is_non_spurious() {
183 return res;
184 }
185 }
186 }
187 }
188
189 let reachable_assignments = self.compute_reachable_variable_assignments();
191 let mut queue: VecDeque<SteadyErrorPath> = VecDeque::new();
192 for assignment in reachable_assignments.clone() {
193 if self.initial_states().contains_sym_assignment(&assignment) {
194 let initial_states_for_assignment =
195 self.initial_states.intersect_assignment(&assignment);
196 let initial_steady_path =
197 self.construct_steady_path(initial_states_for_assignment, assignment);
198 queue.push_back(SteadyErrorPath::new(initial_steady_path, self));
199 }
200 }
201
202 let mut spurious_ce_count = 0;
205 let mut next_to_print = 1;
206
207 while !queue.is_empty() {
208 let steady_error_path = queue.pop_front().unwrap();
209 let last_states = steady_error_path.get_last_states();
210 let last_assignment = steady_error_path.get_last_assignment();
211 for rule in self.cs.transitions() {
212 let successor_states = self.compute_successors(last_states.clone(), rule.clone());
213
214 let (non_empty, error_intersection) =
216 self.get_error_intersection(successor_states.clone());
217 if non_empty {
218 for assignment in all_possible_assignments.clone() {
220 if error_intersection.contains_sym_assignment(&assignment) {
221 let mut updated_steady_path = steady_error_path.clone();
222 let error_steady_path =
223 SteadyPath::new(vec![], error_intersection.clone(), assignment);
224 updated_steady_path.add_successor(rule, error_steady_path);
225
226 let res = updated_steady_path.is_non_spurious(true, smc_ctx);
227 if res.is_non_spurious() {
228 return res;
229 } else if smc_ctx.print_spurious_ce() {
230 info!("Spurious counterexample found: {updated_steady_path}");
231 }
232 }
233 }
234 }
235 let (non_empty, mut non_error_intersection) =
237 self.get_non_error_intersection(successor_states.clone());
238 if non_empty {
239 if smc_ctx.heuristics().is_monotonic()
240 || steady_error_path.last_is_monotonic(smc_ctx.ta())
241 {
242 non_error_intersection =
243 non_error_intersection.remove_assignment(&last_assignment);
244 }
245 if non_error_intersection.is_empty() {
246 continue;
247 }
248 for assignment in reachable_assignments.iter() {
249 if non_error_intersection.contains_sym_assignment(assignment) {
250 let mut updated_steady_path = steady_error_path.clone();
251 let non_error_states_for_assignment =
252 non_error_intersection.intersect_assignment(assignment);
253 let successor_steady_path = self.construct_steady_path(
254 non_error_states_for_assignment,
255 assignment.clone(),
256 );
257 updated_steady_path.add_successor(rule, successor_steady_path);
258 let res = updated_steady_path.is_non_spurious(false, smc_ctx);
259 if res.is_non_spurious() {
260 queue.push_back(updated_steady_path);
261 } else if smc_ctx.print_spurious_ce() {
262 spurious_ce_count += 1;
263 if spurious_ce_count == next_to_print {
264 info!("Spurious subpath found: {updated_steady_path}");
265 next_to_print *= 10;
266 }
267 }
268 }
269 }
270 }
271 }
272 }
273 SpuriousResult::new_spurious()
275 }
276
277 fn get_all_possible_variable_assignments(&self) -> Vec<SymbolicVariableAssignment> {
279 let mut possible_assignments = vec![VariableAssignment::new()];
280 for shared in self.variables() {
281 let mut updated_assignments = Vec::new();
282 for assignment in possible_assignments {
283 let intervals = self.get_ordered_intervals_for_shared(shared);
284 for interval in intervals {
285 let mut updated_assignment = assignment.clone();
286 updated_assignment
287 .add_shared_var_interval(shared.clone(), interval.clone())
288 .expect("failed to update variable_assignment");
289 updated_assignments.push(updated_assignment);
290 }
291 }
292 possible_assignments = updated_assignments;
293 }
294
295 possible_assignments
296 .iter()
297 .map(|assign| self.cs.get_sym_var_assignment(assign.clone()))
298 .collect()
299 }
300
301 fn compute_reachable_variable_assignments(&self) -> Vec<SymbolicVariableAssignment> {
303 let possible_assignments = self.get_all_possible_variable_assignments();
304
305 possible_assignments
306 .into_iter()
307 .filter(|assign| {
308 self.explored_states[1..self.explored_states.len()]
309 .iter()
310 .any(|state| state.contains_sym_assignment(assign))
311 })
312 .collect()
313 }
314
315 fn construct_steady_path(
321 &self,
322 sym_state: ZCSStates,
323 assignment: SymbolicVariableAssignment,
324 ) -> SteadyPath {
325 let mut reachable_states = sym_state;
326 let mut applied_rules = Vec::new();
327 let mut reachable_states_changed = true;
328 while reachable_states_changed {
329 reachable_states_changed = false;
330 for rule in self.cs.transitions() {
331 let mut succ = self
332 .cs
333 .compute_successor(reachable_states.clone(), rule.clone());
334 succ = succ.intersect_assignment(&assignment);
336 let (not_empty, non_error_succ) = self.get_non_error_intersection(succ.clone());
338 if not_empty {
339 if !applied_rules.contains(&rule) {
340 applied_rules.push(rule);
341 }
342 let updated_reachable_states = reachable_states.union(&non_error_succ);
343 if !updated_reachable_states.equal(&reachable_states) {
344 reachable_states = updated_reachable_states;
346 reachable_states_changed = true;
347 break;
348 }
349 }
350 }
351 }
352 SteadyPath::new(applied_rules, reachable_states, assignment)
353 }
354
355 pub fn locations(&self) -> impl Iterator<Item = &Location> {
357 self.cs.locations()
358 }
359
360 pub fn variables(&self) -> impl Iterator<Item = &Variable> {
362 self.cs.variables()
363 }
364
365 pub fn get_ordered_intervals_for_shared(&self, shared: &Variable) -> &Vec<Interval> {
367 self.cs.get_ordered_intervals_for_shared(shared)
368 }
369}
370
371#[cfg(test)]
372mod tests {
373 use crate::IntervalThresholdAutomaton;
374 use crate::ZCSErrorGraphBuilder;
375 use crate::zcs;
376 use crate::zcs::ZCS;
377 use crate::zcs::ZCSStates;
378 use taco_interval_ta::IntervalTAAction;
379 use taco_interval_ta::IntervalTARule;
380 use taco_interval_ta::builder::IntervalTABuilder;
381 use taco_interval_ta::interval::Interval;
382 use taco_interval_ta::interval::IntervalBoundary;
383 use taco_interval_ta::{IntervalActionEffect, IntervalConstraint};
384 use taco_model_checker::ModelCheckerContext;
385 use taco_model_checker::internal_spec::ErrorTarget;
386 use taco_model_checker::internal_spec::upwards_closed_set::UpwardsClosedSet;
387 use taco_smt_encoder::SMTSolverBuilder;
388 use taco_threshold_automaton::ParameterConstraint;
389 use taco_threshold_automaton::expressions::fraction::Fraction;
390 use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::Threshold;
391
392 use crate::ZCSModelCheckerContext;
393 use crate::ZCSModelCheckerHeuristics;
394 use taco_model_checker::SMTBddContext;
395
396 use crate::zcs_error_graph::VariableAssignment;
397 use taco_threshold_automaton::RuleDefinition;
398 use taco_threshold_automaton::expressions::BooleanConnective;
399 use taco_threshold_automaton::expressions::{Location, Variable};
400 use taco_threshold_automaton::lia_threshold_automaton::integer_thresholds::WeightedSum;
401 use taco_threshold_automaton::{
402 BooleanVarConstraint, LocationConstraint,
403 expressions::{ComparisonOp, IntegerExpression, Parameter},
404 general_threshold_automaton::{Action, builder::RuleBuilder},
405 };
406 use zcs::builder::ZCSBuilder;
407
408 fn get_test_ata() -> IntervalThresholdAutomaton {
450 let ta_builder =
451 taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
452 .with_locations(vec![
453 Location::new("l0"),
454 Location::new("l1"),
455 Location::new("l2"),
456 ])
457 .unwrap()
458 .with_variables(vec![Variable::new("x"), Variable::new("y")])
459 .unwrap()
460 .with_parameters(vec![
461 Parameter::new("n"),
462 Parameter::new("t"),
463 Parameter::new("f"),
464 ])
465 .unwrap()
466 .initialize()
467 .with_initial_location_constraints(vec![
468 LocationConstraint::ComparisonExpression(
469 Box::new(IntegerExpression::Atom(Location::new("l0"))),
470 ComparisonOp::Eq,
471 Box::new(
472 IntegerExpression::Param(Parameter::new("n"))
473 - IntegerExpression::Param(Parameter::new("t")),
474 ),
475 ),
476 LocationConstraint::ComparisonExpression(
477 Box::new(IntegerExpression::Atom(Location::new("l1"))),
478 ComparisonOp::Eq,
479 Box::new(IntegerExpression::Const(0)),
480 ),
481 LocationConstraint::ComparisonExpression(
482 Box::new(IntegerExpression::Atom(Location::new("l2"))),
483 ComparisonOp::Eq,
484 Box::new(IntegerExpression::Const(0)),
485 ),
486 ])
487 .unwrap()
488 .with_resilience_conditions([ParameterConstraint::ComparisonExpression(
489 Box::new(IntegerExpression::Atom(Parameter::new("n")) - IntegerExpression::Atom(Parameter::new("t"))),
490 ComparisonOp::Gt,
491 Box::new(IntegerExpression::Const(1)),
492 )])
493 .unwrap()
494 .with_initial_variable_constraints([BooleanVarConstraint::ComparisonExpression(Box::new(IntegerExpression::Atom(Variable::new("x"))), ComparisonOp::Eq, Box::new(IntegerExpression::Const(0))), BooleanVarConstraint::ComparisonExpression(Box::new(IntegerExpression::Atom(Variable::new("y"))), ComparisonOp::Eq, Box::new(IntegerExpression::Const(0)))])
495 .unwrap()
496 .with_rules(vec![
497 RuleBuilder::new(0, Location::new("l0"), Location::new("l1"))
498 .with_action(
499 Action::new(
500 Variable::new("x"),
501 IntegerExpression::Atom(Variable::new("x"))
502 + IntegerExpression::Const(1),
503 )
504 .unwrap(),
505 )
506 .build(),
507 RuleBuilder::new(1, Location::new("l0"), Location::new("l2"))
508 .with_guard(
509 BooleanVarConstraint::BinaryExpression(
510 Box::new(BooleanVarConstraint::ComparisonExpression(
511 Box::new(IntegerExpression::Atom(Variable::new("x"))),
512 ComparisonOp::Geq,
513 Box::new(
514 IntegerExpression::Param(Parameter::new("n"))
515 - IntegerExpression::Param(Parameter::new("t")),
516 ))),
517 BooleanConnective::And,
518 Box::new(BooleanVarConstraint::ComparisonExpression(
519 Box::new(IntegerExpression::Atom(Variable::new("y"))),
520 ComparisonOp::Eq,
521 Box::new(IntegerExpression::Const(0)),
522 )
523 )
524 ))
525 .build(),
526 RuleBuilder::new(2, Location::new("l1"), Location::new("l2"))
527 .with_guard(
528 BooleanVarConstraint::BinaryExpression(
529 Box::new(BooleanVarConstraint::ComparisonExpression(
530 Box::new(IntegerExpression::Atom(Variable::new("x"))),
531 ComparisonOp::Geq,
532 Box::new(
533 IntegerExpression::Param(Parameter::new("n"))
534 - IntegerExpression::Param(Parameter::new("t")),
535 ))),
536 BooleanConnective::And,
537 Box::new(BooleanVarConstraint::ComparisonExpression(
538 Box::new(IntegerExpression::Atom(Variable::new("y"))),
539 ComparisonOp::Eq,
540 Box::new(IntegerExpression::Const(0)),
541 )
542 )
543 ))
544 .build(),
545 ])
546 .unwrap();
547 let ta = ta_builder.build();
548 let lia =
549 taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
550 ta.clone(),
551 )
552 .unwrap();
553
554 let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
555 .build()
556 .unwrap();
557 interval_tas.next().unwrap()
558 }
559
560 fn get_error_states(cs: &ZCS) -> ZCSStates {
561 let l0 = cs.get_sym_state_for_loc(&Location::new("l0"));
562 let l1 = cs.get_sym_state_for_loc(&Location::new("l1"));
563 let l2 = cs.get_sym_state_for_loc(&Location::new("l2"));
564 l0.complement()
565 .intersection(&l1.complement())
566 .intersection(&l2)
567 }
568
569 #[test]
570 fn test_is_empty() {
571 let ata = get_test_ata();
572 let mgr = taco_bdd::BDDManager::default();
573 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
574 let cs = builder.build();
575 let error_states = get_error_states(&cs);
576
577 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
578 let error_graph = error_graph_builder.build();
579
580 assert!(!error_graph.is_empty());
581 }
582
583 #[test]
584 fn test_initial_states() {
585 let ata = get_test_ata();
586 let mgr = taco_bdd::BDDManager::default();
587 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
588 let cs = builder.build();
589 let error_states = get_error_states(&cs);
590
591 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
592 let error_graph = error_graph_builder.build();
593
594 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
595 let l1 = error_graph.cs.get_sym_state_for_loc(&Location::new("l1"));
596 let l2 = error_graph.cs.get_sym_state_for_loc(&Location::new("l2"));
597
598 let interval_0 =
600 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
601
602 let i0 = error_graph
603 .cs
604 .get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0);
605 let i0 = i0.intersection(
606 &error_graph
607 .cs
608 .get_sym_state_for_shared_interval(&Variable::new("y"), &interval_0),
609 );
610 let abstract_rule_0 = IntervalTARule::new(
612 0,
613 Location::new("l0"),
614 Location::new("l1"),
615 IntervalConstraint::True,
616 vec![IntervalTAAction::new(
617 Variable::new("x"),
618 IntervalActionEffect::Inc(1),
619 )],
620 );
621
622 let r0 = error_graph.cs.get_sym_state_for_rule(&abstract_rule_0);
623
624 let initial_states = l0
625 .intersection(&l1.complement())
626 .intersection(&l2.complement())
627 .intersection(&i0)
628 .intersection(&r0);
629
630 assert!(initial_states.equal(&error_graph.initial_states()));
631 }
632
633 #[test]
634 fn test_get_non_error_intersection_empty() {
635 let ata = get_test_ata();
636 let mgr = taco_bdd::BDDManager::default();
637 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
638 let cs = builder.build();
639 let error_states = get_error_states(&cs);
640
641 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
642 let error_graph = error_graph_builder.build();
643
644 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
645 let l1 = error_graph.cs.get_sym_state_for_loc(&Location::new("l1"));
646 let l2 = error_graph.cs.get_sym_state_for_loc(&Location::new("l2"));
647 let error_states = l0
648 .complement()
649 .intersection(&l1.complement())
650 .intersection(&l2);
651
652 let res = error_graph.get_non_error_intersection(error_states);
653
654 assert!(!res.0);
655 assert!(res.1.is_empty());
656 }
657
658 #[test]
659 fn test_get_non_error_intersection() {
660 let ata = get_test_ata();
661 let mgr = taco_bdd::BDDManager::default();
662 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
663 let cs = builder.build();
664 let error_states = get_error_states(&cs);
665
666 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
667 let error_graph = error_graph_builder.build();
668
669 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
670 let l1 = error_graph.cs.get_sym_state_for_loc(&Location::new("l1"));
671 let l2 = error_graph.cs.get_sym_state_for_loc(&Location::new("l2"));
672 let error_states = l0
673 .complement()
674 .intersection(&l1.complement())
675 .intersection(&l2);
676
677 let interval_0 =
679 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
680
681 let i0 = error_graph
682 .cs
683 .get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0);
684
685 let initial_states = l0
686 .intersection(&l1.complement())
687 .intersection(&l2.complement())
688 .intersection(&i0);
689
690 let res = error_graph.get_non_error_intersection(error_states.union(&initial_states));
691
692 assert!(res.0);
693 assert!(res.1.equal(&initial_states));
694 }
695
696 #[test]
697 fn test_get_error_intersection_empty() {
698 let ata = get_test_ata();
699 let mgr = taco_bdd::BDDManager::default();
700 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
701 let cs = builder.build();
702 let error_states = get_error_states(&cs);
703
704 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
705 let error_graph = error_graph_builder.build();
706
707 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
708
709 let res = error_graph.get_error_intersection(l0);
710
711 assert!(!res.0);
712 assert!(res.1.is_empty());
713 }
714
715 #[test]
716 fn test_get_error_intersection() {
717 let ata = get_test_ata();
718 let mgr = taco_bdd::BDDManager::default();
719 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
720 let cs = builder.build();
721 let error_states = get_error_states(&cs);
722
723 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
724 let error_graph = error_graph_builder.build();
725
726 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
727 let l1 = error_graph.cs.get_sym_state_for_loc(&Location::new("l1"));
728 let l2 = error_graph.cs.get_sym_state_for_loc(&Location::new("l2"));
729
730 let interval_2 = Interval::new(
732 IntervalBoundary::Bound(Threshold::new(
733 [
734 (Parameter::new("n"), 1.into()),
735 (Parameter::new("t"), Fraction::new(1, 1, true)),
736 ],
737 0,
738 )),
739 false,
740 IntervalBoundary::new_infty(),
741 true,
742 );
743
744 let i2 = error_graph
745 .cs
746 .get_sym_state_for_shared_interval(&Variable::new("x"), &interval_2);
747
748 let error_states = l0
749 .complement()
750 .intersection(&l1.complement())
751 .intersection(&l2)
752 .intersection(&i2);
753
754 let builder = ZCSErrorGraphBuilder::new(error_graph.cs.clone(), error_states.clone());
755 let error_graph = builder.build();
756
757 let abstract_rule_0 = IntervalTARule::new(
759 0,
760 Location::new("l0"),
761 Location::new("l1"),
762 IntervalConstraint::True,
763 vec![IntervalTAAction::new(
764 Variable::new("x"),
765 IntervalActionEffect::Inc(1),
766 )],
767 );
768
769 let r0 = error_graph.cs.get_sym_state_for_rule(&abstract_rule_0);
770
771 let res = error_graph
772 .get_error_intersection(l0.clone().union(&l2).intersection(&i2).intersection(&r0));
773
774 assert!(res.0);
775 assert!(
776 res.1.equal(
777 &l0.complement()
778 .intersection(&l1.complement())
779 .intersection(&l2)
780 .intersection(&i2)
781 .intersection(&r0)
782 )
783 );
784 }
785
786 #[test]
787 fn test_contains_non_spurious_error_path() {
788 let mgr = taco_bdd::BDDManager::default();
789 let ata = get_test_ata();
790 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
791 let cs = builder.build();
792 let error_states = get_error_states(&cs);
793
794 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
795 let sym_err_graph = error_graph_builder.build();
796
797 let spec = ErrorTarget::Reach(UpwardsClosedSet::new_reach(
798 [(Location::new("l2"), 1)],
799 [Location::new("l0"), Location::new("l1")],
800 ));
801
802 let ctx = SMTBddContext::try_new(None).unwrap();
803
804 let smc_ctx = ZCSModelCheckerContext::new(
805 &ctx,
806 ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics,
807 &ata,
808 &spec,
809 );
810
811 let res = sym_err_graph.contains_non_spurious_error_path(&smc_ctx);
812
813 assert!(res.is_non_spurious());
814 }
815
816 #[test]
817 fn test_get_sym_state_for_loc() {
818 let ata = get_test_ata();
819 let mgr = taco_bdd::BDDManager::default();
820 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
821 let cs = builder.build();
822 let error_states = get_error_states(&cs);
823
824 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
825 let error_graph = error_graph_builder.build();
826
827 let l0 = error_graph.cs.get_sym_state_for_loc(&Location::new("l0"));
828 let l1 = error_graph.cs.get_sym_state_for_loc(&Location::new("l1"));
829 let l2 = error_graph.cs.get_sym_state_for_loc(&Location::new("l2"));
830
831 assert!(
832 error_graph
833 .get_sym_state_for_loc(&Location::new("l0"))
834 .equal(&l0)
835 );
836 assert!(
837 error_graph
838 .get_sym_state_for_loc(&Location::new("l1"))
839 .equal(&l1)
840 );
841 assert!(
842 error_graph
843 .get_sym_state_for_loc(&Location::new("l2"))
844 .equal(&l2)
845 );
846 }
847
848 #[test]
849 fn test_get_sym_state_for_shared_interval() {
850 let ata = get_test_ata();
851 let mgr = taco_bdd::BDDManager::default();
852 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
853 let cs = builder.build();
854 let error_states = get_error_states(&cs);
855
856 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
857 let error_graph = error_graph_builder.build();
858
859 let interval_0 =
861 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
862 let i0 = error_graph
863 .cs
864 .get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0);
865
866 assert!(
867 error_graph
868 .get_sym_state_for_shared_interval(&Variable::new("x"), &interval_0)
869 .equal(&i0)
870 );
871 }
872
873 #[test]
874 fn test_get_sym_state_for_rule() {
875 let ata = get_test_ata();
876 let mgr = taco_bdd::BDDManager::default();
877 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
878 let cs = builder.build();
879 let error_states = get_error_states(&cs);
880
881 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
882 let error_graph = error_graph_builder.build();
883
884 let abstract_rule_0 = IntervalTARule::new(
886 0,
887 Location::new("l0"),
888 Location::new("l1"),
889 IntervalConstraint::True,
890 vec![IntervalTAAction::new(
891 Variable::new("x"),
892 IntervalActionEffect::Inc(1),
893 )],
894 );
895
896 let r0 = error_graph.cs.get_sym_state_for_rule(&abstract_rule_0);
897
898 assert!(
899 error_graph
900 .cs
901 .get_sym_state_for_rule(&abstract_rule_0)
902 .equal(&r0)
903 );
904 }
905
906 #[test]
907 fn test_transitions() {
908 let ata = get_test_ata();
909 let mgr = taco_bdd::BDDManager::default();
910 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
911 let cs = builder.build();
912 let error_states = get_error_states(&cs);
913 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
914 let error_graph = error_graph_builder.build();
915
916 let transitions = error_graph
917 .transitions()
918 .map(|r| r.abstract_rule().id())
919 .collect::<Vec<_>>();
920 assert_eq!(transitions.len(), 3);
921 assert!(transitions.contains(&0));
922 assert!(transitions.contains(&1));
923 assert!(transitions.contains(&2));
924 }
925
926 #[test]
927 fn test_new_empty_sym_state() {
928 let ata = get_test_ata();
929 let mgr = taco_bdd::BDDManager::default();
930 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
931 let cs = builder.build();
932 let error_states = get_error_states(&cs);
933
934 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
935 let error_graph = error_graph_builder.build();
936
937 let empty_state = error_graph.new_empty_sym_state();
938
939 assert!(empty_state.is_empty());
940 }
941
942 #[test]
943 fn test_compute_reachable_variable_assignments() {
944 let ata = get_test_ata();
945 let mgr = taco_bdd::BDDManager::default();
946 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
947 let cs = builder.build();
948 let error_states = get_error_states(&cs);
949
950 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
951 let error_graph = error_graph_builder.build();
952
953 let assignments = error_graph.compute_reachable_variable_assignments();
954
955 assert_eq!(assignments.len(), 3);
960
961 let interval_0 =
963 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
964
965 let interval_2 = Interval::new(
967 IntervalBoundary::Bound(Threshold::new(
968 [
969 (Parameter::new("n"), 1.into()),
970 (Parameter::new("t"), Fraction::new(1, 1, true)),
971 ],
972 0,
973 )),
974 false,
975 IntervalBoundary::new_infty(),
976 true,
977 );
978 let mut assign2 = VariableAssignment::new();
979 assign2
980 .add_shared_var_interval(Variable::new("x"), interval_2.clone())
981 .unwrap();
982
983 let mut reached_i_0 = false;
984 let mut reached_i_1 = false;
985 let mut reached_i_2 = false;
986 for assign in assignments {
987 for (var, interval) in assign.assignment().assignments() {
988 if *var == Variable::new("y") {
989 assert_eq!(interval, &interval_0);
990 }
991 if *var == Variable::new("x") {
992 if interval == &interval_0 {
993 reached_i_0 = true;
994 } else if interval == &interval_2 {
995 reached_i_2 = true;
996 } else {
997 reached_i_1 = true;
998 }
999 }
1000 }
1001 }
1002 assert!(reached_i_0);
1003 assert!(reached_i_1);
1004 assert!(reached_i_2);
1005
1006 let interval_1 = Interval::new(
1008 IntervalBoundary::new_bound(WeightedSum::new_empty(), Fraction::new(1, 1, false)),
1009 false,
1010 IntervalBoundary::new_infty(),
1011 true,
1012 );
1013
1014 let all_possible_assignments = error_graph.get_all_possible_variable_assignments();
1015 assert_eq!(all_possible_assignments.len(), 6);
1016 assert!(all_possible_assignments.iter().any(|assign| {
1017 assign
1018 .assignment()
1019 .assignments()
1020 .any(|(var, interval)| *var == Variable::new("y") && *interval == interval_1)
1021 }));
1022 }
1023
1024 #[test]
1025 fn test_construct_steady_path() {
1026 let ata = get_test_ata();
1027 let mgr = taco_bdd::BDDManager::default();
1028 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
1029 let cs = builder.build();
1030 let error_states = get_error_states(&cs);
1031
1032 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
1033 let error_graph = error_graph_builder.build();
1034
1035 let interval_0 =
1037 Interval::new_constant(Fraction::new(0, 1, false), Fraction::new(1, 1, false));
1038 let mut assignment = VariableAssignment::new();
1039 assignment
1040 .add_shared_var_interval(Variable::new("x"), interval_0.clone())
1041 .unwrap();
1042 assignment
1043 .add_shared_var_interval(Variable::new("y"), interval_0.clone())
1044 .unwrap();
1045 let assignment = error_graph.cs.get_sym_var_assignment(assignment);
1046
1047 let initial_states = error_graph.initial_states();
1048
1049 let steady_path =
1050 error_graph.construct_steady_path(initial_states.clone(), assignment.clone());
1051
1052 println!("{:?}", steady_path._reachable_states());
1053 println!("{:?}", initial_states);
1054
1055 assert!(steady_path._reachable_states().equal(&initial_states));
1056 assert!(
1057 steady_path
1058 ._reachable_states()
1059 .remove_assignment(&assignment)
1060 .is_empty()
1061 );
1062 }
1063
1064 fn get_test_cycle_ata() -> IntervalThresholdAutomaton {
1079 let ta_builder =
1080 taco_threshold_automaton::general_threshold_automaton::builder::GeneralThresholdAutomatonBuilder::new("test")
1081 .with_locations(vec![
1082 Location::new("l0"),
1083 Location::new("l1"),
1084 Location::new("l2"),
1085 Location::new("l3"),
1086 ])
1087 .unwrap()
1088 .with_variables(vec![Variable::new("x"), Variable::new("y")])
1089 .unwrap()
1090 .with_parameters(vec![
1091 Parameter::new("n"),
1092 ])
1093 .unwrap()
1094 .initialize()
1095 .with_initial_location_constraints(vec![
1096 LocationConstraint::ComparisonExpression(
1097 Box::new(IntegerExpression::Atom(Location::new("l0"))),
1098 ComparisonOp::Eq,
1099 Box::new(
1100 IntegerExpression::Param(Parameter::new("n"))
1101 ),
1102 ),
1103 LocationConstraint::ComparisonExpression(
1104 Box::new(IntegerExpression::Atom(Location::new("l1"))),
1105 ComparisonOp::Eq,
1106 Box::new(IntegerExpression::Const(0)),
1107 ),
1108 LocationConstraint::ComparisonExpression(
1109 Box::new(IntegerExpression::Atom(Location::new("l2"))),
1110 ComparisonOp::Eq,
1111 Box::new(IntegerExpression::Const(0)),
1112 ),
1113 LocationConstraint::ComparisonExpression(
1114 Box::new(IntegerExpression::Atom(Location::new("l3"))),
1115 ComparisonOp::Eq,
1116 Box::new(IntegerExpression::Const(0)),
1117 ),
1118 ])
1119 .unwrap()
1120 .with_resilience_conditions([ParameterConstraint::ComparisonExpression(
1121 Box::new(IntegerExpression::Atom(Parameter::new("n"))),
1122 ComparisonOp::Gt,
1123 Box::new(IntegerExpression::Const(1)),
1124 )])
1125 .unwrap()
1126 .with_initial_variable_constraints([BooleanVarConstraint::ComparisonExpression(Box::new(IntegerExpression::Atom(Variable::new("x"))), ComparisonOp::Eq, Box::new(IntegerExpression::Const(0))), BooleanVarConstraint::ComparisonExpression(Box::new(IntegerExpression::Atom(Variable::new("y"))), ComparisonOp::Eq, Box::new(IntegerExpression::Const(0)))])
1127 .unwrap()
1128 .with_rules(vec![
1129 RuleBuilder::new(0, Location::new("l0"), Location::new("l1"))
1130 .with_guard(
1131 BooleanVarConstraint::ComparisonExpression(
1132 Box::new(IntegerExpression::Atom(Variable::new("x"))),
1133 ComparisonOp::Lt,
1134 Box::new(IntegerExpression::Const(1)),
1135 )
1136 )
1137 .with_action(
1138 Action::new(
1139 Variable::new("x"),
1140 IntegerExpression::Atom(Variable::new("x"))
1141 + IntegerExpression::Const(1),
1142 )
1143 .unwrap(),
1144 )
1145 .build(),
1146 RuleBuilder::new(1, Location::new("l1"), Location::new("l2"))
1147 .build(),
1148 RuleBuilder::new(2, Location::new("l2"), Location::new("l1"))
1149 .with_action(
1150 Action::new(
1151 Variable::new("y"),
1152 IntegerExpression::Atom(Variable::new("y"))
1153 + IntegerExpression::Const(1),
1154 )
1155 .unwrap(),
1156 )
1157 .build(),
1158 RuleBuilder::new(3, Location::new("l0"), Location::new("l3"))
1159 .with_guard(
1160 BooleanVarConstraint::ComparisonExpression(
1161 Box::new(IntegerExpression::Atom(Variable::new("y"))),
1162 ComparisonOp::Geq,
1163 Box::new(IntegerExpression::Const(5)),
1164 )
1165 )
1166 .build(),
1167 ])
1168 .unwrap();
1169 let ta = ta_builder.build();
1170 let lia =
1171 taco_threshold_automaton::lia_threshold_automaton::LIAThresholdAutomaton::try_from(
1172 ta.clone(),
1173 )
1174 .unwrap();
1175
1176 let mut interval_tas = IntervalTABuilder::new(lia, SMTSolverBuilder::default(), vec![])
1177 .build()
1178 .unwrap();
1179 interval_tas.next().unwrap()
1180 }
1181
1182 #[test]
1183 fn test_cycle_ta_for_spuriousness() {
1184 let ata = get_test_cycle_ata();
1185 let mgr = taco_bdd::BDDManager::default();
1186 let builder: ZCSBuilder = ZCSBuilder::new(&mgr, &ata);
1187 let cs = builder.build();
1188 let l3 = cs.get_sym_state_for_loc(&Location::new("l3"));
1189 let error_states = l3;
1191
1192 let error_graph_builder = ZCSErrorGraphBuilder::new(cs, error_states);
1193 let sym_err_graph = error_graph_builder.build();
1194
1195 let spec = ErrorTarget::Reach(UpwardsClosedSet::new_cover([Location::new("l3")]));
1196
1197 let ctx = SMTBddContext::try_new(None).unwrap();
1198 let smc_ctx = ZCSModelCheckerContext::new(
1199 &ctx,
1200 ZCSModelCheckerHeuristics::DecrementAndIncrementHeuristics,
1201 &ata,
1202 &spec,
1203 );
1204
1205 let res = sym_err_graph.contains_non_spurious_error_path(&smc_ctx);
1206
1207 assert!(res.is_non_spurious());
1208 }
1209}