taco_acs_model_checker/
error_graph.rs1use std::{cell::RefCell, collections::VecDeque, rc::Rc};
18
19use log::info;
20use taco_model_checker::{ModelCheckerResult, reachability_specification::DisjunctionTargetConfig};
21use taco_smt_encoder::SMTSolver;
22
23use crate::{
24 acs_threshold_automaton::{
25 ACSThresholdAutomaton, configuration::partially_ordered_cfg_map::PartiallyOrderedConfigMap,
26 },
27 error_graph::{node::ErrorGraphNode, spurious_path_checker::SpuriousGraphChecker},
28};
29
30mod edge;
31mod node;
32mod spurious_path_checker;
33
34type NodeRef = Rc<RefCell<ErrorGraphNode>>;
40
41impl From<ErrorGraphNode> for NodeRef {
42 fn from(value: ErrorGraphNode) -> Self {
43 Rc::new(RefCell::new(value))
44 }
45}
46
47pub struct ErrorGraph {
50 initial_leafs: Vec<NodeRef>,
52 ta: ACSThresholdAutomaton,
54 spec: DisjunctionTargetConfig,
56}
57
58impl ErrorGraph {
59 pub fn compute_error_graph(spec: DisjunctionTargetConfig, ta: ACSThresholdAutomaton) -> Self {
63 let explored_cfgs = Self::construct_full_error_graph(&spec, &ta);
65
66 let len_before_init = explored_cfgs.len();
68
69 let initial_leafs: Vec<NodeRef> = explored_cfgs
71 .get_all_values()
72 .filter(|node| node.borrow().config().is_initial(&ta))
73 .collect();
74
75 let max_error_level = initial_leafs
76 .iter()
77 .map(|n| n.borrow().error_level())
78 .max()
79 .unwrap_or(0);
80
81 info!(
82 "Finished error graph construction for property '{}'. Explored {len_before_init} configurations of which {} are initial and have to be checked for spuriousness (max. error level {max_error_level})",
83 spec.name(),
84 initial_leafs.len(),
85 );
86
87 Self {
88 initial_leafs,
89 ta,
90 spec,
91 }
92 }
93
94 fn construct_full_error_graph(
97 spec: &DisjunctionTargetConfig,
98 ta: &ACSThresholdAutomaton,
99 ) -> PartiallyOrderedConfigMap<NodeRef> {
100 let mut explored_cfgs = ErrorGraphNode::new_roots_from_spec(spec, ta);
101 let mut unexplored_queue = explored_cfgs.values().cloned().collect::<VecDeque<_>>();
102
103 while let Some(unexplored) = unexplored_queue.pop_front() {
104 ErrorGraphNode::compute_predecessors_and_insert_to_graph(
105 &mut explored_cfgs,
106 &mut unexplored_queue,
107 &unexplored,
108 ta,
109 );
110 }
111
112 explored_cfgs
113 }
114
115 pub fn is_empty(&self) -> bool {
118 self.initial_leafs.is_empty()
119 }
120
121 pub fn check_for_non_spurious_counter_example(self, context: SMTSolver) -> ModelCheckerResult {
125 let mut visitor = SpuriousGraphChecker::new(&self.ta, self.spec.clone(), context);
126
127 visitor.find_violation(&self)
128 }
129}
130
131#[cfg(test)]
132mod tests {
133
134 use taco_interval_ta::IntervalThresholdAutomaton;
135 use taco_model_checker::{
136 SpecificationTrait, TATrait, reachability_specification::ReachabilityProperty,
137 };
138 use taco_parser::{ParseTAWithLTL, bymc::ByMCParser};
139 use taco_smt_encoder::SMTSolverBuilder;
140 use taco_threshold_automaton::expressions::Location;
141
142 use crate::{
143 acs_threshold_automaton::{
144 ACSThresholdAutomaton,
145 configuration::{ACSIntervalState, ACSLocState, ACSTAConfig},
146 },
147 error_graph::ErrorGraph,
148 };
149
150 #[test]
151 fn test_small_automaton_empty_err_graph() {
152 let test_spec = "
153 skel test_ta1 {
154 shared var1, var2, var3;
155 parameters n, f;
156
157 assumptions (1) {
158 n > 3 * f;
159 n == 1;
160 }
161
162 locations (2) {
163 loc1 : [0];
164 loc2 : [1];
165 loc3 : [2];
166 }
167
168 inits (1) {
169 loc1 == n - f;
170 loc2 == 0;
171 loc3 == 0;
172 var1 == 0;
173 var2 == 0;
174 var3 == 0;
175 }
176
177 rules (4) {
178 0: loc1 -> loc2
179 when(true)
180 do {};
181 }
182
183 specifications (1) {
184 test1:
185 [](loc3 == 0)
186 }
187 }
188 ";
189
190 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
191 let ctx = SMTSolverBuilder::default();
192
193 let spec = ReachabilityProperty::try_from_eltl(spec.into_iter(), &ctx).unwrap();
194
195 let spec_ta = ReachabilityProperty::transform_threshold_automaton(ta, spec, &ctx);
196
197 assert_eq!(spec_ta.len(), 1);
198 let (spec, ta) = spec_ta.into_iter().next().unwrap();
199
200 let mut ta = IntervalThresholdAutomaton::try_from_general_ta(ta, &ctx, &spec)
201 .expect("Failed to create interval ta");
202 assert_eq!(ta.len(), 1, "Expected one interval automaton to be parsed");
203
204 let ta = ACSThresholdAutomaton::from(ta.pop().unwrap());
205
206 let e_graph = ErrorGraph::compute_error_graph(spec, ta);
207
208 assert!(
209 e_graph.is_empty(),
210 "Got configs: {:?}",
211 e_graph.initial_leafs
212 );
213 }
214
215 #[test]
216 fn test_small_automaton_chain_finds_init() {
217 let test_spec = "
218 skel test_ta1 {
219 shared var1, var2, var3;
220 parameters n, f;
221
222 assumptions (1) {
223 n > 3 * f;
224 n == 1;
225 }
226
227 locations (2) {
228 loc1 : [0];
229 loc2 : [1];
230 loc3 : [2];
231 loc4 : [3];
232 loc5 : [4];
233 }
234
235 inits (1) {
236 loc1 == n - f;
237 loc2 == 0;
238 loc3 == 0;
239 loc4 == 0;
240 loc5 == 0;
241 var1 == 0;
242 var2 == 0;
243 var3 == 0;
244 }
245
246 rules (4) {
247 0: loc1 -> loc2
248 when(true)
249 do {};
250 1: loc2 -> loc3
251 when(true)
252 do {};
253 2: loc3 -> loc4
254 when(true)
255 do {};
256 3: loc4 -> loc5
257 when(true)
258 do {};
259 }
260
261 specifications (1) {
262 test1:
263 [](loc5 == 0)
264 }
265 }
266 ";
267
268 let (ta, spec) = ByMCParser::new().parse_ta_and_spec(test_spec).unwrap();
269 let ctx = SMTSolverBuilder::default();
270
271 let spec = ReachabilityProperty::try_from_eltl(spec.into_iter(), &ctx).unwrap();
272
273 let spec_ta = ReachabilityProperty::transform_threshold_automaton(ta, spec, &ctx);
274
275 assert_eq!(spec_ta.len(), 1);
276 let (spec, ta) = spec_ta.into_iter().next().unwrap();
277
278 let mut ta = IntervalThresholdAutomaton::try_from_general_ta(ta, &ctx, &spec)
279 .expect("Failed to create interval ta");
280 assert_eq!(ta.len(), 1, "Expected one interval automaton to be parsed");
281
282 let ta = ACSThresholdAutomaton::from(ta.pop().unwrap());
283
284 let e_graph = ErrorGraph::compute_error_graph(spec, ta.clone());
285
286 assert!(!e_graph.is_empty(),);
287
288 let mut expected_loc_state = ACSLocState::new_empty(&ta);
289 expected_loc_state[ta.to_cs_loc(&Location::new("loc1"))] = 1;
290 let expected_interval_state = ACSIntervalState::new_mock_zero(&ta);
291 let expected_config = ACSTAConfig::new_mock(expected_loc_state, expected_interval_state);
292
293 assert!(
294 e_graph
295 .initial_leafs
296 .into_iter()
297 .any(|l| l.borrow().config() == &expected_config)
298 );
299 }
300}