Skip to main content

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::internal_spec::upwards_closed_set::UpwardsClosedSet;
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: &UpwardsClosedSet,
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
311    use taco_model_checker::internal_spec::upwards_closed_set::UpwardsClosedSet;
312    use taco_smt_encoder::SMTSolverBuilder;
313    use taco_threshold_automaton::{
314        expressions::{
315            BooleanExpression, ComparisonOp, IntegerExpression, Location, Parameter, Variable,
316        },
317        general_threshold_automaton::{
318            Action,
319            builder::{GeneralThresholdAutomatonBuilder, RuleBuilder},
320        },
321        lia_threshold_automaton::LIAThresholdAutomaton,
322    };
323
324    use crate::{
325        acs_threshold_automaton::{
326            ACSInterval, ACSLocation, ACSThresholdAutomaton, CSIntervalConstraint, CSRule,
327            configuration::{ACSIntervalState, ACSLocState, ACSTAConfig},
328        },
329        error_graph::{NodeRef, edge::ErrorGraphEdge, node::ErrorGraphNode},
330    };
331
332    fn get_test_ta() -> ACSThresholdAutomaton {
333        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
334            .with_variables([Variable::new("x"), Variable::new("y")])
335            .unwrap()
336            .with_locations([
337                Location::new("l1"),
338                Location::new("l2"),
339                Location::new("l3"),
340            ])
341            .unwrap()
342            .with_parameter(Parameter::new("n"))
343            .unwrap()
344            .initialize()
345            .with_resilience_condition(BooleanExpression::ComparisonExpression(
346                Box::new(IntegerExpression::Param(Parameter::new("n"))),
347                ComparisonOp::Gt,
348                Box::new(IntegerExpression::Const(3)),
349            ))
350            .unwrap()
351            .with_initial_location_constraints([
352                BooleanExpression::ComparisonExpression(
353                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
354                    ComparisonOp::Eq,
355                    Box::new(IntegerExpression::Const(0)),
356                ),
357                BooleanExpression::ComparisonExpression(
358                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
359                    ComparisonOp::Eq,
360                    Box::new(IntegerExpression::Const(0)),
361                ),
362            ])
363            .unwrap()
364            .with_initial_variable_constraints([
365                BooleanExpression::ComparisonExpression(
366                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
367                    ComparisonOp::Eq,
368                    Box::new(IntegerExpression::Const(0)),
369                ),
370                BooleanExpression::ComparisonExpression(
371                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
372                    ComparisonOp::Eq,
373                    Box::new(IntegerExpression::Const(0)),
374                ),
375            ])
376            .unwrap()
377            .with_rule(
378                RuleBuilder::new(0, Location::new("l1"), Location::new("l2"))
379                    .with_action(
380                        Action::new(
381                            Variable::new("x"),
382                            IntegerExpression::Const(1)
383                                + IntegerExpression::Atom(Variable::new("x")),
384                        )
385                        .unwrap(),
386                    )
387                    .build(),
388            )
389            .unwrap()
390            .build();
391        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
392        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
393            .build()
394            .unwrap();
395        let interval_ta = interval_tas.next().unwrap();
396        assert!(interval_tas.next().is_none());
397        ACSThresholdAutomaton::from(interval_ta)
398    }
399
400    #[test]
401    fn test_getters() {
402        let edge1 = ErrorGraphEdge::new(
403            &CSRule::new_mock(
404                0,
405                ACSLocation::new_mock(1),
406                ACSLocation::new_mock(2),
407                CSIntervalConstraint::True,
408                Vec::new(),
409            ),
410            Rc::new(RefCell::new(ErrorGraphNode {
411                error_level: 0,
412                config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
413                parents: Vec::new(),
414                self_loops: Vec::new(),
415                back_edges: Vec::new(),
416            })),
417        );
418
419        let edge2 = ErrorGraphEdge::new(
420            &CSRule::new_mock(
421                3,
422                ACSLocation::new_mock(1),
423                ACSLocation::new_mock(2),
424                CSIntervalConstraint::True,
425                Vec::new(),
426            ),
427            Rc::new(RefCell::new(ErrorGraphNode {
428                error_level: 0,
429                config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
430                parents: Vec::new(),
431                self_loops: Vec::new(),
432                back_edges: Vec::new(),
433            })),
434        );
435
436        let node = ErrorGraphNode {
437            error_level: 42,
438            config: ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new()),
439            parents: vec![edge1.clone()],
440            self_loops: vec![1, 2, 3],
441            back_edges: vec![edge2.clone()],
442        };
443
444        assert_eq!(node.error_level(), 42);
445        assert_eq!(
446            node.config(),
447            &ACSTAConfig::new_mock_from_vecs(Vec::new(), Vec::new())
448        );
449        assert_eq!(
450            node.self_loops().cloned().collect::<Vec<_>>(),
451            vec![1, 2, 3]
452        );
453        assert_eq!(node.parents().collect::<Vec<_>>(), vec![&edge1]);
454        assert_eq!(node.back_edges().collect::<Vec<_>>(), vec![&edge2]);
455    }
456
457    #[test]
458    fn test_new_roots_from_spec_single() {
459        let ta = get_test_ta();
460
461        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
462
463        let spec = UpwardsClosedSet::new_cover([Location::new("l3")]);
464
465        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
466        let created_nodes = created_node_map
467            .get_all_values()
468            .map(|n| n.borrow().clone())
469            .collect::<Vec<_>>();
470
471        let mut loc_state = ACSLocState::new_empty(&ta);
472        loc_state[loc_l3] = 1;
473
474        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
475            .into_iter()
476            .map(|is| ErrorGraphNode {
477                error_level: 0,
478                config: ACSTAConfig::new_mock(loc_state.clone(), is),
479                parents: Vec::new(),
480                self_loops: Vec::new(),
481                back_edges: Vec::new(),
482            })
483            .collect::<Vec<_>>();
484
485        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
486        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
487    }
488
489    #[test]
490    fn test_new_roots_from_spec_all_cover() {
491        let ta = get_test_ta();
492
493        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
494        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
495        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
496
497        let spec = UpwardsClosedSet::new_cover([
498            Location::new("l3"),
499            Location::new("l2"),
500            Location::new("l1"),
501        ]);
502
503        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
504        let created_nodes = created_node_map
505            .get_all_values()
506            .map(|n| n.borrow().clone())
507            .collect::<Vec<_>>();
508
509        let mut loc_state = ACSLocState::new_empty(&ta);
510        loc_state[loc_l3] = 1;
511        loc_state[loc_l2] = 1;
512        loc_state[loc_l1] = 1;
513
514        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
515            .into_iter()
516            .map(|is| ErrorGraphNode {
517                error_level: 0,
518                config: ACSTAConfig::new_mock(loc_state.clone(), is),
519                parents: Vec::new(),
520                self_loops: Vec::new(),
521                back_edges: Vec::new(),
522            })
523            .collect::<Vec<_>>();
524
525        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
526        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
527    }
528
529    #[test]
530    fn test_new_roots_from_spec_multi_cover() {
531        let ta = get_test_ta();
532
533        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
534        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
535        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
536
537        let spec = UpwardsClosedSet::new_cover([Location::new("l3")])
538            | UpwardsClosedSet::new_cover([Location::new("l2")])
539            | UpwardsClosedSet::new_cover([Location::new("l1")]);
540
541        let created_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
542        let created_nodes = created_node_map
543            .get_all_values()
544            .map(|n| n.borrow().clone())
545            .collect::<Vec<_>>();
546
547        let mut loc_state_3 = ACSLocState::new_empty(&ta);
548        loc_state_3[loc_l3] = 1;
549
550        let mut loc_state_2 = ACSLocState::new_empty(&ta);
551        loc_state_2[loc_l2] = 1;
552
553        let mut loc_state_1 = ACSLocState::new_empty(&ta);
554        loc_state_1[loc_l1] = 1;
555
556        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
557            .into_iter()
558            .flat_map(|is| {
559                [
560                    ErrorGraphNode {
561                        error_level: 0,
562                        config: ACSTAConfig::new_mock(loc_state_3.clone(), is.clone()),
563                        parents: Vec::new(),
564                        self_loops: Vec::new(),
565                        back_edges: Vec::new(),
566                    },
567                    ErrorGraphNode {
568                        error_level: 0,
569                        config: ACSTAConfig::new_mock(loc_state_2.clone(), is.clone()),
570                        parents: Vec::new(),
571                        self_loops: Vec::new(),
572                        back_edges: Vec::new(),
573                    },
574                    ErrorGraphNode {
575                        error_level: 0,
576                        config: ACSTAConfig::new_mock(loc_state_1.clone(), is),
577                        parents: Vec::new(),
578                        self_loops: Vec::new(),
579                        back_edges: Vec::new(),
580                    },
581                ]
582            })
583            .collect::<Vec<_>>();
584
585        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
586        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
587    }
588
589    #[test]
590    fn test_new_compute_predecessor_ta_rule_not_exec_inc_all_cover() {
591        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
592            .with_variables([Variable::new("x"), Variable::new("y")])
593            .unwrap()
594            .with_locations([
595                Location::new("l1"),
596                Location::new("l2"),
597                Location::new("l3"),
598            ])
599            .unwrap()
600            .with_parameter(Parameter::new("n"))
601            .unwrap()
602            .initialize()
603            .with_resilience_condition(BooleanExpression::ComparisonExpression(
604                Box::new(IntegerExpression::Param(Parameter::new("n"))),
605                ComparisonOp::Gt,
606                Box::new(IntegerExpression::Const(3)),
607            ))
608            .unwrap()
609            .with_initial_location_constraints([
610                BooleanExpression::ComparisonExpression(
611                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
612                    ComparisonOp::Eq,
613                    Box::new(IntegerExpression::Const(0)),
614                ),
615                BooleanExpression::ComparisonExpression(
616                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
617                    ComparisonOp::Eq,
618                    Box::new(IntegerExpression::Const(0)),
619                ),
620            ])
621            .unwrap()
622            .with_initial_variable_constraints([
623                BooleanExpression::ComparisonExpression(
624                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
625                    ComparisonOp::Eq,
626                    Box::new(IntegerExpression::Const(0)),
627                ),
628                BooleanExpression::ComparisonExpression(
629                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
630                    ComparisonOp::Eq,
631                    Box::new(IntegerExpression::Const(0)),
632                ),
633            ])
634            .unwrap()
635            .with_rule(
636                RuleBuilder::new(0, Location::new("l1"), Location::new("l1"))
637                    .with_guard(BooleanExpression::False) // disabled rule
638                    .with_action(
639                        Action::new(
640                            Variable::new("x"),
641                            IntegerExpression::Const(1)
642                                + IntegerExpression::Atom(Variable::new("x")),
643                        )
644                        .unwrap(),
645                    )
646                    .build(),
647            )
648            .unwrap()
649            .build();
650        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
651        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
652            .build()
653            .unwrap();
654        let interval_ta = interval_tas.next().unwrap();
655        assert!(interval_tas.next().is_none());
656        let ta = ACSThresholdAutomaton::from(interval_ta);
657
658        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
659        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
660        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
661
662        let var_x = ta.to_cs_var(&Variable::new("x"));
663
664        let spec = UpwardsClosedSet::new_cover([
665            Location::new("l3"),
666            Location::new("l2"),
667            Location::new("l1"),
668        ]);
669
670        // The target config will be (1, 1, 1) and should already be a
671        // fix point
672        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
673        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
674
675        let mut loc_state = ACSLocState::new_empty(&ta);
676        loc_state[loc_l3] = 1;
677        loc_state[loc_l2] = 1;
678        loc_state[loc_l1] = 1;
679
680        let mut explored_is = ACSIntervalState::new_mock_zero(&ta);
681        explored_is[var_x] = ACSInterval::new_mock(1);
682
683        // Get the node out of the map or it won't be updated
684        let node_to_explore = cfg_node_map
685            .values()
686            .find(|n| *n.borrow().config.interval_state() == explored_is)
687            .unwrap()
688            .clone();
689
690        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
691            &mut cfg_node_map,
692            &mut to_explore_queue,
693            &node_to_explore,
694            &ta,
695        );
696
697        assert_eq!(
698            to_explore_queue.len(),
699            0,
700            "Got configs {:?}",
701            join_iterator(
702                to_explore_queue
703                    .iter()
704                    .map(|n| n.borrow().config.display(&ta)),
705                ", "
706            )
707        );
708
709        let created_nodes = cfg_node_map
710            .get_all_values()
711            .map(|n| n.borrow().clone())
712            .collect::<Vec<_>>();
713
714        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
715            .into_iter()
716            .map(|is| ErrorGraphNode {
717                error_level: 0,
718                config: ACSTAConfig::new_mock(loc_state.clone(), is),
719                parents: Vec::new(),
720                self_loops: vec![],
721                back_edges: Vec::new(),
722            })
723            .collect::<Vec<_>>();
724
725        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
726        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
727    }
728
729    #[test]
730    fn test_new_compute_predecessor_ta_only_loc_update_all_cover() {
731        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
732            .with_variables([Variable::new("x"), Variable::new("y")])
733            .unwrap()
734            .with_locations([
735                Location::new("l1"),
736                Location::new("l2"),
737                Location::new("l3"),
738            ])
739            .unwrap()
740            .with_parameter(Parameter::new("n"))
741            .unwrap()
742            .initialize()
743            .with_resilience_condition(BooleanExpression::ComparisonExpression(
744                Box::new(IntegerExpression::Param(Parameter::new("n"))),
745                ComparisonOp::Gt,
746                Box::new(IntegerExpression::Const(3)),
747            ))
748            .unwrap()
749            .with_initial_location_constraints([
750                BooleanExpression::ComparisonExpression(
751                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
752                    ComparisonOp::Eq,
753                    Box::new(IntegerExpression::Const(0)),
754                ),
755                BooleanExpression::ComparisonExpression(
756                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
757                    ComparisonOp::Eq,
758                    Box::new(IntegerExpression::Const(0)),
759                ),
760            ])
761            .unwrap()
762            .with_initial_variable_constraints([
763                BooleanExpression::ComparisonExpression(
764                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
765                    ComparisonOp::Eq,
766                    Box::new(IntegerExpression::Const(0)),
767                ),
768                BooleanExpression::ComparisonExpression(
769                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
770                    ComparisonOp::Eq,
771                    Box::new(IntegerExpression::Const(0)),
772                ),
773            ])
774            .unwrap()
775            .with_rule(RuleBuilder::new(0, Location::new("l1"), Location::new("l2")).build())
776            .unwrap()
777            .build();
778        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
779        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
780            .build()
781            .unwrap();
782        let interval_ta = interval_tas.next().unwrap();
783        assert!(interval_tas.next().is_none());
784        let ta = ACSThresholdAutomaton::from(interval_ta);
785
786        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
787        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
788        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
789
790        let var_x = ta.to_cs_var(&Variable::new("x"));
791
792        let spec = UpwardsClosedSet::new_cover([
793            Location::new("l3"),
794            Location::new("l2"),
795            Location::new("l1"),
796        ]);
797
798        // The target config will be (1, 1, 1) and should already be a
799        // fix point
800        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
801        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
802
803        let mut loc_state = ACSLocState::new_empty(&ta);
804        loc_state[loc_l3] = 1;
805        loc_state[loc_l2] = 1;
806        loc_state[loc_l1] = 1;
807
808        let mut is_to_explore = ACSIntervalState::new_mock_zero(&ta);
809        is_to_explore[var_x] = ACSInterval::new_mock(1);
810
811        // Get the node out of the map or it won't be updated
812        let node_to_explore = cfg_node_map
813            .values()
814            .find(|n| *n.borrow().config.interval_state() == is_to_explore.clone())
815            .unwrap()
816            .clone();
817
818        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
819            &mut cfg_node_map,
820            &mut to_explore_queue,
821            &node_to_explore,
822            &ta,
823        );
824
825        assert_eq!(
826            to_explore_queue.len(),
827            1,
828            "Got configs {}",
829            join_iterator(
830                to_explore_queue
831                    .iter()
832                    .map(|n| n.borrow().config.display(&ta)),
833                ", "
834            )
835        );
836
837        let mut new_loc_state = ACSLocState::new_empty(&ta);
838        new_loc_state[loc_l3] = 1;
839        new_loc_state[loc_l2] = 0;
840        new_loc_state[loc_l1] = 2;
841
842        let expected_node_to_explore = ErrorGraphNode {
843            error_level: 1,
844            config: ACSTAConfig::new_mock(new_loc_state, is_to_explore.clone()),
845            parents: vec![ErrorGraphEdge::new_mock_with_id(0, node_to_explore.clone())],
846            self_loops: Vec::new(),
847            back_edges: Vec::new(),
848        };
849
850        assert_eq!(
851            to_explore_queue,
852            VecDeque::from([expected_node_to_explore.clone().into()])
853        );
854
855        let created_nodes = cfg_node_map
856            .get_all_values()
857            .map(|n| n.borrow().clone())
858            .collect::<Vec<_>>();
859
860        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
861            .into_iter()
862            .map(|is| ErrorGraphNode {
863                error_level: 0,
864                config: ACSTAConfig::new_mock(loc_state.clone(), is),
865                parents: Vec::new(),
866                self_loops: vec![],
867                back_edges: Vec::new(),
868            })
869            .chain([expected_node_to_explore])
870            .collect::<Vec<_>>();
871
872        assert!(created_nodes.iter().all(|n| expected_nodes.contains(n)));
873        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
874    }
875
876    #[test]
877    fn test_new_compute_predecessor_ta_only_self_loop_with_inc_all_cover() {
878        let ta = GeneralThresholdAutomatonBuilder::new("test_ta")
879            .with_variables([Variable::new("x"), Variable::new("y")])
880            .unwrap()
881            .with_locations([
882                Location::new("l1"),
883                Location::new("l2"),
884                Location::new("l3"),
885            ])
886            .unwrap()
887            .with_parameter(Parameter::new("n"))
888            .unwrap()
889            .initialize()
890            .with_resilience_condition(BooleanExpression::ComparisonExpression(
891                Box::new(IntegerExpression::Param(Parameter::new("n"))),
892                ComparisonOp::Gt,
893                Box::new(IntegerExpression::Const(3)),
894            ))
895            .unwrap()
896            .with_initial_location_constraints([
897                BooleanExpression::ComparisonExpression(
898                    Box::new(IntegerExpression::Atom(Location::new("l2"))),
899                    ComparisonOp::Eq,
900                    Box::new(IntegerExpression::Const(0)),
901                ),
902                BooleanExpression::ComparisonExpression(
903                    Box::new(IntegerExpression::Atom(Location::new("l3"))),
904                    ComparisonOp::Eq,
905                    Box::new(IntegerExpression::Const(0)),
906                ),
907            ])
908            .unwrap()
909            .with_initial_variable_constraints([
910                BooleanExpression::ComparisonExpression(
911                    Box::new(IntegerExpression::Atom(Variable::new("x"))),
912                    ComparisonOp::Lt,
913                    Box::new(IntegerExpression::Const(1)),
914                ),
915                BooleanExpression::ComparisonExpression(
916                    Box::new(IntegerExpression::Atom(Variable::new("y"))),
917                    ComparisonOp::Lt,
918                    Box::new(IntegerExpression::Const(1)),
919                ),
920            ])
921            .unwrap()
922            .with_rule(
923                RuleBuilder::new(0, Location::new("l1"), Location::new("l1"))
924                    .with_action(
925                        Action::new(
926                            Variable::new("x"),
927                            IntegerExpression::Const(1)
928                                + IntegerExpression::Atom(Variable::new("x")),
929                        )
930                        .unwrap(),
931                    )
932                    .build(),
933            )
934            .unwrap()
935            .build();
936        let lia_ta = LIAThresholdAutomaton::try_from(ta).unwrap();
937        let mut interval_tas = IntervalTABuilder::new(lia_ta, SMTSolverBuilder::default(), vec![])
938            .build()
939            .unwrap();
940        let interval_ta = interval_tas.next().unwrap();
941        assert!(interval_tas.next().is_none());
942        let ta = ACSThresholdAutomaton::from(interval_ta);
943
944        let loc_l1 = ta.to_cs_loc(&Location::new("l1"));
945        let loc_l2 = ta.to_cs_loc(&Location::new("l2"));
946        let loc_l3 = ta.to_cs_loc(&Location::new("l3"));
947
948        let var_x = ta.to_cs_var(&Variable::new("x"));
949
950        let spec = UpwardsClosedSet::new_cover([
951            Location::new("l3"),
952            Location::new("l2"),
953            Location::new("l1"),
954        ]);
955
956        // The target config will be (1, 1, 1) and should already be a
957        // fix point
958        let mut cfg_node_map = ErrorGraphNode::new_roots_from_spec(&spec, &ta);
959        let mut to_explore_queue: VecDeque<NodeRef> = VecDeque::new();
960
961        let mut loc_state = ACSLocState::new_empty(&ta);
962        loc_state[loc_l3] = 1;
963        loc_state[loc_l2] = 1;
964        loc_state[loc_l1] = 1;
965
966        let mut explored_is = ACSIntervalState::new_mock_zero(&ta);
967        explored_is[var_x] = ACSInterval::new_mock(1);
968
969        // Get the node out of the map or it won't be updated
970        let node_to_explore = cfg_node_map
971            .values()
972            .find(|n| *n.borrow().config.interval_state() == explored_is)
973            .unwrap()
974            .clone();
975
976        ErrorGraphNode::compute_predecessors_and_insert_to_graph(
977            &mut cfg_node_map,
978            &mut to_explore_queue,
979            &node_to_explore,
980            &ta,
981        );
982
983        assert_eq!(
984            to_explore_queue.len(),
985            0,
986            "Got configs {:?}",
987            join_iterator(
988                to_explore_queue
989                    .iter()
990                    .map(|n| n.borrow().config.display(&ta)),
991                ", "
992            )
993        );
994
995        let created_nodes = cfg_node_map
996            .get_all_values()
997            .map(|n| n.borrow().clone())
998            .collect::<Vec<_>>();
999
1000        // all nodes are already error states, so we expect them to stay
1001        let expected_nodes = ACSIntervalState::all_possible_interval_configs(&ta)
1002            .into_iter()
1003            .map(|is| {
1004                // in the abstraction using rule x = [1,\infty[, y = [0,1[ we could
1005                // have stayed in the same interval
1006                // -> insert a self-loop
1007                if is == explored_is {
1008                    return ErrorGraphNode {
1009                        error_level: 0,
1010                        config: ACSTAConfig::new_mock(loc_state.clone(), is),
1011                        parents: Vec::new(),
1012                        self_loops: vec![0],
1013                        back_edges: Vec::new(),
1014                    };
1015                }
1016
1017                if is == ACSIntervalState::new_mock_zero(&ta) {
1018                    return ErrorGraphNode {
1019                        error_level: 0,
1020                        config: ACSTAConfig::new_mock(
1021                            loc_state.clone(),
1022                            ACSIntervalState::new_mock_zero(&ta),
1023                        ),
1024                        parents: vec![ErrorGraphEdge::new_mock_with_id(
1025                            0,
1026                            ErrorGraphNode {
1027                                error_level: 0,
1028                                config: ACSTAConfig::new_mock(
1029                                    loc_state.clone(),
1030                                    explored_is.clone(),
1031                                ),
1032                                parents: Vec::new(),
1033                                self_loops: vec![0],
1034                                back_edges: Vec::new(),
1035                            }
1036                            .into(),
1037                        )],
1038                        self_loops: vec![],
1039                        back_edges: Vec::new(),
1040                    };
1041                }
1042
1043                // Don't update
1044
1045                ErrorGraphNode {
1046                    error_level: 0,
1047                    config: ACSTAConfig::new_mock(loc_state.clone(), is),
1048                    parents: Vec::new(),
1049                    self_loops: vec![],
1050                    back_edges: Vec::new(),
1051                }
1052            })
1053            .collect::<Vec<_>>();
1054
1055        assert!(
1056            created_nodes.iter().all(|n| expected_nodes.contains(n)),
1057            "got: {}\n expected: {}\n additional: {}",
1058            join_iterator(
1059                created_nodes
1060                    .iter()
1061                    .map(|n| { n.config.display_compact(&ta) }),
1062                ","
1063            ),
1064            join_iterator(
1065                expected_nodes
1066                    .iter()
1067                    .map(|n| { n.config.display_compact(&ta) }),
1068                ","
1069            ),
1070            join_iterator(
1071                created_nodes
1072                    .iter()
1073                    .filter(|n| !expected_nodes.contains(n))
1074                    .map(|n| { n.config.display_compact(&ta) }),
1075                "; "
1076            )
1077        );
1078        assert!(expected_nodes.iter().all(|n| created_nodes.contains(n)));
1079    }
1080}