1#![allow(missing_docs, clippy::ptr_arg)] #[allow(unused_imports)]
12use crate::prelude::*;
13
14pub type Lit = i32;
16
17pub type ClauseId = usize;
19
20pub type Level = usize;
22
23#[derive(Debug, Clone)]
25pub enum Reason {
26 Decision,
28 Clause(ClauseId),
30 Binary(Lit),
32 Theory(Vec<Lit>),
34}
35
36#[derive(Debug, Clone)]
38pub struct LitInfo {
39 pub level: Level,
41 pub reason: Reason,
43 pub seen: bool,
45 pub poisoned: bool,
47}
48
49#[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#[derive(Debug, Clone)]
62pub struct RecursiveMinConfig {
63 pub enable_recursive: bool,
65 pub enable_binary: bool,
67 pub enable_stamp: bool,
69 pub max_recursion_depth: usize,
71 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
87pub struct RecursiveMinimizer {
89 config: RecursiveMinConfig,
90 stats: RecursiveMinStats,
91 lit_info: FxHashMap<Lit, LitInfo>,
93 conflict_level: Level,
95 min_stack: Vec<Lit>,
97 analyzed: FxHashSet<Lit>,
99}
100
101impl RecursiveMinimizer {
102 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 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 self.conflict_level = clause
124 .iter()
125 .map(|&lit| self.get_level(lit))
126 .max()
127 .unwrap_or(0);
128
129 for &lit in clause.iter() {
131 if let Some(info) = self.lit_info.get_mut(&lit) {
132 info.seen = true;
133 }
134 }
135
136 if self.config.enable_recursive {
138 self.recursive_minimize(clause)?;
139 }
140
141 if self.config.enable_binary {
143 self.binary_minimize(clause)?;
144 }
145
146 if self.config.enable_stamp {
148 self.stamp_minimize(clause)?;
149 }
150
151 if self.config.enable_self_subsumption {
153 self.detect_self_subsumption(clause)?;
154 }
155
156 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 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 if self.get_level(lit) == self.conflict_level && self.is_decision(lit) {
173 continue;
174 }
175
176 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 clause.retain(|lit| !to_remove.contains(lit));
186
187 Ok(())
188 }
189
190 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 if self.analyzed.contains(&lit) {
200 return Ok(false);
201 }
202 self.analyzed.insert(lit);
203
204 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 self.is_redundant(other_lit, depth)
215 }
216 Reason::Clause(clause_id) => {
217 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 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 fn is_redundant(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
246 if self.lit_info.get(&lit).is_some_and(|info| info.seen) {
248 return Ok(true);
249 }
250
251 if self.get_level(lit) < self.conflict_level {
253 return Ok(true);
254 }
255
256 self.can_remove_recursive(lit, depth + 1)
258 }
259
260 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 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 if clause.contains(&-other_lit) {
276 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 fn stamp_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
290 self.stats.stamp_minimizations += 1;
291
292 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 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 fn can_stamp_out(&self, lit: Lit, abstract_level: &FxHashSet<Lit>) -> Result<bool, String> {
321 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 fn detect_self_subsumption(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
346 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 self.stats.self_subsuming_resolutions += 1;
358 }
360 }
361
362 Ok(())
363 }
364
365 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 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 Ok(resolvent.len() < clause.len())
385 }
386
387 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 fn get_level(&self, lit: Lit) -> Level {
402 self.lit_info.get(&lit).map_or(0, |info| info.level)
403 }
404
405 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 fn get_clause_literals(&self, _clause_id: ClauseId) -> Result<Vec<Lit>, String> {
414 Ok(vec![])
416 }
417
418 pub fn stats(&self) -> &RecursiveMinStats {
420 &self.stats
421 }
422
423 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 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 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}