Skip to main content

oxiz_solver/conflict/
recursive_minimizer.rs

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