taco_acs_model_checker/error_graph/
node.rs

1//! Node type of the error graph
2
3use std::collections::VecDeque;
4
5use log::{debug, info};
6use taco_model_checker::reachability_specification::DisjunctionTargetConfig;
7
8use crate::{
9    acs_threshold_automaton::{
10        ACSThresholdAutomaton,
11        configuration::{ACSTAConfig, partially_ordered_cfg_map::PartiallyOrderedConfigMap},
12    },
13    error_graph::{NodeRef, edge::ErrorGraphEdge},
14};
15
16/// Node in the error graph
17#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct ErrorGraphNode {
19    /// Distance to an error configuration
20    error_level: usize,
21    /// Configuration
22    config: ACSTAConfig,
23    /// Parent nodes
24    /// [`ErrorGraphEdge`]s to nodes with a smaller distance to an error
25    parents: Vec<ErrorGraphEdge>,
26    /// Rules that result in a self-loop
27    self_loops: Vec<u32>,
28    /// [`ErrorGraphEdge`]s to a equal or higher error level node, potentially
29    /// part of a cycle
30    back_edges: Vec<ErrorGraphEdge>,
31}
32
33impl ErrorGraphNode {
34    /// Get the error graph edges leading to the parents of the current node
35    ///
36    /// The edges returned here will have an error level smaller than the current
37    /// node
38    pub fn parents(&self) -> impl Iterator<Item = &ErrorGraphEdge> {
39        self.parents.iter()
40    }
41
42    /// Get all rules that can be self-loops
43    pub fn self_loops(&self) -> impl Iterator<Item = &u32> {
44        self.self_loops.iter()
45    }
46
47    /// Get rules that can lead to higher or equal error level
48    pub fn back_edges(&self) -> impl Iterator<Item = &ErrorGraphEdge> {
49        self.back_edges.iter()
50    }
51
52    /// Get the configuration of the current node
53    pub fn config(&self) -> &ACSTAConfig {
54        &self.config
55    }
56
57    /// Get the error level of the current node
58    pub fn error_level(&self) -> usize {
59        self.error_level
60    }
61
62    /// Create new root error nodes from the specification
63    ///
64    /// Note: This function only returns the root of the error graph, to
65    /// construct the full graph
66    /// [`ErrorGraphNode::compute_predecessors_and_insert_to_graph`]
67    /// for all computed nodes until the `to_explore_queue` is empty
68    pub fn new_roots_from_spec(
69        spec: &DisjunctionTargetConfig,
70        ta: &ACSThresholdAutomaton,
71    ) -> PartiallyOrderedConfigMap<NodeRef> {
72        let error_level = 0;
73
74        let configs = ACSTAConfig::from_disjunct_target(spec, ta);
75
76        let mut res: PartiallyOrderedConfigMap<NodeRef> = PartiallyOrderedConfigMap::new();
77
78        configs
79            .into_iter()
80            .map(|config| {
81                let node: NodeRef = Self {
82                    error_level,
83                    config: config.clone(),
84                    self_loops: Vec::new(),
85                    parents: Vec::new(),
86                    back_edges: Vec::new(),
87                }
88                .into();
89
90                (config, node)
91            })
92            .for_each(|(config, node)| {
93                let (smaller, bigger) = res.get_leq_or_bigger(&config);
94
95                if smaller.is_some() {
96                    return;
97                }
98
99                if let Some(bigger_cfg) = bigger {
100                    let bigger_cfg = bigger_cfg.borrow().config().clone();
101                    res.remove_entries_with_config(&bigger_cfg);
102                }
103
104                res.insert(config, node);
105            });
106
107        debug_assert!(res.len() >= 1);
108        info!("Found {} potentially reachable error configs", res.len());
109
110        res
111    }
112
113    /// Compute the predecessor of `node_to_compute_succs` in `ta`, adding nodes
114    /// that still require exploration to `to_explore_queue` while checking that
115    /// no smaller or equal node exists in `cfg_node_map`
116    ///
117    /// This function corresponds to `ComputePredBasis` of Algorithm 1 in the
118    /// paper
119    ///
120    /// Safety: This function assumes that none of the nodes in the error graph
121    /// still have an existing borrow
122    pub fn compute_predecessors_and_insert_to_graph(
123        cfg_node_map: &mut PartiallyOrderedConfigMap<NodeRef>,
124        to_explore_queue: &mut VecDeque<NodeRef>,
125        node_to_explore: &NodeRef,
126        ta: &ACSThresholdAutomaton,
127    ) {
128        // clone the values and drop the borrow
129        let error_level = node_to_explore.borrow().error_level + 1;
130        let config_node_to_explore = node_to_explore.borrow().config.clone();
131
132        // compute all predecessors from all rules
133        for rule in ta.rules() {
134            let predecessors = config_node_to_explore.compute_potential_predecessors(rule, ta);
135            if predecessors.is_none() {
136                continue;
137            }
138            let predecessors = predecessors.unwrap();
139
140            for cfg in predecessors {
141                if cfg == config_node_to_explore {
142                    // Should be the only existing borrow (for the whole graph)
143                    node_to_explore.borrow_mut().self_loops.push(rule.id());
144                    continue;
145                }
146
147                // New edge to insert
148                let new_edge = ErrorGraphEdge::new(rule, node_to_explore.clone());
149
150                let (smaller_or_eq_node, bigger_node) = cfg_node_map.get_leq_or_bigger(&cfg);
151
152                // If there exists a smaller or equal node in the graph, insert
153                // the new edge accordingly
154                if let Some(small_or_eq_node) = smaller_or_eq_node {
155                    Self::insert_existing_smaller_node(
156                        small_or_eq_node,
157                        node_to_explore,
158                        new_edge.clone(),
159                        rule.id(),
160                    );
161                    continue;
162                }
163
164                // If there exists a bigger node in the graph, remove it but keep edges
165                if let Some(bigger_node) = bigger_node {
166                    let bigger_node = bigger_node.clone();
167                    let bigger_node_cfg = bigger_node.borrow().config().clone();
168
169                    debug_assert!(
170                        bigger_node_cfg != cfg,
171                        "Equal configurations should have been caught beforehand: bigger {}, cfg {}",
172                        bigger_node_cfg.display_compact(ta),
173                        cfg.display_compact(ta)
174                    );
175
176                    Self::insert_existing_bigger_node(&bigger_node, cfg.clone(), new_edge);
177
178                    // remove the index by the bigger node
179                    cfg_node_map.remove_entries_with_config(&bigger_node_cfg);
180
181                    // continue exploring the smaller node
182                    to_explore_queue.push_back(bigger_node.clone());
183                    // reindex node by its new config
184                    cfg_node_map.insert(cfg, bigger_node);
185                    continue;
186                }
187
188                // Node is incomparable or smaller than all existing ones,
189                // we need to insert and explore it
190                let node = Self {
191                    error_level,
192                    config: cfg.clone(),
193                    parents: vec![new_edge],
194                    self_loops: vec![],
195                    back_edges: vec![],
196                };
197
198                debug!("Found new node {}", node.config.display_compact(ta));
199                let node: NodeRef = node.into();
200
201                cfg_node_map.insert(cfg, node.clone());
202                to_explore_queue.push_back(node);
203            }
204        }
205    }
206
207    /// Insert the new configuration and corresponding edge into the error graph,
208    /// in case there exists an already inserted node with a smaller or equal
209    /// configuration
210    ///
211    /// This function corresponds to line 20-21 in Algorithm 1
212    fn insert_existing_smaller_node(
213        smaller_or_eq_node: &NodeRef,
214        node_to_explore: &NodeRef,
215        edge_to_insert: ErrorGraphEdge,
216        rule_id: u32,
217    ) {
218        // Check whether we are in a self-loop, if this is the case
219        // simply insert the rule as such
220        if smaller_or_eq_node == node_to_explore {
221            // Should be the only existing borrow (for the whole graph)
222            smaller_or_eq_node.borrow_mut().self_loops.push(rule_id);
223            return;
224        }
225
226        let error_level = node_to_explore.borrow().error_level();
227
228        // `eq_node` is on the same error level (or a bigger one),
229        // therefore `node_to_explore` must be a parent
230        // to `eq_node`
231        if smaller_or_eq_node.borrow().error_level >= error_level {
232            // This should not happen as long as `to_explore_queue`
233            // is a FIFO queue
234            ErrorGraphEdge::insert_to_edge_collection(
235                &mut smaller_or_eq_node.borrow_mut().parents,
236                edge_to_insert,
237            );
238            return;
239        }
240
241        // `smaller_or_eq_node` is from a smaller error level, therefore
242        // we are on a back edge with the potential for cycles
243        ErrorGraphEdge::insert_to_edge_collection(
244            &mut smaller_or_eq_node.borrow_mut().back_edges,
245            edge_to_insert,
246        );
247    }
248
249    /// Insert the new configuration and corresponding edge into the error graph,
250    /// in case there exists an already inserted node with a bigger
251    /// configuration
252    ///
253    /// This function corresponds to line 24-26 in Algorithm 1
254    fn insert_existing_bigger_node(
255        bigger_node: &NodeRef,
256        cfg_to_insert: ACSTAConfig,
257        edge_to_insert: ErrorGraphEdge,
258    ) {
259        if bigger_node == edge_to_insert.node() {
260            bigger_node
261                .borrow_mut()
262                .self_loops
263                .extend(edge_to_insert.get_ids_of_rules());
264
265            bigger_node.borrow_mut().config = cfg_to_insert;
266            return;
267        }
268
269        let mut bigger_node = bigger_node.borrow_mut();
270        // replace the configuration
271        bigger_node.config = cfg_to_insert;
272
273        ErrorGraphEdge::insert_to_edge_collection(&mut bigger_node.back_edges, edge_to_insert);
274    }
275}
276
277#[cfg(test)]
278mod mock_objects {
279    use crate::{
280        acs_threshold_automaton::configuration::ACSTAConfig,
281        error_graph::{edge::ErrorGraphEdge, node::ErrorGraphNode},
282    };
283
284    impl ErrorGraphNode {
285        /// Create a new mock error graph node
286        pub fn new_mock(
287            error_level: usize,
288            config: ACSTAConfig,
289            parents: Vec<ErrorGraphEdge>,
290            self_loops: Vec<u32>,
291            back_edges: Vec<ErrorGraphEdge>,
292        ) -> Self {
293            Self {
294                error_level,
295                config,
296                parents,
297                self_loops,
298                back_edges,
299            }
300        }
301    }
302}
303
304#[cfg(test)]
305mod tests {
306    use std::{cell::RefCell, collections::VecDeque, rc::Rc};
307
308    use taco_display_utils::join_iterator;
309    use taco_interval_ta::builder::IntervalTABuilder;
310    use taco_model_checker::reachability_specification::{DisjunctionTargetConfig, TargetConfig};
311    use taco_smt_encoder::SMTSolverBuilder;
312    use taco_threshold_automaton::{
313        expressions::{
314            BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
315        },
316        general_threshold_automaton::{
317            Action,
318            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
319        },
320        lia_threshold_automaton::LIAThresholdAutomaton,
321    };
322
323    use crate::{
324        acs_threshold_automaton::{
325            ACSInterval, ACSLocation, ACSThresholdAutomaton, CSIntervalConstraint, CSRule,
326            configuration::{ACSIntervalState, ACSLocState, ACSTAConfig},
327        },
328        error_graph::{NodeRef, edge::ErrorGraphEdge, node::ErrorGraphNode},
329    };
330
331    fn get_test_ta() -> ACSThresholdAutomaton {
332        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
333            .with_variables([Variable::new("x"), Variable::new("y")])
334            .unwrap()
335            .with_locations([
336                Location::new("l1"),
337                Location::new("l2"),
338                Location::new("l3"),
339            ])
340            .unwrap()
341            .with_parameter(Parameter::new("n"))
342            .unwrap()
343            .initialize()
344            .with_resilience_condition(BooleanExpression::ComparisonExpression(
345                Box::new(IntegerExpression::Param(Parameter::new("n"))),
346                ComparisonOp::Gt,
347                Box::new(IntegerExpression::Const(3)),
348            ))
349            .unwrap()
350            .with_initial_location_constraints([
351                BooleanExpression::ComparisonExpression(
352                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
353                    ComparisonOp::Eq,
354                    Box::new(IntegerExpression::Const(0)),
355                ),
356                BooleanExpression::ComparisonExpression(
357                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
358                    ComparisonOp::Eq,
359                    Box::new(IntegerExpression::Const(0)),
360                ),
361            ])
362            .unwrap()
363            .with_initial_variable_constraints([
364                BooleanExpression::ComparisonExpression(
365                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
366                    ComparisonOp::Eq,
367                    Box::new(IntegerExpression::Const(0)),
368                ),
369                BooleanExpression::ComparisonExpression(
370                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
371                    ComparisonOp::Eq,
372                    Box::new(IntegerExpression::Const(0)),
373                ),
374            ])
375            .unwrap()
376            .with_rule(
377                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
378                    .with_action(
379                        Action::new(
380                            Variable::new("x"),
381                            IntegerExpression::Const(1)
382                                + IntegerExpression::Atom(Variable::new("x")),
383                        )
384                        .unwrap(),
385                    )
386                    .build(),
387            )
388            .unwrap()
389            .build();
390        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
391        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
392            .build()
393            .unwrap();
394        let interval_ta = interval_tas.next().unwrap();
395        assert!(interval_tas.next().is_none());
396        ACSThresholdAutomaton::from(interval_ta)
397    }
398
399    #[test]
400    fn test_getters() {
401        let edge1 = ErrorGraphEdge::new(
402            &CSRule::new_mock(
403                0,
404                ACSLocation::new_mock(1),
405                ACSLocation::new_mock(2),
406                CSIntervalConstraint::True,
407                Vec::new(),
408            ),
409            Rc::new(RefCell::new(ErrorGraphNode {
410                error_level: 0,
411                config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
412                parents: Vec::new(),
413                self_loops: Vec::new(),
414                back_edges: Vec::new(),
415            })),
416        );
417
418        let edge2 = ErrorGraphEdge::new(
419            &CSRule::new_mock(
420                3,
421                ACSLocation::new_mock(1),
422                ACSLocation::new_mock(2),
423                CSIntervalConstraint::True,
424                Vec::new(),
425            ),
426            Rc::new(RefCell::new(ErrorGraphNode {
427                error_level: 0,
428                config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
429                parents: Vec::new(),
430                self_loops: Vec::new(),
431                back_edges: Vec::new(),
432            })),
433        );
434
435        let node = ErrorGraphNode {
436            error_level: 42,
437            config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
438            parents: vec![edge1.clone()],
439            self_loops: vec![1, 2, 3],
440            back_edges: vec![edge2.clone()],
441        };
442
443        assert_eq!(node.error_level(), 42);
444        assert_eq!(
445            node.config(),
446            &ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new())
447        );
448        assert_eq!(
449            node.self_loops().cloned().collect::<Vec<_>>(),
450            vec![1, 2, 3]
451        );
452        assert_eq!(node.parents().collect::<Vec<_>>(), vec![&edge1]);
453        assert_eq!(node.back_edges().collect::<Vec<_>>(), vec![&edge2]);
454    }
455
456    #[test]
457    fn test_new_roots_from_spec_single() {
458        let ta = get_test_ta();
459
460        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
461
462        let spec = TargetConfig::new_cover([Location::new("l3")])
463            .unwrap()
464            .into_disjunct_with_name("test");
465
466        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
467        let created_nodes = created_node_map
468            .get_all_values()
469            .map(|n| n.borrow().clone())
470            .collect::<Vec<_>>();
471
472        let mut loc_state = ACSLocState::new_empty(&ta);
473        loc_state[loc_l3] = 1;
474
475        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
476            .into_iter()
477            .map(|is| ErrorGraphNode {
478                error_level: 0,
479                config: ACSTAConfig::new_mock(loc_state.clone(), is),
480                parents: Vec::new(),
481                self_loops: Vec::new(),
482                back_edges: Vec::new(),
483            })
484            .collect::<Vec<_>>();
485
486        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
487        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
488    }
489
490    #[test]
491    fn test_new_roots_from_spec_all_cover() {
492        let ta = get_test_ta();
493
494        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
495        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
496        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
497
498        let spec = TargetConfig::new_cover([
499            Location::new("l3"),
500            Location::new("l2"),
501            Location::new("l1"),
502        ])
503        .unwrap()
504        .into_disjunct_with_name("test");
505
506        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
507        let created_nodes = created_node_map
508            .get_all_values()
509            .map(|n| n.borrow().clone())
510            .collect::<Vec<_>>();
511
512        let mut loc_state = ACSLocState::new_empty(&ta);
513        loc_state[loc_l3] = 1;
514        loc_state[loc_l2] = 1;
515        loc_state[loc_l1] = 1;
516
517        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
518            .into_iter()
519            .map(|is| ErrorGraphNode {
520                error_level: 0,
521                config: ACSTAConfig::new_mock(loc_state.clone(), is),
522                parents: Vec::new(),
523                self_loops: Vec::new(),
524                back_edges: Vec::new(),
525            })
526            .collect::<Vec<_>>();
527
528        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
529        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
530    }
531
532    #[test]
533    fn test_new_roots_from_spec_multi_cover() {
534        let ta = get_test_ta();
535
536        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
537        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
538        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
539
540        let spec = DisjunctionTargetConfig::new_from_targets(
541            "test".into(),
542            [
543                TargetConfig::new_cover([Location::new("l3")]).unwrap(),
544                TargetConfig::new_cover([Location::new("l2")]).unwrap(),
545                TargetConfig::new_cover([Location::new("l1")]).unwrap(),
546            ],
547        );
548
549        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
550        let created_nodes = created_node_map
551            .get_all_values()
552            .map(|n| n.borrow().clone())
553            .collect::<Vec<_>>();
554
555        let mut loc_state_3 = ACSLocState::new_empty(&ta);
556        loc_state_3[loc_l3] = 1;
557
558        let mut loc_state_2 = ACSLocState::new_empty(&ta);
559        loc_state_2[loc_l2] = 1;
560
561        let mut loc_state_1 = ACSLocState::new_empty(&ta);
562        loc_state_1[loc_l1] = 1;
563
564        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
565            .into_iter()
566            .flat_map(|is| {
567                [
568                    ErrorGraphNode {
569                        error_level: 0,
570                        config: ACSTAConfig::new_mock(loc_state_3.clone(), is.clone()),
571                        parents: Vec::new(),
572                        self_loops: Vec::new(),
573                        back_edges: Vec::new(),
574                    },
575                    ErrorGraphNode {
576                        error_level: 0,
577                        config: ACSTAConfig::new_mock(loc_state_2.clone(), is.clone()),
578                        parents: Vec::new(),
579                        self_loops: Vec::new(),
580                        back_edges: Vec::new(),
581                    },
582                    ErrorGraphNode {
583                        error_level: 0,
584                        config: ACSTAConfig::new_mock(loc_state_1.clone(), is),
585                        parents: Vec::new(),
586                        self_loops: Vec::new(),
587                        back_edges: Vec::new(),
588                    },
589                ]
590            })
591            .collect::<Vec<_>>();
592
593        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
594        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
595    }
596
597    #[test]
598    fn test_new_compute_predecessor_ta_rule_not_exec_inc_all_cover() {
599        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
600            .with_variables([Variable::new("x"), Variable::new("y")])
601            .unwrap()
602            .with_locations([
603                Location::new("l1"),
604                Location::new("l2"),
605                Location::new("l3"),
606            ])
607            .unwrap()
608            .with_parameter(Parameter::new("n"))
609            .unwrap()
610            .initialize()
611            .with_resilience_condition(BooleanExpression::ComparisonExpression(
612                Box::new(IntegerExpression::Param(Parameter::new("n"))),
613                ComparisonOp::Gt,
614                Box::new(IntegerExpression::Const(3)),
615            ))
616            .unwrap()
617            .with_initial_location_constraints([
618                BooleanExpression::ComparisonExpression(
619                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
620                    ComparisonOp::Eq,
621                    Box::new(IntegerExpression::Const(0)),
622                ),
623                BooleanExpression::ComparisonExpression(
624                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
625                    ComparisonOp::Eq,
626                    Box::new(IntegerExpression::Const(0)),
627                ),
628            ])
629            .unwrap()
630            .with_initial_variable_constraints([
631                BooleanExpression::ComparisonExpression(
632                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
633                    ComparisonOp::Eq,
634                    Box::new(IntegerExpression::Const(0)),
635                ),
636                BooleanExpression::ComparisonExpression(
637                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
638                    ComparisonOp::Eq,
639                    Box::new(IntegerExpression::Const(0)),
640                ),
641            ])
642            .unwrap()
643            .with_rule(
644                RuleBuilder::new(0, Location::new("l1"), Location::new("l1"))
645                    .with_guard(BooleanExpression::False) // disabled rule
646                    .with_action(
647                        Action::new(
648                            Variable::new("x"),
649                            IntegerExpression::Const(1)
650                                + IntegerExpression::Atom(Variable::new("x")),
651                        )
652                        .unwrap(),
653                    )
654                    .build(),
655            )
656            .unwrap()
657            .build();
658        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
659        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
660            .build()
661            .unwrap();
662        let interval_ta = interval_tas.next().unwrap();
663        assert!(interval_tas.next().is_none());
664        let ta = ACSThresholdAutomaton::from(interval_ta);
665
666        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
667        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
668        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
669
670        let var_x = ta.to_cs_var(&Variable::new("x"));
671
672        let spec = TargetConfig::new_cover([
673            Location::new("l3"),
674            Location::new("l2"),
675            Location::new("l1"),
676        ])
677        .unwrap()
678        .into_disjunct_with_name("test");
679
680        // The target config will be (1, 1, 1) and should already be a
681        // fix point
682        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
683        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
684
685        let mut loc_state = ACSLocState::new_empty(&ta);
686        loc_state[loc_l3] = 1;
687        loc_state[loc_l2] = 1;
688        loc_state[loc_l1] = 1;
689
690        let mut explored_is = ACSIntervalState::new_mock_zero(&ta);
691        explored_is[var_x] = ACSInterval::new_mock(1);
692
693        // Get the node out of the map or it won't be updated
694        let node_to_explore = cfg_node_map
695            .values()
696            .find(|n| *n.borrow().config.interval_state() == explored_is)
697            .unwrap()
698            .clone();
699
700        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
701            &mut cfg_node_map,
702            &mut to_explore_queue,
703            &node_to_explore,
704            &ta,
705        );
706
707        assert_eq!(
708            to_explore_queue.len(),
709            0,
710            "Got configs {:?}",
711            join_iterator(
712                to_explore_queue
713                    .iter()
714                    .map(|n| n.borrow().config.display(&ta)),
715                ", "
716            )
717        );
718
719        let created_nodes = cfg_node_map
720            .get_all_values()
721            .map(|n| n.borrow().clone())
722            .collect::<Vec<_>>();
723
724        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
725            .into_iter()
726            .map(|is| ErrorGraphNode {
727                error_level: 0,
728                config: ACSTAConfig::new_mock(loc_state.clone(), is),
729                parents: Vec::new(),
730                self_loops: vec![],
731                back_edges: Vec::new(),
732            })
733            .collect::<Vec<_>>();
734
735        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
736        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
737    }
738
739    #[test]
740    fn test_new_compute_predecessor_ta_only_loc_update_all_cover() {
741        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
742            .with_variables([Variable::new("x"), Variable::new("y")])
743            .unwrap()
744            .with_locations([
745                Location::new("l1"),
746                Location::new("l2"),
747                Location::new("l3"),
748            ])
749            .unwrap()
750            .with_parameter(Parameter::new("n"))
751            .unwrap()
752            .initialize()
753            .with_resilience_condition(BooleanExpression::ComparisonExpression(
754                Box::new(IntegerExpression::Param(Parameter::new("n"))),
755                ComparisonOp::Gt,
756                Box::new(IntegerExpression::Const(3)),
757            ))
758            .unwrap()
759            .with_initial_location_constraints([
760                BooleanExpression::ComparisonExpression(
761                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
762                    ComparisonOp::Eq,
763                    Box::new(IntegerExpression::Const(0)),
764                ),
765                BooleanExpression::ComparisonExpression(
766                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
767                    ComparisonOp::Eq,
768                    Box::new(IntegerExpression::Const(0)),
769                ),
770            ])
771            .unwrap()
772            .with_initial_variable_constraints([
773                BooleanExpression::ComparisonExpression(
774                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
775                    ComparisonOp::Eq,
776                    Box::new(IntegerExpression::Const(0)),
777                ),
778                BooleanExpression::ComparisonExpression(
779                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
780                    ComparisonOp::Eq,
781                    Box::new(IntegerExpression::Const(0)),
782                ),
783            ])
784            .unwrap()
785            .with_rule(RuleBuilder::new(0, Location::new("l1"), Location::new("l2")).build())
786            .unwrap()
787            .build();
788        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
789        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
790            .build()
791            .unwrap();
792        let interval_ta = interval_tas.next().unwrap();
793        assert!(interval_tas.next().is_none());
794        let ta = ACSThresholdAutomaton::from(interval_ta);
795
796        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
797        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
798        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
799
800        let var_x = ta.to_cs_var(&Variable::new("x"));
801
802        let spec = TargetConfig::new_cover([
803            Location::new("l3"),
804            Location::new("l2"),
805            Location::new("l1"),
806        ])
807        .unwrap()
808        .into_disjunct_with_name("test");
809
810        // The target config will be (1, 1, 1) and should already be a
811        // fix point
812        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
813        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
814
815        let mut loc_state = ACSLocState::new_empty(&ta);
816        loc_state[loc_l3] = 1;
817        loc_state[loc_l2] = 1;
818        loc_state[loc_l1] = 1;
819
820        let mut is_to_explore = ACSIntervalState::new_mock_zero(&ta);
821        is_to_explore[var_x] = ACSInterval::new_mock(1);
822
823        // Get the node out of the map or it won't be updated
824        let node_to_explore = cfg_node_map
825            .values()
826            .find(|n| *n.borrow().config.interval_state() == is_to_explore.clone())
827            .unwrap()
828            .clone();
829
830        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
831            &mut cfg_node_map,
832            &mut to_explore_queue,
833            &node_to_explore,
834            &ta,
835        );
836
837        assert_eq!(
838            to_explore_queue.len(),
839            1,
840            "Got configs {}",
841            join_iterator(
842                to_explore_queue
843                    .iter()
844                    .map(|n| n.borrow().config.display(&ta)),
845                ", "
846            )
847        );
848
849        let mut new_loc_state = ACSLocState::new_empty(&ta);
850        new_loc_state[loc_l3] = 1;
851        new_loc_state[loc_l2] = 0;
852        new_loc_state[loc_l1] = 2;
853
854        let expected_node_to_explore = ErrorGraphNode {
855            error_level: 1,
856            config: ACSTAConfig::new_mock(new_loc_state, is_to_explore.clone()),
857            parents: vec![ErrorGraphEdge::new_mock_with_id(0, node_to_explore.clone())],
858            self_loops: Vec::new(),
859            back_edges: Vec::new(),
860        };
861
862        assert_eq!(
863            to_explore_queue,
864            VecDeque::from([expected_node_to_explore.clone().into()])
865        );
866
867        let created_nodes = cfg_node_map
868            .get_all_values()
869            .map(|n| n.borrow().clone())
870            .collect::<Vec<_>>();
871
872        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
873            .into_iter()
874            .map(|is| ErrorGraphNode {
875                error_level: 0,
876                config: ACSTAConfig::new_mock(loc_state.clone(), is),
877                parents: Vec::new(),
878                self_loops: vec![],
879                back_edges: Vec::new(),
880            })
881            .chain([expected_node_to_explore])
882            .collect::<Vec<_>>();
883
884        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
885        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
886    }
887
888    #[test]
889    fn test_new_compute_predecessor_ta_only_self_loop_with_inc_all_cover() {
890        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
891            .with_variables([Variable::new("x"), Variable::new("y")])
892            .unwrap()
893            .with_locations([
894                Location::new("l1"),
895                Location::new("l2"),
896                Location::new("l3"),
897            ])
898            .unwrap()
899            .with_parameter(Parameter::new("n"))
900            .unwrap()
901            .initialize()
902            .with_resilience_condition(BooleanExpression::ComparisonExpression(
903                Box::new(IntegerExpression::Param(Parameter::new("n"))),
904                ComparisonOp::Gt,
905                Box::new(IntegerExpression::Const(3)),
906            ))
907            .unwrap()
908            .with_initial_location_constraints([
909                BooleanExpression::ComparisonExpression(
910                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
911                    ComparisonOp::Eq,
912                    Box::new(IntegerExpression::Const(0)),
913                ),
914                BooleanExpression::ComparisonExpression(
915                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
916                    ComparisonOp::Eq,
917                    Box::new(IntegerExpression::Const(0)),
918                ),
919            ])
920            .unwrap()
921            .with_initial_variable_constraints([
922                BooleanExpression::ComparisonExpression(
923                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
924                    ComparisonOp::Eq,
925                    Box::new(IntegerExpression::Const(0)),
926                ),
927                BooleanExpression::ComparisonExpression(
928                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
929                    ComparisonOp::Eq,
930                    Box::new(IntegerExpression::Const(0)),
931                ),
932            ])
933            .unwrap()
934            .with_rule(
935                RuleBuilder::new(0, Location::new("l1"), Location::new("l1"))
936                    .with_action(
937                        Action::new(
938                            Variable::new("x"),
939                            IntegerExpression::Const(1)
940                                + IntegerExpression::Atom(Variable::new("x")),
941                        )
942                        .unwrap(),
943                    )
944                    .build(),
945            )
946            .unwrap()
947            .build();
948        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
949        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
950            .build()
951            .unwrap();
952        let interval_ta = interval_tas.next().unwrap();
953        assert!(interval_tas.next().is_none());
954        let ta = ACSThresholdAutomaton::from(interval_ta);
955
956        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
957        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
958        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
959
960        let var_x = ta.to_cs_var(&Variable::new("x"));
961
962        let spec = TargetConfig::new_cover([
963            Location::new("l3"),
964            Location::new("l2"),
965            Location::new("l1"),
966        ])
967        .unwrap()
968        .into_disjunct_with_name("test");
969
970        // The target config will be (1, 1, 1) and should already be a
971        // fix point
972        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
973        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
974
975        let mut loc_state = ACSLocState::new_empty(&ta);
976        loc_state[loc_l3] = 1;
977        loc_state[loc_l2] = 1;
978        loc_state[loc_l1] = 1;
979
980        let mut explored_is = ACSIntervalState::new_mock_zero(&ta);
981        explored_is[var_x] = ACSInterval::new_mock(1);
982
983        // Get the node out of the map or it won't be updated
984        let node_to_explore = cfg_node_map
985            .values()
986            .find(|n| *n.borrow().config.interval_state() == explored_is)
987            .unwrap()
988            .clone();
989
990        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
991            &mut cfg_node_map,
992            &mut to_explore_queue,
993            &node_to_explore,
994            &ta,
995        );
996
997        assert_eq!(
998            to_explore_queue.len(),
999            0,
1000            "Got configs {:?}",
1001            join_iterator(
1002                to_explore_queue
1003                    .iter()
1004                    .map(|n| n.borrow().config.display(&ta)),
1005                ", "
1006            )
1007        );
1008
1009        let created_nodes = cfg_node_map
1010            .get_all_values()
1011            .map(|n| n.borrow().clone())
1012            .collect::<Vec<_>>();
1013
1014        // all nodes are already error states, so we expect them to stay
1015        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
1016            .into_iter()
1017            .map(|is| {
1018                // in the abstraction using rule x = [1,\infty[, y = [0,1[ we could
1019                // have stayed in the same interval
1020                // -> insert a self-loop
1021                if is == explored_is {
1022                    return ErrorGraphNode {
1023                        error_level: 0,
1024                        config: ACSTAConfig::new_mock(loc_state.clone(), is),
1025                        parents: Vec::new(),
1026                        self_loops: vec![0],
1027                        back_edges: Vec::new(),
1028                    };
1029                }
1030
1031                if is == ACSIntervalState::new_mock_zero(&ta) {
1032                    return ErrorGraphNode {
1033                        error_level: 0,
1034                        config: ACSTAConfig::new_mock(
1035                            loc_state.clone(),
1036                            ACSIntervalState::new_mock_zero(&ta),
1037                        ),
1038                        parents: vec![ErrorGraphEdge::new_mock_with_id(
1039                            0,
1040                            ErrorGraphNode {
1041                                error_level: 0,
1042                                config: ACSTAConfig::new_mock(
1043                                    loc_state.clone(),
1044                                    explored_is.clone(),
1045                                ),
1046                                parents: Vec::new(),
1047                                self_loops: vec![0],
1048                                back_edges: Vec::new(),
1049                            }
1050                            .into(),
1051                        )],
1052                        self_loops: vec![],
1053                        back_edges: Vec::new(),
1054                    };
1055                }
1056
1057                // Don't update
1058
1059                ErrorGraphNode {
1060                    error_level: 0,
1061                    config: ACSTAConfig::new_mock(loc_state.clone(), is),
1062                    parents: Vec::new(),
1063                    self_loops: vec![],
1064                    back_edges: Vec::new(),
1065                }
1066            })
1067            .collect::<Vec<_>>();
1068
1069        assert!(
1070            created_nodes.iter().all(|n| expected_nodes.contains(n)),
1071            "got: {}\n expected: {}\n missing: {:?}",
1072            join_iterator(
1073                created_nodes
1074                    .iter()
1075                    .map(|n| { n.config.display_compact(&ta) }),
1076                ","
1077            ),
1078            join_iterator(
1079                expected_nodes
1080                    .iter()
1081                    .map(|n| { n.config.display_compact(&ta) }),
1082                ","
1083            ),
1084            created_nodes
1085                .iter()
1086                .find(|n| !expected_nodes.contains(n))
1087                .unwrap()
1088        );
1089        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
1090    }
1091}