1use crate::clause::{ClauseDatabase, ClauseId};
15use crate::literal::{Lit, Var};
16#[allow(unused_imports)]
17use crate::prelude::*;
18use crate::proof::DratProof;
19
20#[derive(Debug, Default, Clone)]
22pub struct DratInprocessingStats {
23 pub clauses_eliminated: usize,
25 pub variables_eliminated: usize,
27 pub subsumptions: usize,
29 pub blocked_clauses_eliminated: usize,
31 pub proof_steps: usize,
33 pub rounds: usize,
35}
36
37impl DratInprocessingStats {
38 pub fn display(&self) -> String {
40 format!(
41 "DRAT Inprocessing Stats:\n\
42 - Rounds: {}\n\
43 - Clauses eliminated: {}\n\
44 - Variables eliminated: {}\n\
45 - Subsumptions: {}\n\
46 - Blocked clauses: {}\n\
47 - Proof steps: {}",
48 self.rounds,
49 self.clauses_eliminated,
50 self.variables_eliminated,
51 self.subsumptions,
52 self.blocked_clauses_eliminated,
53 self.proof_steps,
54 )
55 }
56}
57
58#[derive(Debug, Clone)]
60pub struct DratInprocessingConfig {
61 pub enable_subsumption: bool,
63 pub enable_variable_elimination: bool,
65 pub enable_blocked_clause_elimination: bool,
67 pub max_clause_size_for_elimination: usize,
69 pub max_resolution_size: usize,
71}
72
73impl Default for DratInprocessingConfig {
74 fn default() -> Self {
75 Self {
76 enable_subsumption: true,
77 enable_variable_elimination: true,
78 enable_blocked_clause_elimination: true,
79 max_clause_size_for_elimination: 10,
80 max_resolution_size: 100,
81 }
82 }
83}
84
85pub struct DratInprocessor {
89 config: DratInprocessingConfig,
91 stats: DratInprocessingStats,
93 eliminated_clauses: HashSet<ClauseId>,
95 eliminated_vars: HashSet<Var>,
97}
98
99impl DratInprocessor {
100 pub fn new(config: DratInprocessingConfig) -> Self {
102 Self {
103 config,
104 stats: DratInprocessingStats::default(),
105 eliminated_clauses: HashSet::new(),
106 eliminated_vars: HashSet::new(),
107 }
108 }
109
110 pub fn default_config() -> Self {
112 Self::new(DratInprocessingConfig::default())
113 }
114
115 pub fn inprocess(
119 &mut self,
120 db: &mut ClauseDatabase,
121 proof: &mut DratProof,
122 ) -> std::io::Result<usize> {
123 self.stats.rounds += 1;
124 let mut simplifications = 0;
125
126 if self.config.enable_subsumption {
128 simplifications += self.eliminate_subsumed(db, proof)?;
129 }
130
131 if self.config.enable_blocked_clause_elimination {
133 simplifications += self.eliminate_blocked_clauses(db, proof)?;
134 }
135
136 if self.config.enable_variable_elimination {
138 simplifications += self.eliminate_variables(db, proof)?;
139 }
140
141 Ok(simplifications)
142 }
143
144 fn eliminate_subsumed(
146 &mut self,
147 db: &mut ClauseDatabase,
148 proof: &mut DratProof,
149 ) -> std::io::Result<usize> {
150 let mut eliminated = 0;
151
152 let clause_ids: Vec<ClauseId> = db.iter_ids().collect();
154
155 for i in 0..clause_ids.len() {
156 let cid1 = clause_ids[i];
157 if self.eliminated_clauses.contains(&cid1) {
158 continue;
159 }
160
161 let clause1 = match db.get(cid1) {
162 Some(c) => c.lits.to_vec(),
163 None => continue,
164 };
165
166 for &cid2 in clause_ids.iter().skip(i + 1) {
167 if self.eliminated_clauses.contains(&cid2) {
168 continue;
169 }
170
171 let clause2 = match db.get(cid2) {
172 Some(c) => c.lits.to_vec(),
173 None => continue,
174 };
175
176 if Self::subsumes(&clause1, &clause2) {
178 proof.delete_clause(&clause2)?;
180 self.stats.proof_steps += 1;
181
182 self.eliminated_clauses.insert(cid2);
184 self.stats.subsumptions += 1;
185 eliminated += 1;
186 } else if Self::subsumes(&clause2, &clause1) {
187 proof.delete_clause(&clause1)?;
189 self.stats.proof_steps += 1;
190
191 self.eliminated_clauses.insert(cid1);
193 self.stats.subsumptions += 1;
194 eliminated += 1;
195 break; }
197 }
198 }
199
200 self.stats.clauses_eliminated += eliminated;
201 Ok(eliminated)
202 }
203
204 fn subsumes(clause1: &[Lit], clause2: &[Lit]) -> bool {
206 if clause1.len() > clause2.len() {
207 return false;
208 }
209
210 clause1.iter().all(|lit1| clause2.contains(lit1))
212 }
213
214 fn eliminate_blocked_clauses(
216 &mut self,
217 db: &mut ClauseDatabase,
218 proof: &mut DratProof,
219 ) -> std::io::Result<usize> {
220 let mut eliminated = 0;
221
222 let clause_ids: Vec<ClauseId> = db.iter_ids().collect();
223
224 for cid in clause_ids {
225 if self.eliminated_clauses.contains(&cid) {
226 continue;
227 }
228
229 let clause = match db.get(cid) {
230 Some(c) => c.lits.to_vec(),
231 None => continue,
232 };
233
234 for &lit in &clause {
236 if self.is_blocked(db, &clause, lit) {
237 proof.delete_clause(&clause)?;
239 self.stats.proof_steps += 1;
240
241 self.eliminated_clauses.insert(cid);
243 self.stats.blocked_clauses_eliminated += 1;
244 eliminated += 1;
245 break;
246 }
247 }
248 }
249
250 self.stats.clauses_eliminated += eliminated;
251 Ok(eliminated)
252 }
253
254 fn is_blocked(&self, db: &ClauseDatabase, clause: &[Lit], lit: Lit) -> bool {
259 let neg_lit = lit.negate();
260
261 for other_cid in db.iter_ids() {
263 if self.eliminated_clauses.contains(&other_cid) {
264 continue;
265 }
266
267 let other_clause = match db.get(other_cid) {
268 Some(c) => &c.lits,
269 None => continue,
270 };
271
272 if !other_clause.contains(&neg_lit) {
274 continue;
275 }
276
277 let resolvent = Self::resolve(clause, other_clause, lit.var());
279 if !Self::is_tautology(&resolvent) {
280 return false; }
282 }
283
284 true }
286
287 fn resolve(clause1: &[Lit], clause2: &[Lit], var: Var) -> Vec<Lit> {
289 let mut resolvent = Vec::new();
290
291 for &lit in clause1 {
293 if lit.var() != var {
294 resolvent.push(lit);
295 }
296 }
297
298 for &lit in clause2 {
300 if lit.var() != var && !resolvent.contains(&lit) {
301 resolvent.push(lit);
302 }
303 }
304
305 resolvent
306 }
307
308 fn is_tautology(clause: &[Lit]) -> bool {
310 for &lit in clause {
311 if clause.contains(&lit.negate()) {
312 return true;
313 }
314 }
315 false
316 }
317
318 fn eliminate_variables(
320 &mut self,
321 db: &mut ClauseDatabase,
322 proof: &mut DratProof,
323 ) -> std::io::Result<usize> {
324 let mut eliminated = 0;
325
326 let vars = self.find_elimination_candidates(db);
328
329 for var in vars {
330 if self.can_eliminate_variable(db, var) {
331 eliminated += self.eliminate_variable(db, proof, var)?;
332 self.eliminated_vars.insert(var);
333 self.stats.variables_eliminated += 1;
334 }
335 }
336
337 Ok(eliminated)
338 }
339
340 fn find_elimination_candidates(&self, db: &ClauseDatabase) -> Vec<Var> {
342 let mut var_counts: HashMap<Var, usize> = HashMap::new();
343
344 for cid in db.iter_ids() {
346 if self.eliminated_clauses.contains(&cid) {
347 continue;
348 }
349
350 let clause = match db.get(cid) {
351 Some(c) => &c.lits,
352 None => continue,
353 };
354
355 for &lit in clause {
356 *var_counts.entry(lit.var()).or_insert(0) += 1;
357 }
358 }
359
360 let mut candidates: Vec<_> = var_counts
362 .into_iter()
363 .filter(|(_, count)| *count <= 5) .map(|(var, _)| var)
365 .collect();
366
367 candidates.sort_unstable_by_key(|v| v.0);
368 candidates
369 }
370
371 fn can_eliminate_variable(&self, db: &ClauseDatabase, var: Var) -> bool {
373 if self.eliminated_vars.contains(&var) {
374 return false;
375 }
376
377 let (pos_clauses, neg_clauses) = self.collect_clauses_with_var(db, var);
379
380 let num_resolvents = pos_clauses.len() * neg_clauses.len();
382 if num_resolvents > self.config.max_resolution_size {
383 return false;
384 }
385
386 for pos_clause in &pos_clauses {
388 if pos_clause.len() > self.config.max_clause_size_for_elimination {
389 return false;
390 }
391 }
392
393 for neg_clause in &neg_clauses {
394 if neg_clause.len() > self.config.max_clause_size_for_elimination {
395 return false;
396 }
397 }
398
399 true
400 }
401
402 fn collect_clauses_with_var(
404 &self,
405 db: &ClauseDatabase,
406 var: Var,
407 ) -> (Vec<Vec<Lit>>, Vec<Vec<Lit>>) {
408 let mut pos_clauses = Vec::new();
409 let mut neg_clauses = Vec::new();
410
411 for cid in db.iter_ids() {
412 if self.eliminated_clauses.contains(&cid) {
413 continue;
414 }
415
416 let clause = match db.get(cid) {
417 Some(c) => c.lits.to_vec(),
418 None => continue,
419 };
420
421 let pos_lit = Lit::pos(var);
422 let neg_lit = Lit::neg(var);
423
424 if clause.contains(&pos_lit) {
425 pos_clauses.push(clause);
426 } else if clause.contains(&neg_lit) {
427 neg_clauses.push(clause);
428 }
429 }
430
431 (pos_clauses, neg_clauses)
432 }
433
434 fn eliminate_variable(
436 &mut self,
437 db: &mut ClauseDatabase,
438 proof: &mut DratProof,
439 var: Var,
440 ) -> std::io::Result<usize> {
441 let (pos_clauses, neg_clauses) = self.collect_clauses_with_var(db, var);
442
443 for pos_clause in &pos_clauses {
445 for neg_clause in &neg_clauses {
446 let resolvent = Self::resolve(pos_clause, neg_clause, var);
447
448 if Self::is_tautology(&resolvent) {
450 continue;
451 }
452
453 proof.add_clause(&resolvent)?;
455 self.stats.proof_steps += 1;
456 }
457 }
458
459 for clause in pos_clauses.iter().chain(neg_clauses.iter()) {
461 proof.delete_clause(clause)?;
462 self.stats.proof_steps += 1;
463 }
464
465 let eliminated = pos_clauses.len() + neg_clauses.len();
466 self.stats.clauses_eliminated += eliminated;
467
468 Ok(eliminated)
469 }
470
471 pub fn stats(&self) -> &DratInprocessingStats {
473 &self.stats
474 }
475
476 pub fn reset_stats(&mut self) {
478 self.stats = DratInprocessingStats::default();
479 }
480
481 pub fn clear(&mut self) {
483 self.eliminated_clauses.clear();
484 self.eliminated_vars.clear();
485 self.stats = DratInprocessingStats::default();
486 }
487}
488
489impl Default for DratInprocessor {
490 fn default() -> Self {
491 Self::default_config()
492 }
493}
494
495#[cfg(test)]
496mod tests {
497 use super::*;
498
499 #[test]
500 fn test_drat_inprocessor_creation() {
501 let inprocessor = DratInprocessor::default();
502 assert_eq!(inprocessor.stats().rounds, 0);
503 }
504
505 #[test]
506 fn test_subsumes() {
507 let v0 = Var(0);
508 let v1 = Var(1);
509
510 let clause1 = vec![Lit::pos(v0)];
511 let clause2 = vec![Lit::pos(v0), Lit::pos(v1)];
512
513 assert!(DratInprocessor::subsumes(&clause1, &clause2));
514 assert!(!DratInprocessor::subsumes(&clause2, &clause1));
515 }
516
517 #[test]
518 fn test_subsumes_equal() {
519 let v0 = Var(0);
520 let v1 = Var(1);
521
522 let clause1 = vec![Lit::pos(v0), Lit::pos(v1)];
523 let clause2 = vec![Lit::pos(v0), Lit::pos(v1)];
524
525 assert!(DratInprocessor::subsumes(&clause1, &clause2));
526 assert!(DratInprocessor::subsumes(&clause2, &clause1));
527 }
528
529 #[test]
530 fn test_resolve() {
531 let v0 = Var(0);
532 let v1 = Var(1);
533
534 let clause1 = vec![Lit::pos(v0), Lit::pos(v1)];
535 let clause2 = vec![Lit::neg(v0)];
536
537 let resolvent = DratInprocessor::resolve(&clause1, &clause2, v0);
538 assert_eq!(resolvent.len(), 1);
539 assert!(resolvent.contains(&Lit::pos(v1)));
540 }
541
542 #[test]
543 fn test_is_tautology() {
544 let v0 = Var(0);
545 let v1 = Var(1);
546
547 let tautology = vec![Lit::pos(v0), Lit::neg(v0), Lit::pos(v1)];
548 let non_tautology = vec![Lit::pos(v0), Lit::pos(v1)];
549
550 assert!(DratInprocessor::is_tautology(&tautology));
551 assert!(!DratInprocessor::is_tautology(&non_tautology));
552 }
553
554 #[test]
555 fn test_config_default() {
556 let config = DratInprocessingConfig::default();
557 assert!(config.enable_subsumption);
558 assert!(config.enable_variable_elimination);
559 assert!(config.enable_blocked_clause_elimination);
560 }
561
562 #[test]
563 fn test_stats_display() {
564 let stats = DratInprocessingStats {
565 clauses_eliminated: 10,
566 variables_eliminated: 2,
567 subsumptions: 5,
568 blocked_clauses_eliminated: 3,
569 proof_steps: 20,
570 rounds: 1,
571 };
572
573 let display = stats.display();
574 assert!(display.contains("10"));
575 assert!(display.contains("2"));
576 assert!(display.contains("5"));
577 }
578
579 #[test]
580 fn test_clear() {
581 let mut inprocessor = DratInprocessor::default();
582 inprocessor.stats.rounds = 5;
583 inprocessor.eliminated_vars.insert(Var(0));
584
585 inprocessor.clear();
586
587 assert_eq!(inprocessor.stats.rounds, 0);
588 assert!(inprocessor.eliminated_vars.is_empty());
589 }
590}