rust_formal_verification/algorithms/proof/
ic3_stateless_solver.rs

1//! This algorithm is an exact implementation of what is described in "SAT-Based Model Checking without Unrolling".
2//!
3//! Bradley, A.R. (2011). SAT-Based Model Checking without Unrolling.
4//! In: Jhala, R., Schmidt, D. (eds) Verification, Model Checking, and Abstract Interpretation.
5//! VMCAI 2011. Lecture Notes in Computer Science, vol 6538. Springer, Berlin,
6//! Heidelberg. https://doi.org/10.1007/978-3-642-18275-4_7
7//!
8//! Abstract: A new form of SAT-based symbolic model checking is described.
9//! Instead of unrolling the transition relation, it incrementally generates clauses that are
10//! inductive relative to (and augment) stepwise approximate reachability information.
11//! In this way, the algorithm gradually refines the property, eventually producing either an
12//! inductive strengthening of the property or a counterexample trace. Our experimental studies
13//! show that induction is a powerful tool for generalizing the unreachability of given error
14//! states: it can refine away many states at once, and it is effective at focusing the proof
15//! search on aspects of the transition system relevant to the property. Furthermore, the
16//! incremental structure of the algorithm lends itself to a parallel implementation.
17
18// ************************************************************************************************
19// use
20// ************************************************************************************************
21
22use crate::{
23    formulas::{literal::VariableType, Clause, Cube, Literal, CNF},
24    models::FiniteStateTransitionSystem,
25    solvers::sat::{SatResponse, StatelessSatSolver},
26};
27use priority_queue::PriorityQueue;
28use rand::rngs::ThreadRng;
29use rand::seq::SliceRandom;
30use rand::thread_rng;
31use std::cmp::{max, Reverse};
32use std::time;
33
34use super::{FiniteStateTransitionSystemProver, ProofResult};
35
36// ************************************************************************************************
37// Enum
38// ************************************************************************************************
39
40enum StrengthenResult {
41    Success,
42    Failure { _depth: VariableType },
43}
44
45enum InductivelyGeneralizeResult {
46    Success { n: usize },
47    Failure,
48}
49
50enum PushGeneralizeResult {
51    Success,
52    Failure,
53}
54
55// ************************************************************************************************
56// struct
57// ************************************************************************************************
58
59pub struct IC3Stateless<T> {
60    // for the algorithm
61    clauses: Vec<CNF>,
62    fin_state: FiniteStateTransitionSystem,
63    solver: T,
64    rng: ThreadRng,
65
66    // caching for speedup
67    initial: CNF,
68    transition: CNF,
69    p0: CNF,
70    not_p0: CNF,
71    not_p1: CNF,
72
73    // for printing
74    verbose: bool,
75    number_of_sat_calls: u32,
76    time_in_sat_calls: time::Duration,
77    start_time: time::Instant,
78}
79
80// ************************************************************************************************
81// impl
82// ************************************************************************************************
83
84impl<T: StatelessSatSolver> IC3Stateless<T> {
85    // ********************************************************************************************
86    // sat calls
87    // ********************************************************************************************
88
89    fn sat_call(&mut self, cnf_to_solve: &CNF) -> SatResponse {
90        self.number_of_sat_calls += 1;
91        let start_time = time::Instant::now();
92        let result = self.solver.solve_cnf(cnf_to_solve);
93        self.time_in_sat_calls += start_time.elapsed();
94        result
95    }
96
97    // ********************************************************************************************
98    // helper functions
99    // ********************************************************************************************
100
101    fn get_fk(&self, k: usize) -> CNF {
102        let mut clauses_fk = self.clauses[k].to_owned();
103        if k == 0 {
104            clauses_fk.append(&self.initial);
105        } else {
106            clauses_fk.append(&self.p0);
107        }
108        clauses_fk
109    }
110
111    fn is_bad_reached_in_0_steps(&mut self) -> SatResponse {
112        let mut cnf = CNF::new();
113        cnf.append(&self.initial);
114        cnf.append(&self.not_p0);
115        // println!("I ^ !P = {}", cnf);
116        self.sat_call(&cnf)
117    }
118
119    fn is_bad_reached_in_1_steps(&mut self) -> SatResponse {
120        let mut cnf = CNF::new();
121        cnf.append(&self.initial);
122        cnf.append(&self.transition);
123        cnf.append(&self.not_p1);
124        // println!("I ^ T ^ !P' = {}", cnf);
125        self.sat_call(&cnf)
126    }
127
128    fn propagate_clauses(&mut self, k: usize) {
129        for i in 1..(k + 1) {
130            let clauses_fi = self.clauses[i].to_owned();
131            for c in clauses_fi.iter() {
132                let mut cnf = CNF::new();
133                cnf.append(&self.get_fk(i));
134                cnf.append(&self.transition);
135                cnf.append(
136                    &self
137                        .fin_state
138                        .add_tags_to_relation(&(!(c.to_owned())).to_cnf(), 1),
139                );
140                match self.sat_call(&cnf) {
141                    SatResponse::UnSat => {
142                        // can propagate this property :)
143                        self.clauses[i + 1].add_clause(c);
144                    }
145                    SatResponse::Sat { assignment: _ } => {
146                        // can't propagate this clause :(
147                    }
148                }
149            }
150        }
151    }
152
153    fn is_bad_reached_in_1_step_from_cnf(&mut self, cnf: &CNF) -> SatResponse {
154        let mut new_cnf = CNF::new();
155        new_cnf.append(cnf);
156        new_cnf.append(&self.transition);
157        new_cnf.append(&self.not_p1);
158        self.sat_call(&new_cnf)
159    }
160
161    // calculates sat(Fi ^ T ^ !s ^ s')
162    fn is_fi_and_t_and_not_s_and_s_tag_sat(&mut self, i: usize, s: &Cube) -> bool {
163        let mut new_cnf = CNF::new();
164        new_cnf.append(&self.get_fk(i));
165        new_cnf.append(&self.transition);
166        new_cnf.add_clause(&!(s.to_owned()));
167        new_cnf.append(&self.fin_state.add_tags_to_relation(&s.to_cnf(), 1));
168        match self.sat_call(&new_cnf) {
169            SatResponse::UnSat => false,
170            SatResponse::Sat { assignment: _ } => true,
171        }
172    }
173
174    fn is_clause_inductive_relative_to_fi(&mut self, d: &Clause, i: usize) -> bool {
175        // return !(Init ∧ ¬d) && !((Fi ∧ d)∧ Tr ∧ ¬d’)
176
177        let mut first_cnf = self.initial.to_owned();
178        first_cnf.append(&(!d.to_owned()).to_cnf());
179        match self.sat_call(&first_cnf) {
180            SatResponse::UnSat => {}
181            SatResponse::Sat { assignment: _ } => {
182                return false;
183            }
184        }
185
186        let mut second_cnf = self.get_fk(i);
187        second_cnf.add_clause(d);
188        second_cnf.append(&self.transition);
189        second_cnf.append(
190            &self
191                .fin_state
192                .add_tags_to_relation(&(!d.to_owned()).to_cnf(), 1),
193        );
194        match self.sat_call(&second_cnf) {
195            SatResponse::UnSat => true,
196            SatResponse::Sat { assignment: _ } => false,
197        }
198    }
199
200    fn get_subclause_of_not_s_that_is_inductive_relative_to_fi(
201        &mut self,
202        s: &Cube,
203        i: usize,
204    ) -> Clause {
205        let c = !(s.to_owned());
206        let mut c_literals: Vec<Literal> = c.iter().map(|l| l.to_owned()).collect();
207        c_literals.shuffle(&mut self.rng);
208        let mut j = 0;
209        while j < c_literals.len() {
210            let removed = c_literals.swap_remove(j);
211            let d = Clause::new(&c_literals);
212            if self.is_clause_inductive_relative_to_fi(&d, i) {
213                // remove successful, j should remain the same
214            } else {
215                // undo remove
216                c_literals.push(removed);
217                let last_index = c_literals.len() - 1;
218                c_literals.swap(j, last_index);
219                // move on to next literal
220                j += 1;
221            }
222        }
223        Clause::new(&c_literals)
224    }
225
226    fn generate_clause(&mut self, s: &Cube, i: usize, _k: usize) {
227        let c = self.get_subclause_of_not_s_that_is_inductive_relative_to_fi(s, i);
228        for j in 1..(i + 2) {
229            self.clauses[j].add_clause(&c);
230        }
231    }
232
233    fn inductively_generalize(
234        &mut self,
235        s: &Cube,
236        min: isize,
237        k: usize,
238    ) -> InductivelyGeneralizeResult {
239        if min < 0 && self.is_fi_and_t_and_not_s_and_s_tag_sat(0, s) {
240            return InductivelyGeneralizeResult::Failure;
241        }
242
243        for i in max(1, min + 1).try_into().unwrap()..(k + 1) {
244            if self.is_fi_and_t_and_not_s_and_s_tag_sat(i, s) {
245                self.generate_clause(s, i - 1, k);
246                return InductivelyGeneralizeResult::Success { n: i - 1 };
247            }
248        }
249        self.generate_clause(s, k, k);
250        InductivelyGeneralizeResult::Success { n: k }
251    }
252
253    // calculates sat(Fi ^ T ^ s')
254    fn solve_fi_and_t_and_s_tag(&mut self, i: usize, s: &Cube) -> SatResponse {
255        let mut new_cnf = CNF::new();
256        new_cnf.append(&self.get_fk(i));
257        new_cnf.append(&self.transition);
258        new_cnf.append(&self.fin_state.add_tags_to_relation(&s.to_cnf(), 1));
259        self.sat_call(&new_cnf)
260    }
261
262    fn push_generalization(
263        &mut self,
264        states: &PriorityQueue<Cube, Reverse<usize>>,
265        k: usize,
266    ) -> PushGeneralizeResult {
267        let mut states = states.to_owned();
268        loop {
269            let (s, reversed_n) = states.pop().unwrap();
270            let n = reversed_n.0;
271            if n > k {
272                return PushGeneralizeResult::Success;
273            }
274            match self.solve_fi_and_t_and_s_tag(n, &s) {
275                SatResponse::Sat { assignment } => {
276                    // we have to block p in order to block n.
277                    let p = self.fin_state.extract_state_from_assignment(&assignment);
278                    // println!("Should block p = {} from F{}", p, n - 1);
279                    match self.inductively_generalize(
280                        &p,
281                        <usize as TryInto<isize>>::try_into(n).unwrap() - 2,
282                        k,
283                    ) {
284                        InductivelyGeneralizeResult::Failure => {
285                            return PushGeneralizeResult::Failure;
286                        }
287                        InductivelyGeneralizeResult::Success { n: m } => {
288                            states.push(s, reversed_n);
289                            states.push(p, Reverse(m + 1));
290                        }
291                    }
292                }
293                SatResponse::UnSat => {
294                    // n can be blocked
295                    match self.inductively_generalize(&s, n.try_into().unwrap(), k) {
296                        InductivelyGeneralizeResult::Failure => {
297                            return PushGeneralizeResult::Failure;
298                        }
299                        InductivelyGeneralizeResult::Success { n: m } => {
300                            states.push(s.to_owned(), Reverse(m + 1));
301                        }
302                    }
303                }
304            }
305        }
306    }
307
308    fn print_progress_if_verbose(&self, k: usize) {
309        if self.verbose {
310            let clauses = self
311                .clauses
312                .iter()
313                .map(|c| c.len())
314                .rev()
315                .take(10)
316                .collect::<Vec<usize>>();
317            println!("IC3 is on k = {}, clauses lengths = {:?}", k, clauses);
318            println!("Number of SAT calls = {}", self.number_of_sat_calls);
319            println!(
320                "Time since start = {}",
321                self.start_time.elapsed().as_secs_f32()
322            );
323            println!(
324                "Time in SAT calls = {}",
325                self.time_in_sat_calls.as_secs_f32()
326            );
327        }
328    }
329
330    fn strengthen(&mut self, k: usize) -> StrengthenResult {
331        loop {
332            match self.is_bad_reached_in_1_step_from_cnf(&self.get_fk(k)) {
333                SatResponse::UnSat => {
334                    break;
335                }
336                SatResponse::Sat { assignment } => {
337                    let s = self.fin_state.extract_state_from_assignment(&assignment);
338                    // println!("Should block s = {} from F{}", s, k - 1);
339                    match self.inductively_generalize(
340                        &s,
341                        <usize as TryInto<isize>>::try_into(k).unwrap() - 2,
342                        k,
343                    ) {
344                        InductivelyGeneralizeResult::Failure => {
345                            return StrengthenResult::Failure {
346                                _depth: k.try_into().unwrap(),
347                            };
348                        }
349                        InductivelyGeneralizeResult::Success { n } => {
350                            let mut queue = PriorityQueue::<Cube, Reverse<usize>>::new();
351                            queue.push(s, Reverse(n + 1));
352                            match self.push_generalization(&queue, k) {
353                                PushGeneralizeResult::Failure => {
354                                    return StrengthenResult::Failure {
355                                        _depth: k.try_into().unwrap(),
356                                    };
357                                }
358                                PushGeneralizeResult::Success => {}
359                            };
360                        }
361                    };
362                }
363            }
364        }
365
366        StrengthenResult::Success
367    }
368
369    // ********************************************************************************************
370    // API functions
371    // ********************************************************************************************
372
373    pub fn new(fin_state: &FiniteStateTransitionSystem, verbose: bool) -> Self {
374        let mut p0 = fin_state.get_state_to_safety_translation();
375        p0.append(&fin_state.get_safety_property().to_cnf());
376
377        let mut not_p0 = fin_state.get_state_to_safety_translation();
378        not_p0.append(&fin_state.get_unsafety_property().to_cnf());
379
380        Self {
381            clauses: Vec::new(),
382            fin_state: fin_state.to_owned(),
383            solver: T::default(),
384            rng: thread_rng(),
385            initial: fin_state.get_initial_relation().to_cnf(),
386            transition: fin_state.get_transition_relation(),
387            p0,
388            not_p0: not_p0.to_owned(),
389            not_p1: fin_state.add_tags_to_relation(&not_p0, 1),
390            verbose,
391            number_of_sat_calls: 0,
392            time_in_sat_calls: time::Duration::from_secs(0),
393            start_time: time::Instant::now(),
394        }
395    }
396
397    pub fn prove(&mut self) -> ProofResult {
398        // update start time.
399        self.start_time = time::Instant::now();
400
401        let init_and_not_p = self.is_bad_reached_in_0_steps();
402        match init_and_not_p {
403            SatResponse::Sat { assignment: _ } => return ProofResult::CTX { depth: 0 },
404            SatResponse::UnSat => (),
405        }
406        // debug_assert!(does_a_imply_b::<T>(&self.initial, &self.p0));
407
408        let init_and_tr_and_not_p_tag = self.is_bad_reached_in_1_steps();
409        match init_and_tr_and_not_p_tag {
410            SatResponse::Sat { assignment: _ } => return ProofResult::CTX { depth: 1 },
411            SatResponse::UnSat => (),
412        }
413
414        self.clauses.push(CNF::new());
415        self.clauses.push(CNF::new());
416        for k in 1.. {
417            self.clauses.push(CNF::new());
418            // debug_assert!(self.does_a_hold(k), "Bug in algorithm implementation found!!");
419            self.print_progress_if_verbose(k);
420            debug_assert_eq!(self.clauses.len(), (k + 2));
421            match self.strengthen(k) {
422                StrengthenResult::Success => {}
423                StrengthenResult::Failure { _depth: _ } => {
424                    return ProofResult::CTX {
425                        depth: k.try_into().unwrap(),
426                    };
427                }
428            };
429            self.propagate_clauses(k);
430            for i in 1..(k + 1) {
431                // all clauses in i+1 should be in i.
432                debug_assert!(self.clauses[i + 1]
433                    .iter()
434                    .all(|c| self.clauses[i].contains(c)));
435                if self.clauses[i].len() == self.clauses[i + 1].len() {
436                    // todo: compare just the lengths
437                    self.print_progress_if_verbose(k);
438                    return ProofResult::Proof {
439                        invariant: self.get_fk(i),
440                    };
441                }
442            }
443        }
444        unreachable!();
445    }
446}
447
448// ************************************************************************************************
449// impl trait
450// ************************************************************************************************
451
452impl<T: StatelessSatSolver> FiniteStateTransitionSystemProver for IC3Stateless<T> {
453    fn new(fin_state: &FiniteStateTransitionSystem) -> Self {
454        IC3Stateless::new(fin_state, true)
455    }
456
457    fn prove(&mut self) -> ProofResult {
458        self.prove()
459    }
460}