logicaffeine_verify/
interpolation.rs1use crate::ir::VerifyExpr;
11use crate::equivalence::Trace;
12use crate::kinduction;
13use std::collections::{HashMap, HashSet};
14use z3::{ast::Bool, ast::Int, SatResult, Solver};
15
16#[derive(Debug)]
18pub enum InterpolationResult {
19 Safe,
21 Unsafe { trace: Trace },
23 Fixpoint { iterations: u32 },
25 Unknown,
27}
28
29pub fn interpolate(a: &VerifyExpr, b: &VerifyExpr) -> Option<VerifyExpr> {
37 let solver = crate::solver::new_solver();
38
39 let a_bool = encode_to_bool(a);
40 let b_bool = encode_to_bool(b);
41
42 solver.assert(&a_bool);
44 solver.assert(&b_bool);
45
46 match solver.check() {
47 SatResult::Unsat => {
48 let a_vars = collect_expr_vars(a);
50 let b_vars = collect_expr_vars(b);
51 let shared: HashSet<&String> = a_vars.intersection(&b_vars).collect();
52
53 let projected = project_to_shared(a, &shared);
57
58 let check_implies = {
60 let s = crate::solver::new_solver();
61 s.assert(&encode_to_bool(a));
62 s.assert(&encode_to_bool(&VerifyExpr::not(projected.clone())));
63 matches!(s.check(), SatResult::Unsat)
64 };
65 let check_contra = {
66 let s = crate::solver::new_solver();
67 s.assert(&encode_to_bool(&projected));
68 s.assert(&encode_to_bool(b));
69 matches!(s.check(), SatResult::Unsat)
70 };
71
72 if check_implies && check_contra {
73 Some(projected)
74 } else {
75 build_interpolant_from_clauses(a, b, &shared)
77 }
78 }
79 SatResult::Sat => None,
80 SatResult::Unknown => None,
81 }
82}
83
84fn project_to_shared(expr: &VerifyExpr, shared: &HashSet<&String>) -> VerifyExpr {
87 match expr {
88 VerifyExpr::Binary { op: crate::ir::VerifyOp::And, left, right } => {
89 let l = project_to_shared(left, shared);
90 let r = project_to_shared(right, shared);
91 match (&l, &r) {
92 (VerifyExpr::Bool(true), _) => r,
93 (_, VerifyExpr::Bool(true)) => l,
94 _ => VerifyExpr::and(l, r),
95 }
96 }
97 _ => {
98 let vars = collect_expr_vars(expr);
99 if vars.is_empty() || vars.iter().all(|v| shared.contains(v)) {
100 expr.clone()
101 } else {
102 VerifyExpr::bool(true)
104 }
105 }
106 }
107}
108
109fn build_interpolant_from_clauses(
112 a: &VerifyExpr,
113 b: &VerifyExpr,
114 shared: &HashSet<&String>,
115) -> Option<VerifyExpr> {
116 let clauses = extract_conjuncts(a);
118
119 let shared_clauses: Vec<VerifyExpr> = clauses.into_iter().filter(|c| {
121 let vars = collect_expr_vars(c);
122 vars.iter().all(|v| shared.contains(v))
123 }).collect();
124
125 if shared_clauses.is_empty() {
126 return try_interpolant_from_b_negation(b, shared);
129 }
130
131 let mut candidate = shared_clauses[0].clone();
132 for c in &shared_clauses[1..] {
133 candidate = VerifyExpr::and(candidate, c.clone());
134 }
135
136 let s = Solver::new();
138 s.assert(&encode_to_bool(&candidate));
139 s.assert(&encode_to_bool(b));
140 if matches!(s.check(), SatResult::Unsat) {
141 Some(candidate)
142 } else {
143 try_interpolant_from_b_negation(b, shared)
146 }
147}
148
149fn try_interpolant_from_b_negation(
151 b: &VerifyExpr,
152 shared: &HashSet<&String>,
153) -> Option<VerifyExpr> {
154 let b_vars = collect_expr_vars(b);
157 if b_vars.iter().all(|v| shared.contains(v)) {
158 return Some(VerifyExpr::not(b.clone()));
159 }
160
161 let not_b = VerifyExpr::not(b.clone());
163 let projected = project_to_shared(¬_b, shared);
164 if !matches!(projected, VerifyExpr::Bool(true)) {
165 return Some(projected);
166 }
167
168 None
169}
170
171fn extract_conjuncts(expr: &VerifyExpr) -> Vec<VerifyExpr> {
173 match expr {
174 VerifyExpr::Binary { op: crate::ir::VerifyOp::And, left, right } => {
175 let mut v = extract_conjuncts(left);
176 v.extend(extract_conjuncts(right));
177 v
178 }
179 _ => vec![expr.clone()],
180 }
181}
182
183fn collect_expr_vars(expr: &VerifyExpr) -> HashSet<String> {
185 let mut vars = HashSet::new();
186 collect_vars_recursive(expr, &mut vars);
187 vars
188}
189
190fn collect_vars_recursive(expr: &VerifyExpr, vars: &mut HashSet<String>) {
191 match expr {
192 VerifyExpr::Var(name) => { vars.insert(name.clone()); }
193 VerifyExpr::Binary { left, right, .. } => {
194 collect_vars_recursive(left, vars);
195 collect_vars_recursive(right, vars);
196 }
197 VerifyExpr::Not(inner) => collect_vars_recursive(inner, vars),
198 VerifyExpr::Iff(l, r) => {
199 collect_vars_recursive(l, vars);
200 collect_vars_recursive(r, vars);
201 }
202 VerifyExpr::ForAll { body, .. } | VerifyExpr::Exists { body, .. } => {
203 collect_vars_recursive(body, vars);
204 }
205 _ => {}
206 }
207}
208
209pub fn itp_model_check(
217 init: &VerifyExpr,
218 transition: &VerifyExpr,
219 property: &VerifyExpr,
220 bound: u32,
221) -> InterpolationResult {
222
223 for k in 0..bound {
225 let solver = crate::solver::new_solver();
226
227 let init_0 = kinduction::instantiate_at(init, 0);
228 solver.assert(&encode_to_bool(&init_0));
229
230 for t in 0..k {
231 let trans = kinduction::instantiate_transition(transition, t);
232 solver.assert(&encode_to_bool(&trans));
233 }
234
235 let prop_k = kinduction::instantiate_at(property, k);
236 solver.assert(&encode_to_bool(&prop_k).not());
237
238 match solver.check() {
239 SatResult::Sat => {
240 return InterpolationResult::Unsafe {
241 trace: Trace { cycles: vec![] },
242 };
243 }
244 SatResult::Unknown => return InterpolationResult::Unknown,
245 SatResult::Unsat => {}
246 }
247 }
248
249 let solver = crate::solver::new_solver();
251 let prop_t = kinduction::instantiate_at(property, 0);
252 let trans = kinduction::instantiate_transition(transition, 0);
253 let prop_t1 = kinduction::instantiate_at(property, 1);
254
255 solver.assert(&encode_to_bool(&prop_t));
256 solver.assert(&encode_to_bool(&trans));
257 solver.assert(&encode_to_bool(&prop_t1).not());
258
259 match solver.check() {
260 SatResult::Unsat => InterpolationResult::Safe,
261 _ => {
262 let kind_result = kinduction::k_induction(
264 init, transition, property, &[], bound,
265 );
266 match kind_result {
267 kinduction::KInductionResult::Proven { k } => {
268 InterpolationResult::Fixpoint { iterations: k }
269 }
270 kinduction::KInductionResult::Counterexample { trace, .. } => {
271 InterpolationResult::Unsafe { trace }
272 }
273 _ => InterpolationResult::Unknown,
274 }
275 }
276 }
277}
278
279fn encode_to_bool(expr: &VerifyExpr) -> Bool {
281 let mut bool_vars: HashMap<String, Bool> = HashMap::new();
282 let mut int_vars: HashMap<String, Int> = HashMap::new();
283
284 let mut all_vars = std::collections::HashSet::new();
285 crate::equivalence::collect_vars_pub(expr, &mut all_vars);
286 for name in &all_vars {
287 bool_vars.insert(name.clone(), Bool::new_const(name.as_str()));
288 }
289 crate::equivalence::collect_int_vars_pub(expr, &mut int_vars);
290
291 kinduction::encode_expr_bool(expr, &bool_vars, &int_vars)
292}