1#![allow(missing_docs, clippy::ptr_arg)] use rustc_hash::{FxHashMap, FxHashSet};
11
12pub type Lit = i32;
14
15pub type ClauseId = usize;
17
18pub type Level = usize;
20
21#[derive(Debug, Clone)]
23pub enum Reason {
24 Decision,
26 Clause(ClauseId),
28 Binary(Lit),
30 Theory(Vec<Lit>),
32}
33
34#[derive(Debug, Clone)]
36pub struct LitInfo {
37 pub level: Level,
39 pub reason: Reason,
41 pub seen: bool,
43 pub poisoned: bool,
45}
46
47#[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#[derive(Debug, Clone)]
60pub struct RecursiveMinConfig {
61 pub enable_recursive: bool,
63 pub enable_binary: bool,
65 pub enable_stamp: bool,
67 pub max_recursion_depth: usize,
69 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
85pub struct RecursiveMinimizer {
87 config: RecursiveMinConfig,
88 stats: RecursiveMinStats,
89 lit_info: FxHashMap<Lit, LitInfo>,
91 conflict_level: Level,
93 min_stack: Vec<Lit>,
95 analyzed: FxHashSet<Lit>,
97}
98
99impl RecursiveMinimizer {
100 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 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 self.conflict_level = clause
122 .iter()
123 .map(|&lit| self.get_level(lit))
124 .max()
125 .unwrap_or(0);
126
127 for &lit in clause.iter() {
129 if let Some(info) = self.lit_info.get_mut(&lit) {
130 info.seen = true;
131 }
132 }
133
134 if self.config.enable_recursive {
136 self.recursive_minimize(clause)?;
137 }
138
139 if self.config.enable_binary {
141 self.binary_minimize(clause)?;
142 }
143
144 if self.config.enable_stamp {
146 self.stamp_minimize(clause)?;
147 }
148
149 if self.config.enable_self_subsumption {
151 self.detect_self_subsumption(clause)?;
152 }
153
154 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 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 if self.get_level(lit) == self.conflict_level && self.is_decision(lit) {
171 continue;
172 }
173
174 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 clause.retain(|lit| !to_remove.contains(lit));
184
185 Ok(())
186 }
187
188 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 if self.analyzed.contains(&lit) {
198 return Ok(false);
199 }
200 self.analyzed.insert(lit);
201
202 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 self.is_redundant(other_lit, depth)
213 }
214 Reason::Clause(clause_id) => {
215 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 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 fn is_redundant(&mut self, lit: Lit, depth: usize) -> Result<bool, String> {
244 if self.lit_info.get(&lit).is_some_and(|info| info.seen) {
246 return Ok(true);
247 }
248
249 if self.get_level(lit) < self.conflict_level {
251 return Ok(true);
252 }
253
254 self.can_remove_recursive(lit, depth + 1)
256 }
257
258 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 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 if clause.contains(&-other_lit) {
274 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 fn stamp_minimize(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
288 self.stats.stamp_minimizations += 1;
289
290 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 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 fn can_stamp_out(&self, lit: Lit, abstract_level: &FxHashSet<Lit>) -> Result<bool, String> {
319 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 fn detect_self_subsumption(&mut self, clause: &mut Vec<Lit>) -> Result<(), String> {
344 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 self.stats.self_subsuming_resolutions += 1;
356 }
358 }
359
360 Ok(())
361 }
362
363 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 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 Ok(resolvent.len() < clause.len())
383 }
384
385 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 fn get_level(&self, lit: Lit) -> Level {
400 self.lit_info.get(&lit).map_or(0, |info| info.level)
401 }
402
403 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 fn get_clause_literals(&self, _clause_id: ClauseId) -> Result<Vec<Lit>, String> {
412 Ok(vec![])
414 }
415
416 pub fn stats(&self) -> &RecursiveMinStats {
418 &self.stats
419 }
420
421 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 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 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}