1use crate::ir::{VerifyExpr, VerifyOp};
9use crate::equivalence::Trace;
10use std::collections::{HashMap, HashSet};
11use z3::{ast::Bool, ast::Int, SatResult, Solver};
12
13#[derive(Debug)]
19pub enum ConsistencyResult {
20 Consistent,
22 Inconsistent {
25 conflicting: Vec<(usize, usize)>,
26 witness: Trace,
27 },
28 Unknown,
30}
31
32pub fn check_consistency(
37 props: &[VerifyExpr],
38 signals: &[String],
39 bound: usize,
40) -> ConsistencyResult {
41 if props.is_empty() {
42 return ConsistencyResult::Consistent;
43 }
44
45 let solver = crate::solver::new_solver();
46
47 let mut var_map: HashMap<String, Bool> = HashMap::new();
49 for sig in signals {
50 for t in 0..=bound {
51 let var_name = format!("{}@{}", sig, t);
52 var_map.insert(var_name.clone(), Bool::new_const(var_name.as_str()));
53 }
54 }
55
56 let mut all_vars: HashSet<String> = HashSet::new();
58 for prop in props {
59 crate::equivalence::collect_vars_pub(prop, &mut all_vars);
60 }
61 for var_name in &all_vars {
62 if !var_map.contains_key(var_name) {
63 var_map.insert(var_name.clone(), Bool::new_const(var_name.as_str()));
64 }
65 }
66
67 let empty_int_vars = HashMap::new();
69 let empty_bv_vars = HashMap::new();
70 let empty_array_vars = HashMap::new();
71 let encoder = crate::equivalence::EquivEncoder::new(&var_map, &empty_int_vars, &empty_bv_vars, &empty_array_vars);
72 for prop in props {
73 let encoded = encoder.encode_as_bool(prop);
74 solver.assert(&encoded);
75 }
76
77 match solver.check() {
78 SatResult::Sat => ConsistencyResult::Consistent,
79 SatResult::Unsat => {
80 let mut conflicting = Vec::new();
82 for i in 0..props.len() {
83 for j in (i + 1)..props.len() {
84 let pair_solver = crate::solver::new_solver();
85 let ei = encoder.encode_as_bool(&props[i]);
86 let ej = encoder.encode_as_bool(&props[j]);
87 pair_solver.assert(&ei);
88 pair_solver.assert(&ej);
89 if pair_solver.check() == SatResult::Unsat {
90 conflicting.push((i, j));
91 }
92 }
93 }
94 ConsistencyResult::Inconsistent {
95 conflicting,
96 witness: Trace { cycles: vec![] },
97 }
98 }
99 SatResult::Unknown => ConsistencyResult::Unknown,
100 }
101}
102
103#[derive(Debug, Clone)]
109pub struct LabeledFormula {
110 pub index: usize,
111 pub label: String,
112 pub expr: VerifyExpr,
113}
114
115#[derive(Debug, Clone)]
117pub struct ConsistencyConfig {
118 pub timeout_ms: u64,
120 pub temporal_bound: u32,
122 pub check_vacuity: bool,
124 pub check_redundancy: bool,
126 pub check_pairwise: bool,
128}
129
130impl Default for ConsistencyConfig {
131 fn default() -> Self {
132 Self {
133 timeout_ms: 5000,
134 temporal_bound: 8,
135 check_vacuity: true,
136 check_redundancy: true,
137 check_pairwise: true,
138 }
139 }
140}
141
142#[derive(Debug)]
144pub struct ConsistencyReport {
145 pub satisfiability: SatisfiabilityResult,
146 pub vacuity: Vec<VacuityFinding>,
147 pub redundancies: Vec<RedundancyFinding>,
148 pub pairwise_conflicts: Vec<PairwiseConflict>,
149}
150
151#[derive(Debug, PartialEq)]
153pub enum SatisfiabilityResult {
154 Satisfiable,
156 Unsatisfiable { mus: Vec<usize> },
159 Unknown,
161}
162
163#[derive(Debug)]
165pub struct VacuityFinding {
166 pub formula_index: usize,
167 pub label: String,
168}
169
170#[derive(Debug)]
172pub struct RedundancyFinding {
173 pub redundant_index: usize,
174 pub label: String,
175 pub entailed_by: Vec<usize>,
176}
177
178#[derive(Debug)]
180pub struct PairwiseConflict {
181 pub i: usize,
182 pub j: usize,
183}
184
185pub fn check_spec_consistency(
196 formulas: &[LabeledFormula],
197 config: &ConsistencyConfig,
198) -> ConsistencyReport {
199 if formulas.is_empty() {
200 return ConsistencyReport {
201 satisfiability: SatisfiabilityResult::Satisfiable,
202 vacuity: vec![],
203 redundancies: vec![],
204 pairwise_conflicts: vec![],
205 };
206 }
207
208
209 let mut all_vars: HashSet<String> = HashSet::new();
211 for lf in formulas {
212 crate::equivalence::collect_vars_pub(&lf.expr, &mut all_vars);
213 }
214
215 let mut bool_vars: HashMap<String, Bool> = HashMap::new();
217 let mut int_vars: HashMap<String, Int> = HashMap::new();
218 for var_name in &all_vars {
219 bool_vars.insert(
220 var_name.clone(),
221 Bool::new_const(var_name.as_str()),
222 );
223 }
224 let exprs: Vec<&VerifyExpr> = formulas.iter().map(|lf| &lf.expr).collect();
226 for expr in &exprs {
227 crate::equivalence::collect_int_vars_pub(expr, &mut int_vars);
228 }
229
230 let empty_bv_vars = HashMap::new();
231 let empty_array_vars = HashMap::new();
232 let encoder = crate::equivalence::EquivEncoder::new(&bool_vars, &int_vars, &empty_bv_vars, &empty_array_vars);
233
234 let solver = crate::solver::new_solver();
236 for lf in formulas {
237 let encoded = encoder.encode_as_bool(&lf.expr);
238 solver.assert(&encoded);
239 }
240
241 match solver.check() {
242 SatResult::Sat => {
243 let vacuity = if config.check_vacuity {
245 detect_vacuity(&encoder, formulas, config.timeout_ms)
246 } else {
247 vec![]
248 };
249
250 let redundancies = if config.check_redundancy {
251 detect_redundancy(&encoder, formulas, config.timeout_ms)
252 } else {
253 vec![]
254 };
255
256 ConsistencyReport {
258 satisfiability: SatisfiabilityResult::Satisfiable,
259 vacuity,
260 redundancies,
261 pairwise_conflicts: vec![],
262 }
263 }
264 SatResult::Unsat => {
265 let mus = extract_mus(&encoder, formulas, config.timeout_ms);
267
268 let pairwise_conflicts = if config.check_pairwise {
270 detect_pairwise_conflicts(&encoder, formulas, config.timeout_ms)
271 } else {
272 vec![]
273 };
274
275 ConsistencyReport {
276 satisfiability: SatisfiabilityResult::Unsatisfiable { mus },
277 vacuity: vec![],
278 redundancies: vec![],
279 pairwise_conflicts,
280 }
281 }
282 SatResult::Unknown => {
283 ConsistencyReport {
284 satisfiability: SatisfiabilityResult::Unknown,
285 vacuity: vec![],
286 redundancies: vec![],
287 pairwise_conflicts: vec![],
288 }
289 }
290 }
291}
292
293fn extract_mus(
306 encoder: &crate::equivalence::EquivEncoder,
307 formulas: &[LabeledFormula],
308 timeout_ms: u64,
309) -> Vec<usize> {
310 let n = formulas.len();
311 let mut in_mus: Vec<bool> = vec![true; n];
312
313 for i in 0..n {
314 let solver = Solver::new();
316 solver.set_params(&{
317 let mut params = z3::Params::new();
318 params.set_u32("timeout", timeout_ms as u32);
319 params
320 });
321
322 for j in 0..n {
323 if j == i || !in_mus[j] {
324 continue;
325 }
326 let encoded = encoder.encode_as_bool(&formulas[j].expr);
327 solver.assert(&encoded);
328 }
329
330 match solver.check() {
331 SatResult::Sat => {
332 }
334 SatResult::Unsat => {
335 in_mus[i] = false;
337 }
338 SatResult::Unknown => {
339 }
341 }
342 }
343
344 in_mus.iter().enumerate()
345 .filter(|(_, &b)| b)
346 .map(|(i, _)| i)
347 .collect()
348}
349
350fn extract_antecedent(expr: &VerifyExpr) -> Option<&VerifyExpr> {
358 match expr {
359 VerifyExpr::Binary { op: VerifyOp::Implies, left, .. } => Some(left),
360 VerifyExpr::ForAll { body, .. } => extract_antecedent(body),
361 _ => None,
362 }
363}
364
365fn detect_vacuity(
371 encoder: &crate::equivalence::EquivEncoder,
372 formulas: &[LabeledFormula],
373 timeout_ms: u64,
374) -> Vec<VacuityFinding> {
375 let mut findings = Vec::new();
376
377 for i in 0..formulas.len() {
378 let antecedent = match extract_antecedent(&formulas[i].expr) {
379 Some(a) => a,
380 None => continue,
381 };
382
383 let solver = Solver::new();
385 solver.set_params(&{
386 let mut params = z3::Params::new();
387 params.set_u32("timeout", timeout_ms as u32);
388 params
389 });
390
391 for j in 0..formulas.len() {
392 if j == i {
393 continue;
394 }
395 let encoded = encoder.encode_as_bool(&formulas[j].expr);
396 solver.assert(&encoded);
397 }
398
399 let ante_encoded = encoder.encode_as_bool(antecedent);
401 solver.assert(&ante_encoded);
402
403 if solver.check() == SatResult::Unsat {
404 findings.push(VacuityFinding {
405 formula_index: i,
406 label: formulas[i].label.clone(),
407 });
408 }
409 }
410
411 findings
412}
413
414fn detect_redundancy(
423 encoder: &crate::equivalence::EquivEncoder,
424 formulas: &[LabeledFormula],
425 timeout_ms: u64,
426) -> Vec<RedundancyFinding> {
427 let mut findings = Vec::new();
428
429 for i in 0..formulas.len() {
430 let solver = Solver::new();
431 solver.set_params(&{
432 let mut params = z3::Params::new();
433 params.set_u32("timeout", timeout_ms as u32);
434 params
435 });
436
437 for j in 0..formulas.len() {
439 if j == i {
440 continue;
441 }
442 let encoded = encoder.encode_as_bool(&formulas[j].expr);
443 solver.assert(&encoded);
444 }
445
446 let fi_encoded = encoder.encode_as_bool(&formulas[i].expr);
448 solver.assert(&fi_encoded.not());
449
450 if solver.check() == SatResult::Unsat {
451 let entailed_by: Vec<usize> = (0..formulas.len())
453 .filter(|&j| j != i)
454 .collect();
455 findings.push(RedundancyFinding {
456 redundant_index: i,
457 label: formulas[i].label.clone(),
458 entailed_by,
459 });
460 }
461 }
462
463 findings
464}
465
466fn detect_pairwise_conflicts(
474 encoder: &crate::equivalence::EquivEncoder,
475 formulas: &[LabeledFormula],
476 timeout_ms: u64,
477) -> Vec<PairwiseConflict> {
478 let mut conflicts = Vec::new();
479
480 for i in 0..formulas.len() {
481 for j in (i + 1)..formulas.len() {
482 let solver = Solver::new();
483 solver.set_params(&{
484 let mut params = z3::Params::new();
485 params.set_u32("timeout", timeout_ms as u32);
486 params
487 });
488
489 let ei = encoder.encode_as_bool(&formulas[i].expr);
490 let ej = encoder.encode_as_bool(&formulas[j].expr);
491 solver.assert(&ei);
492 solver.assert(&ej);
493
494 if solver.check() == SatResult::Unsat {
495 conflicts.push(PairwiseConflict { i, j });
496 }
497 }
498 }
499
500 conflicts
501}