Skip to main content

oxiz_solver/conflict/
recursive_minimizer.rs

1//! Recursive Conflict Clause Minimization
2#![allow(missing_docs, clippy::ptr_arg)] // Under development
3//!
4//! This module implements advanced conflict clause minimization techniques:
5//! - Recursive resolution-based minimization
6//! - Binary resolution minimization
7//! - Stamp-based minimization (seen/poison marking)
8//! - Self-subsuming resolution detection
9
10use rustc_hash::{FxHashMap, FxHashSet};
11
12/// Literal representation
13pub type Lit = i32;
14
15/// Clause identifier
16pub type ClauseId = usize;
17
18/// Level (decision level)
19pub type Level = usize;
20
21/// Reason for a literal assignment
22#[derive(Debug, Clone)]
23pub enum Reason {
24    /// Decision literal (no reason)
25    Decision,
26    /// Unit propagation from clause
27    Clause(ClauseId),
28    /// Binary clause
29    Binary(Lit),
30    /// Theory propagation
31    Theory(Vec<Lit>),
32}
33
34/// Literal information for minimization
35#[derive(Debug, Clone)]
36pub struct LitInfo {
37    /// Assignment level
38    pub level: Level,
39    /// Reason for assignment
40    pub reason: Reason,
41    /// Whether literal is marked as seen
42    pub seen: bool,
43    /// Whether literal is poisoned (cannot be removed)
44    pub poisoned: bool,
45}
46
47/// Statistics for recursive minimization
48#[derive(Debug, Clone, Default)]
49pub struct RecursiveMinStats {
50    pub clauses_minimized: u64,
51    pub literals_removed: u64,
52    pub recursive_calls: u64,
53    pub binary_minimizations: u64,
54    pub stamp_minimizations: u64,
55    pub self_subsuming_resolutions: u64,
56}
57
58/// Configuration for recursive minimization
59#[derive(Debug, Clone)]
60pub struct RecursiveMinConfig {
61    /// Enable recursive resolution minimization
62    pub enable_recursive: bool,
63    /// Enable binary resolution minimization
64    pub enable_binary: bool,
65    /// Enable stamp-based minimization
66    pub enable_stamp: bool,
67    /// Maximum recursion depth
68    pub max_recursion_depth: usize,
69    /// Enable self-subsuming resolution detection
70    pub enable_self_subsumption: bool,
71}
72
73impl Default for RecursiveMinConfig {
74    fn default() -> Self {
75        Self {
76            enable_recursive: true,
77            enable_binary: true,
78            enable_stamp: true,
79            max_recursion_depth: 10,
80            enable_self_subsumption: true,
81        }
82    }
83}
84
85/// Recursive conflict clause minimizer
86pub struct RecursiveMinimizer {
87    config: RecursiveMinConfig,
88    stats: RecursiveMinStats,
89    /// Literal information database
90    lit_info: FxHashMap<Lit, LitInfo>,
91    /// Current conflict level
92    conflict_level: Level,
93    /// Stack for recursive minimization
94    min_stack: Vec<Lit>,
95    /// Analyzed literals (for cycle detection)
96    analyzed: FxHashSet<Lit>,
97}
98
99impl RecursiveMinimizer {
100    /// Create a new recursive minimizer
101    pub fn new(config: RecursiveMinConfig) -> Self {
102        Self {
103            config,
104            stats: RecursiveMinStats::default(),
105            lit_info: FxHashMap::default(),
106            conflict_level: 0,
107            min_stack: Vec::new(),
108            analyzed: FxHashSet::default(),
109        }
110    }
111
112    /// Minimize a conflict clause
113    pub fn minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
114        if clause.len() <= 1 {
115            return Ok(());
116        }
117
118        self.stats.clauses_minimized += 1;
119
120        // Compute conflict level (highest level in clause)
121        self.conflict_level = clause
122            .iter()
123            .map(|&lit| self.get_level(lit))
124            .max()
125            .unwrap_or(0);
126
127        // Mark all literals in clause as seen
128        for &lit in clause.iter() {
129            if let Some(info) = self.lit_info.get_mut(&lit) {
130                info.seen = true;
131            }
132        }
133
134        // Phase 1: Recursive minimization
135        if self.config.enable_recursive {
136            self.recursive_minimize(clause)?;
137        }
138
139        // Phase 2: Binary resolution minimization
140        if self.config.enable_binary {
141            self.binary_minimize(clause)?;
142        }
143
144        // Phase 3: Stamp-based minimization
145        if self.config.enable_stamp {
146            self.stamp_minimize(clause)?;
147        }
148
149        // Phase 4: Self-subsuming resolution
150        if self.config.enable_self_subsumption {
151            self.detect_self_subsumption(clause)?;
152        }
153
154        // Clear seen marks
155        for &lit in clause.iter() {
156            if let Some(info) = self.lit_info.get_mut(&lit) {
157                info.seen = false;
158            }
159        }
160
161        Ok(())
162    }
163
164    /// Recursive resolution-based minimization
165    fn recursive_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
166        let mut to_remove = Vec::new();
167
168        for &lit in clause.iter() {
169            // Skip decision literals at conflict level
170            if self.get_level(lit) == self.conflict_level && self.is_decision(lit) {
171                continue;
172            }
173
174            // Try to resolve this literal away
175            self.analyzed.clear();
176            if self.can_remove_recursive(lit, 0)? {
177                to_remove.push(lit);
178                self.stats.literals_removed += 1;
179            }
180        }
181
182        // Remove literals
183        clause.retain(|lit| !to_remove.contains(lit));
184
185        Ok(())
186    }
187
188    /// Check if a literal can be removed via recursive resolution
189    fn can_remove_recursive(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
190        if depth > self.config.max_recursion_depth {
191            return Ok(false);
192        }
193
194        self.stats.recursive_calls += 1;
195
196        // Check for cycles
197        if self.analyzed.contains(&lit) {
198            return Ok(false);
199        }
200        self.analyzed.insert(lit);
201
202        // Get reason for this literal
203        let reason = match self.lit_info.get(&lit) {
204            Some(info) => info.reason.clone(),
205            None => return Ok(false),
206        };
207
208        match reason {
209            Reason::Decision => Ok(false),
210            Reason::Binary(other_lit) => {
211                // Check if other literal is in clause or can be removed
212                self.is_redundant(other_lit, depth)
213            }
214            Reason::Clause(clause_id) => {
215                // Check if all literals in reason clause are redundant
216                let reason_lits = self.get_clause_literals(clause_id)?;
217                for &reason_lit in &reason_lits {
218                    if reason_lit == lit {
219                        continue;
220                    }
221                    if !self.is_redundant(reason_lit, depth)? {
222                        return Ok(false);
223                    }
224                }
225                Ok(true)
226            }
227            Reason::Theory(theory_lits) => {
228                // Check if all theory literals are redundant
229                for &theory_lit in &theory_lits {
230                    if theory_lit == lit {
231                        continue;
232                    }
233                    if !self.is_redundant(theory_lit, depth)? {
234                        return Ok(false);
235                    }
236                }
237                Ok(true)
238            }
239        }
240    }
241
242    /// Check if a literal is redundant (in clause or can be recursively removed)
243    fn is_redundant(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
244        // Check if literal is in original clause (marked as seen)
245        if self.lit_info.get(&lit).is_some_and(|info| info.seen) {
246            return Ok(true);
247        }
248
249        // Check if at a level below conflict level (definitely needed)
250        if self.get_level(lit) < self.conflict_level {
251            return Ok(true);
252        }
253
254        // Try recursive removal
255        self.can_remove_recursive(lit, depth + 1)
256    }
257
258    /// Binary resolution minimization
259    fn binary_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
260        self.stats.binary_minimizations += 1;
261
262        let mut to_remove = Vec::new();
263
264        for &lit in clause.iter() {
265            // Check if this literal has a binary reason
266            let reason = match self.lit_info.get(&lit) {
267                Some(info) => &info.reason,
268                None => continue,
269            };
270
271            if let Reason::Binary(other_lit) = reason {
272                // Check if -other_lit is in the clause
273                if clause.contains(&-other_lit) {
274                    // Binary resolution: (lit ∨ -other_lit) ∧ (other_lit ∨ C) → C
275                    to_remove.push(lit);
276                    self.stats.literals_removed += 1;
277                }
278            }
279        }
280
281        clause.retain(|lit| !to_remove.contains(lit));
282
283        Ok(())
284    }
285
286    /// Stamp-based minimization (MiniSAT-style)
287    fn stamp_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
288        self.stats.stamp_minimizations += 1;
289
290        // Mark literals at conflict level as "abstract"
291        let mut abstract_level = FxHashSet::default();
292        for &lit in clause.iter() {
293            if self.get_level(lit) == self.conflict_level {
294                abstract_level.insert(lit);
295            }
296        }
297
298        let mut to_remove = Vec::new();
299
300        for &lit in clause.iter() {
301            if self.get_level(lit) < self.conflict_level {
302                continue;
303            }
304
305            // Check if this literal can be "stamped out"
306            if self.can_stamp_out(lit, &abstract_level)? {
307                to_remove.push(lit);
308                self.stats.literals_removed += 1;
309            }
310        }
311
312        clause.retain(|lit| !to_remove.contains(lit));
313
314        Ok(())
315    }
316
317    /// Check if a literal can be stamped out
318    fn can_stamp_out(&self, lit: Lit, abstract_level: &FxHashSet<Lit>) -> Result<bool, String> {
319        // Get reason for this literal
320        let reason = match self.lit_info.get(&lit) {
321            Some(info) => &info.reason,
322            None => return Ok(false),
323        };
324
325        match reason {
326            Reason::Decision => Ok(false),
327            Reason::Binary(other_lit) => Ok(abstract_level.contains(other_lit)),
328            Reason::Clause(clause_id) => {
329                let reason_lits = self.get_clause_literals(*clause_id)?;
330                Ok(reason_lits
331                    .iter()
332                    .filter(|&&l| l != lit)
333                    .all(|l| abstract_level.contains(l)))
334            }
335            Reason::Theory(theory_lits) => Ok(theory_lits
336                .iter()
337                .filter(|&&l| l != lit)
338                .all(|l| abstract_level.contains(l))),
339        }
340    }
341
342    /// Detect self-subsuming resolution opportunities
343    fn detect_self_subsumption(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
344        // Check if clause can subsume its reason clause after resolution
345        for &lit in clause.clone().iter() {
346            let reason = match self.lit_info.get(&lit) {
347                Some(info) => &info.reason,
348                None => continue,
349            };
350
351            if let Reason::Clause(clause_id) = reason
352                && self.is_self_subsuming(clause, lit, *clause_id)?
353            {
354                // Found self-subsumption
355                self.stats.self_subsuming_resolutions += 1;
356                // Would update clause database here
357            }
358        }
359
360        Ok(())
361    }
362
363    /// Check if clause self-subsumes after resolving on lit
364    fn is_self_subsuming(
365        &self,
366        clause: &[Lit],
367        lit: Lit,
368        reason_clause_id: ClauseId,
369    ) -> Result<bool, String> {
370        let reason_lits = self.get_clause_literals(reason_clause_id)?;
371
372        // Compute resolvent
373        let mut resolvent: FxHashSet<_> = clause.iter().filter(|&&l| l != lit).copied().collect();
374
375        for &reason_lit in &reason_lits {
376            if reason_lit != -lit {
377                resolvent.insert(reason_lit);
378            }
379        }
380
381        // Check if resolvent subsumes original clause
382        Ok(resolvent.len() < clause.len())
383    }
384
385    /// Set literal information
386    pub fn set_lit_info(&mut self, lit: Lit, level: Level, reason: Reason) {
387        self.lit_info.insert(
388            lit,
389            LitInfo {
390                level,
391                reason,
392                seen: false,
393                poisoned: false,
394            },
395        );
396    }
397
398    /// Get level of a literal
399    fn get_level(&self, lit: Lit) -> Level {
400        self.lit_info.get(&lit).map_or(0, |info| info.level)
401    }
402
403    /// Check if literal is a decision
404    fn is_decision(&self, lit: Lit) -> bool {
405        self.lit_info
406            .get(&lit)
407            .is_some_and(|info| matches!(info.reason, Reason::Decision))
408    }
409
410    /// Get literals from a clause (placeholder)
411    fn get_clause_literals(&self, _clause_id: ClauseId) -> Result<Vec<Lit>, String> {
412        // Placeholder: would retrieve from clause database
413        Ok(vec![])
414    }
415
416    /// Get statistics
417    pub fn stats(&self) -> &RecursiveMinStats {
418        &self.stats
419    }
420
421    /// Reset minimizer state
422    pub fn reset(&mut self) {
423        self.lit_info.clear();
424        self.min_stack.clear();
425        self.analyzed.clear();
426        self.conflict_level = 0;
427    }
428}
429
430#[cfg(test)]
431mod tests {
432    use super::*;
433
434    #[test]
435    fn test_minimizer_creation() {
436        let config = RecursiveMinConfig::default();
437        let minimizer = RecursiveMinimizer::new(config);
438        assert_eq!(minimizer.stats.clauses_minimized, 0);
439    }
440
441    #[test]
442    fn test_empty_clause() {
443        let config = RecursiveMinConfig::default();
444        let mut minimizer = RecursiveMinimizer::new(config);
445
446        let mut clause = vec![];
447        let result = minimizer.minimize(&mut clause);
448
449        assert!(result.is_ok());
450        assert_eq!(clause.len(), 0);
451    }
452
453    #[test]
454    fn test_unit_clause() {
455        let config = RecursiveMinConfig::default();
456        let mut minimizer = RecursiveMinimizer::new(config);
457
458        let mut clause = vec![1];
459        let result = minimizer.minimize(&mut clause);
460
461        assert!(result.is_ok());
462        assert_eq!(clause.len(), 1);
463    }
464
465    #[test]
466    fn test_set_lit_info() {
467        let config = RecursiveMinConfig::default();
468        let mut minimizer = RecursiveMinimizer::new(config);
469
470        minimizer.set_lit_info(1, 5, Reason::Decision);
471        assert_eq!(minimizer.get_level(1), 5);
472        assert!(minimizer.is_decision(1));
473    }
474
475    #[test]
476    fn test_binary_reason() {
477        let config = RecursiveMinConfig::default();
478        let mut minimizer = RecursiveMinimizer::new(config);
479
480        minimizer.set_lit_info(1, 3, Reason::Binary(2));
481        assert_eq!(minimizer.get_level(1), 3);
482        assert!(!minimizer.is_decision(1));
483    }
484
485    #[test]
486    fn test_theory_reason() {
487        let config = RecursiveMinConfig::default();
488        let mut minimizer = RecursiveMinimizer::new(config);
489
490        minimizer.set_lit_info(1, 4, Reason::Theory(vec![2, 3, 4]));
491        assert_eq!(minimizer.get_level(1), 4);
492    }
493
494    #[test]
495    fn test_conflict_level_computation() {
496        let config = RecursiveMinConfig::default();
497        let mut minimizer = RecursiveMinimizer::new(config);
498
499        minimizer.set_lit_info(1, 3, Reason::Decision);
500        minimizer.set_lit_info(2, 5, Reason::Decision);
501        minimizer.set_lit_info(3, 4, Reason::Decision);
502
503        let mut clause = vec![1, 2, 3];
504        let _ = minimizer.minimize(&mut clause);
505
506        assert_eq!(minimizer.conflict_level, 5);
507    }
508
509    #[test]
510    fn test_recursive_minimization_disabled() {
511        let config = RecursiveMinConfig {
512            enable_recursive: false,
513            ..Default::default()
514        };
515        let mut minimizer = RecursiveMinimizer::new(config);
516
517        minimizer.set_lit_info(1, 3, Reason::Decision);
518        minimizer.set_lit_info(2, 3, Reason::Binary(1));
519
520        let mut clause = vec![1, 2];
521        let _ = minimizer.minimize(&mut clause);
522
523        // Should not remove anything since recursive minimization is disabled
524        assert!(clause.contains(&1) || clause.contains(&2));
525    }
526
527    #[test]
528    fn test_stats_tracking() {
529        let config = RecursiveMinConfig::default();
530        let mut minimizer = RecursiveMinimizer::new(config);
531
532        minimizer.set_lit_info(1, 3, Reason::Decision);
533        minimizer.set_lit_info(2, 3, Reason::Decision);
534
535        let mut clause = vec![1, 2];
536        let _ = minimizer.minimize(&mut clause);
537
538        assert_eq!(minimizer.stats.clauses_minimized, 1);
539    }
540
541    #[test]
542    fn test_reset() {
543        let config = RecursiveMinConfig::default();
544        let mut minimizer = RecursiveMinimizer::new(config);
545
546        minimizer.set_lit_info(1, 3, Reason::Decision);
547        minimizer.conflict_level = 5;
548
549        minimizer.reset();
550
551        assert!(minimizer.lit_info.is_empty());
552        assert_eq!(minimizer.conflict_level, 0);
553    }
554
555    #[test]
556    fn test_max_recursion_depth() {
557        let config = RecursiveMinConfig {
558            max_recursion_depth: 2,
559            ..Default::default()
560        };
561        let mut minimizer = RecursiveMinimizer::new(config);
562
563        // Create deep reason chain
564        minimizer.set_lit_info(1, 5, Reason::Binary(2));
565        minimizer.set_lit_info(2, 5, Reason::Binary(3));
566        minimizer.set_lit_info(3, 5, Reason::Binary(4));
567        minimizer.set_lit_info(4, 5, Reason::Binary(5));
568        minimizer.set_lit_info(5, 5, Reason::Decision);
569
570        let result = minimizer.can_remove_recursive(1, 0);
571        assert!(result.is_ok());
572    }
573}