Skip to main content

warp_types_pdr/
frames.rs

1//! Frame sequence for PDR.
2//!
3//! PDR maintains F₀, F₁, ..., Fₖ where each Fᵢ is a set of clauses
4//! overapproximating states reachable at depth ≤ i. Clauses are the
5//! negations of blocked cubes. The sequence is monotone: Fᵢ ⊆ Fᵢ₊₁.
6//!
7//! Convergence: if Fᵢ = Fᵢ₊₁ for any i ≥ 1, the property is proved
8//! (Fᵢ is an inductive invariant).
9
10use warp_types_sat::literal::Lit;
11
12// ============================================================================
13// Frame
14// ============================================================================
15
16/// A single frame in the PDR sequence.
17///
18/// Contains blocking clauses (negations of blocked cubes) that
19/// overapproximate the set of reachable states at this depth.
20#[derive(Debug, Clone)]
21pub struct Frame {
22    /// Blocking clauses. Each clause is ¬cube for some blocked cube.
23    clauses: Vec<Vec<Lit>>,
24}
25
26impl Frame {
27    /// Create an empty frame.
28    pub fn new() -> Self {
29        Frame {
30            clauses: Vec::new(),
31        }
32    }
33
34    /// Create a frame from initial-state clauses.
35    pub fn from_clauses(clauses: Vec<Vec<Lit>>) -> Self {
36        Frame { clauses }
37    }
38
39    /// Add a blocking clause (deduplicating).
40    pub fn add_clause(&mut self, clause: Vec<Lit>) {
41        // Normalize for comparison: sort literals
42        let mut sorted: Vec<u32> = clause.iter().map(|l| l.code()).collect();
43        sorted.sort();
44
45        // Check for duplicate
46        let is_dup = self.clauses.iter().any(|existing| {
47            let mut ex_sorted: Vec<u32> = existing.iter().map(|l| l.code()).collect();
48            ex_sorted.sort();
49            ex_sorted == sorted
50        });
51
52        if !is_dup {
53            self.clauses.push(clause);
54        }
55    }
56
57    /// Read access to clauses.
58    pub fn clauses(&self) -> &[Vec<Lit>] {
59        &self.clauses
60    }
61
62    /// Number of blocking clauses.
63    pub fn len(&self) -> usize {
64        self.clauses.len()
65    }
66
67    /// Whether the frame has no blocking clauses.
68    pub fn is_empty(&self) -> bool {
69        self.clauses.is_empty()
70    }
71}
72
73impl Default for Frame {
74    fn default() -> Self {
75        Self::new()
76    }
77}
78
79// ============================================================================
80// Frame sequence
81// ============================================================================
82
83/// The sequence of frames maintained by PDR.
84///
85/// F₀ contains the initial-state clauses. F₁..Fₖ accumulate blocking
86/// clauses as cubes are blocked during the strengthen phase.
87pub struct FrameSequence {
88    frames: Vec<Frame>,
89}
90
91impl FrameSequence {
92    /// Create an empty frame sequence.
93    pub fn new() -> Self {
94        FrameSequence {
95            frames: Vec::new(),
96        }
97    }
98
99    /// Add a frame to the sequence.
100    pub fn push(&mut self, frame: Frame) {
101        self.frames.push(frame);
102    }
103
104    /// Current frontier level (index of the last frame).
105    pub fn frontier(&self) -> usize {
106        self.frames.len().saturating_sub(1)
107    }
108
109    /// Number of frames.
110    pub fn len(&self) -> usize {
111        self.frames.len()
112    }
113
114    /// Access a frame by index.
115    pub fn frame(&self, level: usize) -> &Frame {
116        &self.frames[level]
117    }
118
119    /// Mutable access to a frame by index.
120    pub fn frame_mut(&mut self, level: usize) -> &mut Frame {
121        &mut self.frames[level]
122    }
123
124    /// Add a blocking clause to a specific frame level.
125    /// For monotonicity, also add to all frames at lower levels (1..=level).
126    pub fn add_blocked_clause(&mut self, level: usize, clause: Vec<Lit>) {
127        for i in 1..=level {
128            if i < self.frames.len() {
129                self.frames[i].add_clause(clause.clone());
130            }
131        }
132    }
133
134    /// Check convergence: if Fᵢ = Fᵢ₊₁ for any i ≥ 1, return Some(i).
135    ///
136    /// Compares frames by their sorted clause sets (order-independent).
137    pub fn check_convergence(&self) -> Option<usize> {
138        if self.frames.len() < 3 {
139            return None; // Need at least F₀, F₁, F₂
140        }
141        for i in 1..self.frames.len() - 1 {
142            if frames_equal(&self.frames[i], &self.frames[i + 1]) {
143                return Some(i);
144            }
145        }
146        None
147    }
148}
149
150impl Default for FrameSequence {
151    fn default() -> Self {
152        Self::new()
153    }
154}
155
156/// Compare two frames for equality (order-independent clause comparison).
157fn frames_equal(a: &Frame, b: &Frame) -> bool {
158    if a.len() != b.len() {
159        return false;
160    }
161    // Normalize: sort literals within each clause, then sort clauses
162    let normalize = |f: &Frame| -> Vec<Vec<u32>> {
163        let mut clauses: Vec<Vec<u32>> = f
164            .clauses()
165            .iter()
166            .map(|c| {
167                let mut sorted: Vec<u32> = c.iter().map(|l| l.code()).collect();
168                sorted.sort();
169                sorted
170            })
171            .collect();
172        clauses.sort();
173        clauses
174    };
175    normalize(a) == normalize(b)
176}
177
178// ============================================================================
179// Tests
180// ============================================================================
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn frame_add_and_read() {
188        let mut frame = Frame::new();
189        frame.add_clause(vec![Lit::pos(0), Lit::neg(1)]);
190        assert_eq!(frame.len(), 1);
191        assert_eq!(frame.clauses()[0].len(), 2);
192    }
193
194    #[test]
195    fn frames_equal_order_independent() {
196        let mut a = Frame::new();
197        a.add_clause(vec![Lit::pos(0)]);
198        a.add_clause(vec![Lit::neg(1)]);
199
200        let mut b = Frame::new();
201        b.add_clause(vec![Lit::neg(1)]);
202        b.add_clause(vec![Lit::pos(0)]);
203
204        assert!(frames_equal(&a, &b));
205    }
206
207    #[test]
208    fn convergence_detected() {
209        let mut seq = FrameSequence::new();
210        seq.push(Frame::new()); // F₀
211
212        let mut f1 = Frame::new();
213        f1.add_clause(vec![Lit::neg(0)]);
214        seq.push(f1); // F₁
215
216        let mut f2 = Frame::new();
217        f2.add_clause(vec![Lit::neg(0)]);
218        seq.push(f2); // F₂ = F₁
219
220        assert_eq!(seq.check_convergence(), Some(1));
221    }
222
223    #[test]
224    fn no_convergence_when_different() {
225        let mut seq = FrameSequence::new();
226        seq.push(Frame::new()); // F₀
227
228        let mut f1 = Frame::new();
229        f1.add_clause(vec![Lit::neg(0)]);
230        seq.push(f1);
231
232        let f2 = Frame::new(); // F₂ empty, F₁ has a clause
233        seq.push(f2);
234
235        assert_eq!(seq.check_convergence(), None);
236    }
237}