veripb/
database.rs

1use std::{cell::RefCell, ops::Range, rc::Rc};
2
3use ahash::AHashSet;
4use veripb_formula::prelude::*;
5use veripb_propagator::propagation_engine::PropagationEngine;
6
7use crate::{
8    context::{Context, CORE, DERIVED},
9    error::CheckingError,
10    occurrence_list::OccurrenceList,
11};
12
13/// Remove the constraint from the core propagator and add it to the derived propagator.
14#[inline]
15pub fn move_to_derived_propagator(
16    context: &mut Context,
17    constraint: &Rc<DBConstraint>,
18) -> Result<(), CheckingError> {
19    context.propagation_engine.detach(
20        CORE,
21        constraint,
22        context.propagation_engine.only_core_trail,
23    )?;
24    context.propagation_engine.attach(DERIVED, constraint)?;
25    Ok(())
26}
27
28/// Storage for constraints.
29#[derive(Debug, Default)]
30pub struct Database {
31    /// Constraint ID indexed vector to directly get a constraint from its index.
32    pub entries: Vec<Option<Rc<DBConstraint>>>,
33    /// Hash map of unique constraints identified by the constraint of the entry.
34    pub unique_constraints: AHashSet<Rc<DBConstraint>>,
35    /// An occurrence list mapping from literals to constraints. This list is kept in sync with the `unique_constraints`.
36    occurrences: OccurrenceList,
37    /// Only constraint IDs that are less than `next_unique_index_id` have been added to `unique_constraints`.
38    next_unique_index_id: usize,
39    /// Only constraint IDs that are less than `next_occurrences_index_id` have been added to `occurrences`.
40    next_occurrences_index_id: usize,
41    /// Only constraint IDs that are less than `next_propagation_index_id` have been added to the propagation engine.
42    next_propagation_index_id: usize,
43}
44
45impl Database {
46    /// Create a new empty database with dummy entry at position 0.
47    pub fn new() -> Self {
48        Database {
49            entries: vec![None],
50            ..Default::default()
51        }
52    }
53
54    /// Initialize a database from the a `Formula`.
55    pub fn from_formula(constraints: Vec<PBConstraintEnum>) -> Self {
56        let mut entries = Vec::with_capacity(constraints.len() + 1);
57        entries.push(None);
58        let mut unique_constraints = AHashSet::with_capacity(constraints.len());
59        let mut occurrences = OccurrenceList::default();
60        for constraint in constraints {
61            let db_constraint = Rc::new(DBConstraint {
62                header: RefCell::new(DBHeader::default()),
63                constraint,
64            });
65            if unique_constraints.insert(Rc::clone(&db_constraint)) {
66                db_constraint.add_id(entries.len(), true);
67                occurrences.add(&db_constraint);
68                entries.push(Some(db_constraint));
69            } else {
70                // Constraint already in `unique_constraints`.
71                let entry = unique_constraints.get(&db_constraint).unwrap();
72                entry.add_id(entries.len(), true);
73                entries.push(Some(Rc::clone(entry)));
74            }
75        }
76        let first_non_indexed_constraint_id = entries.len();
77
78        Database {
79            entries,
80            unique_constraints,
81            occurrences,
82            next_unique_index_id: first_non_indexed_constraint_id,
83            next_occurrences_index_id: first_non_indexed_constraint_id,
84            next_propagation_index_id: first_non_indexed_constraint_id,
85        }
86    }
87
88    /// Lazily adds a constraint to the database.
89    ///
90    /// If the constraint is already in the database, then it will be added lazily again.
91    #[inline]
92    pub fn add_constraint(&mut self, constraint: Rc<DBConstraint>, add_to_core: bool) {
93        constraint.add_id(self.len(), add_to_core);
94        self.entries.push(Some(constraint));
95    }
96
97    /// Delete a constraint from the database by their ID.
98    ///
99    /// Also deletes the constraint from indexing structures if it was added to them.
100    #[inline]
101    pub fn delete_constraint(
102        &mut self,
103        context: &mut Context,
104        constraint_id: usize,
105    ) -> Result<(), CheckingError> {
106        match self.entries.get_mut(constraint_id) {
107            None => Err(CheckingError::deletion(
108                constraint_id,
109                &format!(
110                    "there are only {} constraints in the database",
111                    self.entries.len()
112                ),
113            )),
114            Some(None) => Err(CheckingError::deletion(
115                constraint_id,
116                "the constraint has already been deleted",
117            )),
118            Some(entry) => {
119                let db_constraint = entry.as_mut().unwrap();
120                let was_core = db_constraint.is_core_constraint();
121                db_constraint.remove_id(constraint_id);
122                if db_constraint.all_constraint_ids_empty() {
123                    if constraint_id < self.next_unique_index_id {
124                        self.unique_constraints.remove(db_constraint.as_ref());
125                    }
126                    if db_constraint.header.borrow().is_in_occurrences {
127                        self.occurrences.remove(db_constraint);
128                    }
129                    context.propagation_engine.detach(
130                        if was_core { CORE } else { DERIVED },
131                        db_constraint,
132                        true,
133                    )?;
134                } else if was_core && !db_constraint.is_core_constraint() {
135                    move_to_derived_propagator(context, db_constraint)?;
136                }
137                *entry = None;
138                Ok(())
139            }
140        }
141    }
142
143    /// Indexes all constraints that are not yet indexed in `unique_constraints`.
144    ///
145    /// In case a duplicate constraint is detected, then the duplicate constraint is merged into the first occurrence.
146    /// In particular, this also removes duplicate constraints from `occurrences` and the propagation engine.
147    ///
148    /// Must be called before:
149    /// - using `Database::unique_constraints` directly
150    /// - calling `Database::lookup()`
151    /// - calling `Database::get_proofgoals()`
152    pub fn update_unique_index(
153        &mut self,
154        prop_engine: &mut PropagationEngine,
155    ) -> Result<(), CheckingError> {
156        for constraint_id in self.next_unique_index_id..self.len() {
157            // First check that the constraint ID is not deleted.
158            if let Some(Some(constraint)) = self.entries.get(constraint_id) {
159                let add_to_core = constraint.is_core_constraint();
160                if !self.unique_constraints.insert(constraint.clone()) {
161                    // Merge this constraint with the existing constraint.
162                    let entry = self.unique_constraints.get(constraint).unwrap();
163                    entry.add_id(constraint_id, add_to_core);
164                    if let Some(out_id) = constraint.get_out_id(constraint_id) {
165                        entry.set_out_id(constraint_id, out_id);
166                    }
167                    // Remove from other indexing structures if the duplicate constraint was added.
168                    if constraint_id < self.next_occurrences_index_id {
169                        self.occurrences.remove(constraint);
170                    }
171                    if constraint_id < self.next_propagation_index_id {
172                        prop_engine.detach(
173                            if add_to_core { CORE } else { DERIVED },
174                            constraint,
175                            true,
176                        )?;
177                    }
178                    // Overwrite the duplicate constraint such that they both IDs point to the same constraint.
179                    self.entries[constraint_id] = Some(Rc::clone(entry));
180                }
181            }
182        }
183        self.next_unique_index_id = self.len();
184        Ok(())
185    }
186
187    /// Indexes all constraints that are not yet indexed in `occurrences`. Will be automatically called before occurrences are needed.
188    pub fn update_occurrences_index(&mut self) {
189        for constraint_id in self.next_occurrences_index_id..self.len() {
190            // First check that the constraint ID is not deleted.
191            if let Some(Some(constraint)) = self.entries.get(constraint_id) {
192                if !constraint.header.borrow().is_in_occurrences {
193                    self.occurrences.add(constraint);
194                }
195            }
196        }
197        self.next_occurrences_index_id = self.len();
198    }
199
200    /// Indexes all constraints that are not yet added to the propagation engine.
201    ///
202    /// Must be called before:
203    /// - calling `PropagationEngine::reverse_unit_propagation_check()`
204    /// - calling `PropagationEngine::propagate_solution()`
205    pub fn update_propagation_index(
206        &mut self,
207        prop_engine: &mut PropagationEngine,
208    ) -> Result<(), CheckingError> {
209        for constraint_id in self.next_propagation_index_id..self.len() {
210            // First check that the constraint ID is not deleted.
211            if let Some(Some(constraint)) = self.entries.get(constraint_id) {
212                if constraint.header.borrow().propagator_id.is_none() {
213                    let add_to_core = constraint.is_core_constraint();
214                    prop_engine.attach(if add_to_core { CORE } else { DERIVED }, constraint)?;
215                }
216            }
217        }
218        self.next_propagation_index_id = self.len();
219        Ok(())
220    }
221
222    /// Check if `constraint` is contained in the `Database` and return a reference to the constraint in the database if it exists.
223    #[inline]
224    pub fn lookup(&self, constraint: &Rc<DBConstraint>) -> Option<&Rc<DBConstraint>> {
225        debug_assert!(self.len() == self.next_unique_index_id);
226        self.unique_constraints.get(constraint)
227    }
228
229    /// Check if the database contain a contradiction.
230    #[inline]
231    pub fn contains_contradiction(&self) -> Option<usize> {
232        for constraint in self.unique_constraints.iter() {
233            if constraint.is_contradicting() {
234                return Some(constraint.get_some_id());
235            }
236        }
237        for constraint_id in self.next_unique_index_id..self.len() {
238            if let Some(Some(constraint)) = self.entries.get(constraint_id) {
239                if constraint.is_contradicting() {
240                    return Some(constraint_id);
241                }
242            }
243        }
244        None
245    }
246
247    #[inline]
248    pub fn normalize_id(&self, index: isize) -> isize {
249        if index < 0 {
250            self.len() as isize + index
251        } else {
252            index
253        }
254    }
255
256    #[inline]
257    pub fn get_entry(&self, index: isize) -> Result<&Rc<DBConstraint>, CheckingError> {
258        let entry = if index < 0 {
259            match self
260                .entries
261                .get(((self.entries.len() as isize) + index) as usize)
262            {
263                Some(entry) => entry,
264                None => {
265                    return Err(CheckingError::out_of_bounds(
266                        index,
267                        -(self.entries.len() as isize) + 1,
268                        (self.entries.len() as isize) - 1,
269                    ))
270                }
271            }
272        } else {
273            match self.entries.get(index as usize) {
274                Some(entry) => entry,
275                None => {
276                    return Err(CheckingError::out_of_bounds(
277                        index,
278                        -(self.entries.len() as isize),
279                        (self.entries.len() as isize) - 1,
280                    ))
281                }
282            }
283        };
284
285        match entry {
286            Some(constraint) => Ok(constraint),
287            None => Err(CheckingError::access_deleted(index)),
288        }
289    }
290
291    #[inline]
292    pub fn get_entry_usize(&self, index: usize) -> Result<&Rc<DBConstraint>, CheckingError> {
293        let entry = match self.entries.get(index) {
294            Some(entry) => entry,
295            None => {
296                return Err(CheckingError::out_of_bounds(
297                    index as isize,
298                    -(self.entries.len() as isize),
299                    (self.entries.len() as isize) - 1,
300                ))
301            }
302        };
303
304        match entry {
305            Some(constraint) => Ok(constraint),
306            None => Err(CheckingError::access_deleted(index as isize)),
307        }
308    }
309
310    #[inline]
311    pub fn get_entry_optionally_deleted_usize(
312        &self,
313        index: usize,
314    ) -> Result<Option<&Rc<DBConstraint>>, CheckingError> {
315        let entry = match self.entries.get(index) {
316            Some(entry) => entry,
317            None => {
318                return Err(CheckingError::out_of_bounds(
319                    index as isize,
320                    -(self.entries.len() as isize),
321                    (self.entries.len() as isize) - 1,
322                ))
323            }
324        };
325
326        match entry {
327            Some(constraint) => Ok(Some(constraint)),
328            None => Ok(None),
329        }
330    }
331
332    /// Check if the constraint at the `id` is not deleted.
333    #[inline]
334    pub fn is_undeleted(&self, index: usize) -> Result<bool, CheckingError> {
335        if let Some(entry) = self.entries.get(index) {
336            return Ok(entry.is_some());
337        }
338        Err(CheckingError::out_of_bounds(
339            index as isize,
340            -(self.entries.len() as isize),
341            (self.entries.len() as isize) - 1,
342        ))
343    }
344
345    /// Move the constraint ID to the core set and add it to the core propagator if it is not already in the core.
346    #[inline]
347    pub fn move_to_core(
348        &self,
349        prop_engine: &mut PropagationEngine,
350        index: usize,
351    ) -> Result<(), CheckingError> {
352        // Find the constraint and get its header.
353        let constraint = self.get_entry_usize(index)?;
354
355        // If the constraint was not in the core propagator before, it is moved there.
356        if !constraint.is_core_constraint() && constraint.header.borrow().propagator_id.is_some() {
357            prop_engine.detach(DERIVED, constraint, false)?;
358            prop_engine.attach(CORE, constraint)?;
359        }
360
361        // Remove ID from derived set and add it to the core set of IDs.
362        constraint.move_id_to_core(index);
363
364        Ok(())
365    }
366
367    /// Move all constraints to the core set and add them to the core propagator.
368    pub fn move_to_core_all(
369        &self,
370        prop_engine: &mut PropagationEngine,
371    ) -> Result<(), CheckingError> {
372        // Move all constraints in database to core set.
373        for constraint in self.unique_constraints.iter() {
374            let header = &mut *constraint.header.borrow_mut();
375            header.core_ids.append(&mut header.derived_ids);
376        }
377        for constraint_id in self.next_unique_index_id..self.len() {
378            if let Some(Some(constraint)) = self.entries.get(constraint_id) {
379                let header = &mut *constraint.header.borrow_mut();
380                header.core_ids.append(&mut header.derived_ids);
381            }
382        }
383
384        // Move all constraints in the derived propagation set to the core propagation set.
385        for constraint in prop_engine.detach_all(DERIVED) {
386            prop_engine.attach(CORE, &constraint)?;
387        }
388
389        Ok(())
390    }
391
392    /// Get the variables actually used by the constraints in the database.
393    #[inline]
394    pub fn get_used_vars(&mut self) -> Vec<VarIdx> {
395        self.update_occurrences_index();
396        self.occurrences.get_used_vars()
397    }
398
399    /// Add constraints to `unique_substituted_constraints` that contain the literal `lit`.
400    ///
401    /// This function only adds constraints that are not obviously implied by the database already.
402    #[inline]
403    fn add_non_obvious_proofgoals(
404        &self,
405        substitution: &Substitution,
406        lit: Lit,
407        unique_substituted_constraints: &mut AHashSet<Rc<DBConstraint>>,
408        add_derived_goals: bool,
409        exclude_database_autoproving: bool,
410        only_core_subproof: bool,
411    ) {
412        if let Some(constraints) = self.occurrences.get_constraints_for_lit(lit) {
413            for constraint in constraints {
414                if add_derived_goals || constraint.is_core_constraint() {
415                    let substituted_constraint = Rc::new(constraint.substitute(substitution));
416                    // The following conditions have to consider a proofgoal non-obvious:
417                    // 1. The proofgoal is not trivial (... >= 1)
418                    // 2. The original constraint does not imply the proofgoal.
419                    // 3. No other database constraint is syntactially equivalent to the proofgoal. This is ignored, if:
420                    //     1. `exclude_database_autoproving` is true.
421                    //     2. We can only use core constraints for the subrpoof and the database constraint is not in the core set.
422                    if !substituted_constraint.is_trivial()
423                        && !constraint.implies(&substituted_constraint)
424                        && (exclude_database_autoproving
425                            || self
426                                .lookup(&substituted_constraint)
427                                .is_none_or(|c| only_core_subproof && !c.is_core_constraint()))
428                    {
429                        // Copy ids from the original constraint.
430                        substituted_constraint.copy_ids(constraint);
431                        unique_substituted_constraints.insert(substituted_constraint);
432                    }
433                }
434            }
435        }
436    }
437
438    /// Get the proofgoals from the formula for strengthening rules with respect to the substitution.
439    ///
440    /// Proofgoals that are trivial, implied by the original constraint, or that can be found in the database are not added as proofgoals.
441    #[inline]
442    pub fn get_proofgoals(
443        &mut self,
444        substitution: &Substitution,
445        add_derived_goals: bool,
446        exclude_database_autoproving: bool,
447        only_core_subproof: bool,
448    ) -> AHashSet<Rc<DBConstraint>> {
449        debug_assert_eq!(self.len(), self.next_unique_index_id);
450
451        self.update_occurrences_index();
452        let mut unique_substituted_constraints = AHashSet::new();
453        for &var_idx in substitution.support.iter() {
454            // Constraints where the literal is mapped to true do not need to be considered as a proofgoal.
455            match substitution.get(var_idx).unwrap() {
456                SubstitutionValue::TRUE => {
457                    self.add_non_obvious_proofgoals(
458                        substitution,
459                        Lit::from_var(var_idx, true),
460                        &mut unique_substituted_constraints,
461                        add_derived_goals,
462                        exclude_database_autoproving,
463                        only_core_subproof,
464                    );
465                }
466                SubstitutionValue::FALSE => {
467                    self.add_non_obvious_proofgoals(
468                        substitution,
469                        Lit::from_var(var_idx, false),
470                        &mut unique_substituted_constraints,
471                        add_derived_goals,
472                        exclude_database_autoproving,
473                        only_core_subproof,
474                    );
475                }
476                _ => {
477                    self.add_non_obvious_proofgoals(
478                        substitution,
479                        Lit::from_var(var_idx, false),
480                        &mut unique_substituted_constraints,
481                        add_derived_goals,
482                        exclude_database_autoproving,
483                        only_core_subproof,
484                    );
485                    self.add_non_obvious_proofgoals(
486                        substitution,
487                        Lit::from_var(var_idx, true),
488                        &mut unique_substituted_constraints,
489                        add_derived_goals,
490                        exclude_database_autoproving,
491                        only_core_subproof,
492                    );
493                }
494            }
495        }
496
497        unique_substituted_constraints
498    }
499
500    #[inline]
501    pub fn get_undeleted(
502        &self,
503        range: Range<usize>,
504    ) -> impl '_ + Iterator<Item = Result<usize, CheckingError>> {
505        range
506            .into_iter()
507            .filter_map(|id| match self.is_undeleted(id) {
508                Ok(true) => Some(Ok(id)),
509                Ok(false) => None,
510                Err(err) => Some(Err(err)),
511            })
512    }
513
514    #[inline]
515    pub fn get_undeleted_non_unique_indexed(&self) -> impl '_ + Iterator<Item = &Rc<DBConstraint>> {
516        self.entries[self.next_unique_index_id..self.len()]
517            .iter()
518            .flatten()
519    }
520
521    /// The length of the database is the number of entries including deleted constraint and duplicates.
522    #[inline]
523    pub fn len(&self) -> usize {
524        self.entries.len()
525    }
526
527    #[inline]
528    pub fn is_empty(&self) -> bool {
529        self.entries.len() == 0
530    }
531
532    /// The size of the database is the number of non-deleted (`Some(constraint)`) entries in the database.
533    #[inline]
534    pub fn size(&self) -> usize {
535        self.entries.iter().flatten().count()
536    }
537}