Skip to main content

oxiz_sat/
recursive_minimization.rs

1//! Recursive Clause Minimization
2//!
3//! This module implements recursive minimization of learned clauses.
4//! The basic idea is to recursively check if a literal in a learned clause
5//! can be removed by examining its reason clause.
6//!
7//! Reference: "Minimizing Learned Clauses" by Niklas Sörensson and Armin Biere
8
9use crate::clause::ClauseDatabase;
10use crate::literal::Lit;
11#[allow(unused_imports)]
12use crate::prelude::*;
13use crate::trail::Reason;
14use smallvec::SmallVec;
15
16/// Literal redundancy status for recursive minimization
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18enum LitStatus {
19    /// Literal is known to be redundant
20    Redundant,
21    /// Literal is known to be non-redundant (essential)
22    Essential,
23    /// Status is unknown (not yet checked)
24    Unknown,
25}
26
27/// Statistics for recursive minimization
28#[derive(Debug, Default, Clone)]
29pub struct RecursiveMinStats {
30    /// Number of clauses minimized
31    pub clauses_minimized: u64,
32    /// Number of literals removed
33    pub literals_removed: u64,
34    /// Number of recursive checks performed
35    pub recursive_checks: u64,
36}
37
38/// Recursive clause minimization manager
39pub struct RecursiveMinimizer {
40    /// Status of each literal during minimization
41    status: Vec<LitStatus>,
42    /// Stack for depth-first traversal
43    stack: Vec<Lit>,
44    /// Statistics
45    stats: RecursiveMinStats,
46}
47
48impl RecursiveMinimizer {
49    /// Create a new recursive minimizer
50    pub fn new(num_vars: usize) -> Self {
51        Self {
52            status: vec![LitStatus::Unknown; num_vars * 2],
53            stack: Vec::new(),
54            stats: RecursiveMinStats::default(),
55        }
56    }
57
58    /// Resize the minimizer for a new number of variables
59    pub fn resize(&mut self, num_vars: usize) {
60        self.status.resize(num_vars * 2, LitStatus::Unknown);
61    }
62
63    /// Get statistics
64    #[must_use]
65    pub fn stats(&self) -> &RecursiveMinStats {
66        &self.stats
67    }
68
69    /// Minimize a learned clause using recursive minimization
70    ///
71    /// Returns the minimized clause
72    pub fn minimize(
73        &mut self,
74        clause: &[Lit],
75        clauses: &ClauseDatabase,
76        reasons: &[Reason],
77        level: &[u32],
78        current_level: u32,
79    ) -> SmallVec<[Lit; 8]> {
80        // Reset status for all literals
81        for status in &mut self.status {
82            *status = LitStatus::Unknown;
83        }
84
85        // Mark all literals in the clause that are at the current decision level
86        let mut abstract_level = 0u32;
87        for &lit in clause {
88            let var = lit.var();
89            if level[var.index()] == current_level {
90                self.status[lit.index()] = LitStatus::Essential;
91                abstract_level |= 1 << (level[var.index()] & 31);
92            }
93        }
94
95        // Try to remove each literal
96        let mut result = SmallVec::new();
97        let mut removed = 0;
98
99        for &lit in clause {
100            let var = lit.var();
101
102            // Skip if at decision level 0 (always keep)
103            if level[var.index()] == 0 {
104                result.push(lit);
105                continue;
106            }
107
108            // Skip if already marked as essential
109            if self.status[lit.index()] == LitStatus::Essential {
110                result.push(lit);
111                continue;
112            }
113
114            // Check if this literal is redundant
115            if self.is_redundant(lit, clauses, reasons, level, abstract_level, current_level) {
116                self.status[lit.index()] = LitStatus::Redundant;
117                removed += 1;
118                self.stats.literals_removed += 1;
119            } else {
120                self.status[lit.index()] = LitStatus::Essential;
121                result.push(lit);
122            }
123        }
124
125        if removed > 0 {
126            self.stats.clauses_minimized += 1;
127        }
128
129        result
130    }
131
132    /// Check if a literal is redundant using recursive analysis
133    fn is_redundant(
134        &mut self,
135        lit: Lit,
136        clauses: &ClauseDatabase,
137        reasons: &[Reason],
138        level: &[u32],
139        abstract_level: u32,
140        _current_level: u32,
141    ) -> bool {
142        self.stats.recursive_checks += 1;
143
144        self.stack.clear();
145        self.stack.push(lit);
146
147        while let Some(current_lit) = self.stack.pop() {
148            let var = current_lit.var();
149
150            // Check cached status
151            match self.status[current_lit.index()] {
152                LitStatus::Redundant => continue,
153                LitStatus::Essential => return false,
154                LitStatus::Unknown => {}
155            }
156
157            // Get the reason for this literal
158            let reason = &reasons[var.index()];
159
160            match reason {
161                Reason::Decision => {
162                    // This is a decision literal, not redundant
163                    return false;
164                }
165                Reason::Theory => {
166                    // Theory propagation - treat as non-redundant for now
167                    return false;
168                }
169                Reason::Propagation(clause_id) => {
170                    // Check all literals in the reason clause
171                    let Some(clause) = clauses.get(*clause_id) else {
172                        return false;
173                    };
174
175                    for &reason_lit in &clause.lits {
176                        // Skip the implied literal itself
177                        if reason_lit.var() == var {
178                            continue;
179                        }
180
181                        let reason_var = reason_lit.var();
182                        let reason_level = level[reason_var.index()];
183
184                        if reason_level == 0 {
185                            // Unit literal, continue
186                            continue;
187                        }
188
189                        // Check if the level is compatible
190                        if (abstract_level & (1 << (reason_level & 31))) == 0 {
191                            return false;
192                        }
193
194                        // Add to stack for recursive check
195                        if self.status[reason_lit.index()] == LitStatus::Unknown {
196                            self.stack.push(reason_lit);
197                        }
198                    }
199                }
200            }
201
202            // Mark this literal as checked
203            self.status[current_lit.index()] = LitStatus::Redundant;
204        }
205
206        true
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213    use crate::clause::ClauseDatabase;
214    use crate::literal::{Lit, Var};
215    use crate::trail::Reason;
216
217    #[test]
218    fn test_recursive_minimizer_creation() {
219        let minimizer = RecursiveMinimizer::new(10);
220        assert_eq!(minimizer.status.len(), 20); // 2 * num_vars
221    }
222
223    #[test]
224    fn test_recursive_minimizer_resize() {
225        let mut minimizer = RecursiveMinimizer::new(10);
226        minimizer.resize(20);
227        assert_eq!(minimizer.status.len(), 40);
228    }
229
230    #[test]
231    fn test_recursive_minimizer_stats() {
232        let minimizer = RecursiveMinimizer::new(10);
233        let stats = minimizer.stats();
234        assert_eq!(stats.clauses_minimized, 0);
235        assert_eq!(stats.literals_removed, 0);
236        assert_eq!(stats.recursive_checks, 0);
237    }
238
239    #[test]
240    fn test_minimize_empty_clause() {
241        let mut minimizer = RecursiveMinimizer::new(10);
242        let db = ClauseDatabase::new();
243        let reasons = vec![Reason::Decision; 10];
244        let level = vec![0; 10];
245
246        let clause: Vec<Lit> = vec![];
247        let result = minimizer.minimize(&clause, &db, &reasons, &level, 0);
248        assert_eq!(result.len(), 0);
249    }
250
251    #[test]
252    fn test_minimize_unit_clause() {
253        let mut minimizer = RecursiveMinimizer::new(10);
254        let db = ClauseDatabase::new();
255        let reasons = vec![Reason::Decision; 10];
256        let level = vec![0; 10];
257
258        let clause = vec![Lit::pos(Var::new(0))];
259        let result = minimizer.minimize(&clause, &db, &reasons, &level, 0);
260        assert_eq!(result.len(), 1);
261    }
262}