smt_scope/parsers/z3/
cdcl.rs

1use std::num::NonZeroU32;
2
3#[cfg(feature = "mem_dbg")]
4use mem_dbg::{MemDbg, MemSize};
5use typed_index_collections::TiSlice;
6
7use crate::{
8    items::{
9        Assignment, Cdcl, CdclBacklink, CdclIdx, CdclKind, Conflict, ENodeIdx, InstIdx,
10        Justification, JustificationKind, LitIdx, Literal, StackIdx, TermIdx,
11    },
12    Error, FxHashMap, Result, TiVec,
13};
14
15use super::stack::Stack;
16
17#[cfg_attr(feature = "mem_dbg", derive(MemSize, MemDbg))]
18#[derive(Debug, Default)]
19pub struct Literals {
20    literals: TiVec<LitIdx, Literal>,
21    // Issue 3: No way to convert LitId -> TermIdx
22    #[cfg(any())]
23    pub lit_stack: SortedVec<LitIdx>,
24    pub assignments: FxHashMap<TermIdx, LitIdx>,
25
26    pub cdcl: CdclTree,
27}
28
29#[cfg(any())]
30#[cfg_attr(feature = "mem_dbg", derive(MemSize, MemDbg))]
31#[derive(Debug)]
32pub struct LitStack {
33    global_lits: u32,
34    stack: Vec<LitIdx>,
35}
36
37impl Literals {
38    pub fn new_literal(
39        &mut self,
40        term: Assignment,
41        iblame: Option<InstIdx>,
42        enode: Option<ENodeIdx>,
43        stack: &Stack,
44    ) -> Result<LitIdx> {
45        let frame = stack.active_frame();
46        let lit = Literal {
47            term,
48            frame,
49            iblame,
50            enode,
51            justification: Default::default(),
52        };
53        self.literals.try_reserve(1)?;
54        let lit = self.literals.push_and_get_key(lit);
55        self.assignments.try_reserve(1)?;
56        self.assignments.insert(term.literal, lit);
57
58        // Issue 3: No way to convert LitId -> TermIdx
59        #[cfg(any())]
60        self.lit_stack
61            .raw
62            .retain(|&l| stack.is_alive(self.literals[l].frame));
63        #[cfg(any())]
64        self.lit_stack.push_by(lit, |a, b| {
65            let a = &self.literals[*a];
66            let b = &self.literals[*b];
67            a.term.literal.cmp(&b.term.literal)
68        })?;
69        // match &mut self.lit_stack {
70        //     Some(ls) => {
71        //         while ls
72        //             .stack
73        //             .last()
74        //             .is_some_and(|&l| !stack.is_alive(self.literals[l].frame))
75        //         {
76        //             ls.stack.pop();
77        //         }
78        //         ls.stack.try_reserve(1)?;
79        //         ls.stack.push(lit);
80        //     }
81        //     None if !stack.is_global(frame) => {
82        //         let mut ls = LitStack {
83        //             global_lits: self.literals.len() as u32 - 1,
84        //             stack: Vec::new(),
85        //         };
86        //         ls.stack.try_reserve(1)?;
87        //         ls.stack.push(lit);
88        //         self.lit_stack = Some(ls);
89        //     }
90        //     None => {}
91        // }
92        Ok(lit)
93    }
94
95    pub fn get_assign(&self, term: TermIdx, stack: &Stack) -> Option<LitIdx> {
96        let lit = *self.assignments.get(&term)?;
97        stack.is_alive(self.literals[lit].frame).then_some(lit)
98    }
99
100    /// Must only be called after a `new_literal` call. If not called the
101    /// justification of the new literal will be an error.
102    pub fn justification(
103        &mut self,
104        lit: LitIdx,
105        kind: JustificationKind,
106        #[allow(unused)] blamed: impl Iterator<Item = Result<(Option<NonZeroU32>, bool)>>,
107    ) -> Result<&Justification> {
108        #[cfg(any())]
109        let mut literals = Vec::new();
110        #[cfg(any())]
111        let negated = matches!(kind, JustificationKind::Propagate);
112        #[cfg(any())]
113        for lit in blamed {
114            let lit = self.literal_to_term(lit?, negated);
115            literals.try_reserve(1)?;
116            literals.push(lit?);
117        }
118        let justification = Justification {
119            kind,
120            #[cfg(any())]
121            blamed: literals.into(),
122        };
123        let j = &mut self.literals[lit].justification;
124        *j = justification;
125        Ok(j)
126    }
127
128    // Issue 3: No way to convert LitId -> TermIdx
129    #[cfg(any())]
130    /// Must only be called after a `new_literal` call.
131    pub fn literal_to_term(
132        &self,
133        (lit, value): (Option<NonZeroU32>, bool),
134        negated: bool,
135    ) -> Result<LitIdx> {
136        let lit = self.literal_to_term_inner(lit);
137        if (self.literals[lit].term.value == value) == negated {
138            return Err(Error::BoolLiteral);
139        }
140        if negated {
141            debug_assert_eq!(!self.literals[lit].term.value, value);
142        } else {
143            debug_assert_eq!(self.literals[lit].term.value, value);
144        }
145        Ok(lit)
146    }
147    #[cfg(any())]
148    fn literal_to_term_inner(&self, lit: Option<NonZeroU32>) -> LitIdx {
149        let lit = lit.unwrap().get() - 1; // TODO: handle `true` and `false` literals
150        self.lit_stack[lit as usize]
151        // let lit = lit.unwrap().get() - 1; // TODO: handle `true` and `false` literals
152        // if let Some(ls) = &self.lit_stack {
153        //     if let Some(lidx) = lit.checked_sub(ls.global_lits) {
154        //         return ls.stack[lidx as usize];
155        //     }
156        // }
157        // LitIdx::from(lit as usize)
158    }
159
160    pub(super) fn curr_to_root_unique(&self) -> bool {
161        let mut assigns = crate::FxHashSet::default();
162        self.cdcl
163            .curr_to_root()
164            .flat_map(|c| self.cdcl[c].get_assignments())
165            .all(|lit| assigns.insert(self[lit].term.literal))
166    }
167}
168
169#[cfg_attr(feature = "mem_dbg", derive(MemSize, MemDbg))]
170#[derive(Debug)]
171pub struct CdclTree {
172    incremental_mode: bool,
173    cdcl: TiVec<CdclIdx, Cdcl>,
174    /// The cut from the last conflict, only set between a `[conflict]` line and
175    /// a `[pop]`. The latter will backtrack and insert this into the above vec.
176    conflict: Option<Box<[Assignment]>>,
177}
178
179impl Default for CdclTree {
180    fn default() -> Self {
181        let mut cdcl = TiVec::default();
182        let zero = cdcl.push_and_get_key(Cdcl::root(Stack::ZERO_FRAME));
183        debug_assert_eq!(zero, CdclIdx::ZERO);
184        Self {
185            incremental_mode: false,
186            cdcl,
187            conflict: None,
188        }
189    }
190}
191
192impl CdclTree {
193    pub fn new_decision(&mut self, lit: LitIdx, stack: &Stack) -> Result<CdclIdx> {
194        debug_assert!(self.conflict.is_none());
195        self.check_frame_decision(stack)?;
196
197        let cdcl = Cdcl::new_decision(lit, stack.active_frame());
198        self.cdcl.raw.try_reserve(1)?;
199        Ok(self.cdcl.push_and_get_key(cdcl))
200    }
201
202    fn check_frame_decision(&mut self, stack: &Stack) -> Result<()> {
203        if stack.is_alive(self.cdcl.last().unwrap().frame) {
204            return Ok(());
205        }
206        // Just before making a decision z3 will always push a new frame.
207        // Take the one just before the push here. Note that this may create an
208        // empty with `ZERO_FRAME`.
209        let prev = stack.pre_active_frame();
210        self.insert_empty_frame(stack, prev)?;
211        Ok(())
212    }
213
214    pub fn new_conflict(&mut self, cut: Box<[Assignment]>, frame: StackIdx) {
215        debug_assert!(self.conflict.is_none());
216        debug_assert!(self.cdcl.last().is_some_and(|cdcl| cdcl.frame == frame));
217        self.cdcl.last_mut().unwrap().conflicts = true;
218        self.conflict = Some(cut);
219    }
220
221    pub fn backtrack(&mut self, stack: &Stack) -> Result<CdclIdx> {
222        let backtrack = self.last_active(stack);
223        debug_assert_ne!(backtrack, self.cdcl.last_key().unwrap());
224        // Not always true:
225        // debug_assert_eq!(self[backtrack].frame, stack.active_frame());
226
227        let cut = self.conflict.take().ok_or(Error::NoConflict)?;
228        let conflict = Conflict { cut, backtrack };
229        let cdcl = Cdcl::new_conflict(conflict, stack.active_frame());
230        self.cdcl.raw.try_reserve(1)?;
231        Ok(self.cdcl.push_and_get_key(cdcl))
232    }
233
234    pub fn new_propagate(&mut self, lit: LitIdx, stack: &Stack) -> Result<()> {
235        debug_assert!(self.conflict.is_none());
236        let mut last = self.cdcl.last_mut().unwrap();
237        let frame = stack.active_frame();
238        if last.frame != frame {
239            last = self.insert_empty_frame(stack, frame)?;
240        }
241        last.propagates.try_reserve(1)?;
242        last.propagates.push(lit);
243        Ok(())
244    }
245
246    fn insert_empty_frame(&mut self, stack: &Stack, at: StackIdx) -> Result<&mut Cdcl> {
247        let parent = self.last_active(stack);
248        let empty = Cdcl::new_empty(parent, at);
249        self.cdcl.raw.try_reserve(1)?;
250        let new = self.cdcl.push_and_get_key(empty);
251        Ok(&mut self.cdcl[new])
252    }
253
254    pub fn begin_check(&mut self, incremental_mode: bool, stack: &Stack) -> Result<()> {
255        debug_assert!(self.conflict.is_none());
256        debug_assert!(!self.incremental_mode || incremental_mode);
257        self.incremental_mode = incremental_mode;
258        let parent = incremental_mode.then(|| self.last_active(stack));
259        let check = Cdcl::begin_check(parent, stack.active_frame());
260        self.cdcl.raw.try_reserve(1)?;
261        self.cdcl.push_and_get_key(check);
262        Ok(())
263    }
264
265    pub fn has_conflict(&self) -> bool {
266        self.conflict.is_some()
267    }
268
269    pub fn cdcls(&self) -> &TiSlice<CdclIdx, Cdcl> {
270        &self.cdcl
271    }
272
273    pub fn backlink(&self, cidx: CdclIdx) -> CdclBacklink {
274        self[cidx].backlink(cidx)
275    }
276
277    fn last_active(&self, stack: &Stack) -> CdclIdx {
278        let active = |i: &CdclIdx| stack.is_alive(self[*i].frame);
279        self.curr_to_root().find(active).unwrap()
280    }
281
282    fn curr_to_root(&self) -> impl Iterator<Item = CdclIdx> + '_ {
283        self.to_root(self.cdcl.last_key().unwrap())
284    }
285
286    fn to_root(&self, from: CdclIdx) -> impl Iterator<Item = CdclIdx> + '_ {
287        let mut curr = Some(from);
288        core::iter::from_fn(move || {
289            let cdcl = curr?;
290            let backlink = self.backlink(cdcl);
291            curr = backlink.to_root();
292            Some(cdcl)
293        })
294    }
295
296    /// Returns an iterator over all of the dead branches explored by the solver.
297    pub fn dead_branches(&self) -> impl Iterator<Item = DeadBranch<'_>> + '_ {
298        self.cdcl
299            .iter_enumerated()
300            .filter_map(|(cidx, cdcl)| match &cdcl.kind {
301                CdclKind::Conflict(conflict) => Some(DeadBranch { cidx, conflict }),
302                _ => None,
303            })
304    }
305
306    /// Returns an iterator over the nodes in a dead branch.
307    pub fn dead_branch(&self, db: DeadBranch) -> impl Iterator<Item = CdclIdx> + '_ {
308        let conflict_leaf = usize::from(db.cidx).checked_sub(1).unwrap();
309        let conflict_leaf = CdclIdx::from(conflict_leaf);
310        let common_ancestor = db.conflict.backtrack;
311        debug_assert!(self.to_root(conflict_leaf).any(|c| c == common_ancestor));
312        self.to_root(conflict_leaf)
313            .take_while(move |&c| c != common_ancestor)
314    }
315}
316
317#[derive(Clone, Copy)]
318pub struct DeadBranch<'a> {
319    /// The index of the cut node.
320    pub cidx: CdclIdx,
321    pub conflict: &'a Conflict,
322}
323
324impl std::ops::Index<LitIdx> for Literals {
325    type Output = Literal;
326    fn index(&self, idx: LitIdx) -> &Self::Output {
327        &self.literals[idx]
328    }
329}
330
331impl std::ops::Index<CdclIdx> for CdclTree {
332    type Output = Cdcl;
333    fn index(&self, idx: CdclIdx) -> &Self::Output {
334        &self.cdcl[idx]
335    }
336}