1use crate::clause::{ClauseDatabase, ClauseId};
36use crate::literal::Lit;
37#[allow(unused_imports)]
38use crate::prelude::*;
39use smallvec::SmallVec;
40
41#[cfg(test)]
42use crate::literal::Var;
43
44#[derive(Debug, Clone)]
46pub struct SubsumptionConfig {
47 pub enable_forward: bool,
49 pub enable_backward: bool,
51 pub enable_self_subsumption: bool,
53 pub check_on_learn: bool,
55 pub periodic_interval: u64,
57 pub max_clause_size: usize,
59 pub time_budget_us: u64,
61}
62
63impl Default for SubsumptionConfig {
64 fn default() -> Self {
65 Self {
66 enable_forward: true,
67 enable_backward: true,
68 enable_self_subsumption: true,
69 check_on_learn: true,
70 periodic_interval: 5000,
71 max_clause_size: 20,
72 time_budget_us: 100,
73 }
74 }
75}
76
77#[derive(Debug, Clone, Default)]
79pub struct SubsumptionStats {
80 pub forward_subsumptions: u64,
82 pub backward_subsumptions: u64,
84 pub self_subsumptions: u64,
86 pub checks_performed: u64,
88 pub checks_timeout: u64,
90 pub total_time_us: u64,
92}
93
94#[derive(Debug, Clone, PartialEq, Eq)]
96pub enum SubsumptionResult {
97 None,
99 Forward {
101 subsumer: ClauseId,
103 subsumed: ClauseId,
105 },
106 Backward {
108 subsumer: ClauseId,
110 },
111 SelfSubsumption {
113 clause: ClauseId,
115 literal: Lit,
117 },
118}
119
120pub struct DynamicSubsumption {
122 config: SubsumptionConfig,
124 stats: SubsumptionStats,
126 occurrences: FxHashMap<Lit, FxHashSet<ClauseId>>,
128 conflicts_since_check: u64,
130}
131
132impl DynamicSubsumption {
133 pub fn new() -> Self {
135 Self::with_config(SubsumptionConfig::default())
136 }
137
138 pub fn with_config(config: SubsumptionConfig) -> Self {
140 Self {
141 config,
142 stats: SubsumptionStats::default(),
143 occurrences: FxHashMap::default(),
144 conflicts_since_check: 0,
145 }
146 }
147
148 pub fn stats(&self) -> &SubsumptionStats {
150 &self.stats
151 }
152
153 pub fn reset_stats(&mut self) {
155 self.stats = SubsumptionStats::default();
156 }
157
158 pub fn check_learned_clause(
163 &mut self,
164 learned_clause: &[Lit],
165 clause_db: &ClauseDatabase,
166 ) -> Vec<SubsumptionResult> {
167 if !self.config.check_on_learn {
168 return vec![];
169 }
170
171 if learned_clause.len() > self.config.max_clause_size {
172 return vec![]; }
174
175 #[cfg(feature = "std")]
176 let start = std::time::Instant::now();
177 let mut results = Vec::new();
178
179 self.stats.checks_performed += 1;
180
181 let candidates = self.find_candidates(learned_clause);
183
184 for &candidate_id in &candidates {
185 #[cfg(feature = "std")]
187 if start.elapsed().as_micros() as u64 > self.config.time_budget_us {
188 self.stats.checks_timeout += 1;
189 break;
190 }
191
192 if let Some(candidate_clause) = clause_db.get(candidate_id) {
193 let candidate_lits = &candidate_clause.lits;
194
195 if self.config.enable_forward && subsumes(learned_clause, candidate_lits) {
197 results.push(SubsumptionResult::Forward {
198 subsumer: ClauseId::NULL, subsumed: candidate_id,
200 });
201 self.stats.forward_subsumptions += 1;
202 }
203
204 if self.config.enable_backward && subsumes(candidate_lits, learned_clause) {
206 results.push(SubsumptionResult::Backward {
207 subsumer: candidate_id,
208 });
209 self.stats.backward_subsumptions += 1;
210 }
211
212 if self.config.enable_self_subsumption
214 && let Some(lit) = find_self_subsumption(learned_clause, candidate_lits)
215 {
216 results.push(SubsumptionResult::SelfSubsumption {
217 clause: candidate_id,
218 literal: lit,
219 });
220 self.stats.self_subsumptions += 1;
221 }
222 }
223 }
224
225 #[cfg(feature = "std")]
226 {
227 self.stats.total_time_us += start.elapsed().as_micros() as u64;
228 }
229 results
230 }
231
232 pub fn on_clause_added(&mut self, clause_id: ClauseId, literals: &[Lit]) {
234 for &lit in literals {
235 self.occurrences.entry(lit).or_default().insert(clause_id);
236 }
237 }
238
239 pub fn on_clause_removed(&mut self, clause_id: ClauseId, literals: &[Lit]) {
241 for &lit in literals {
242 if let Some(occ) = self.occurrences.get_mut(&lit) {
243 occ.remove(&clause_id);
244 }
245 }
246 }
247
248 fn find_candidates(&self, clause: &[Lit]) -> FxHashSet<ClauseId> {
252 let mut candidates = FxHashSet::default();
253
254 let min_lit = clause
256 .iter()
257 .min_by_key(|&lit| self.occurrences.get(lit).map(|s| s.len()).unwrap_or(0));
258
259 if let Some(&lit) = min_lit
260 && let Some(occ) = self.occurrences.get(&lit)
261 {
262 candidates.extend(occ.iter().copied());
263 }
264
265 candidates
266 }
267
268 pub fn periodic_check(
270 &mut self,
271 clause_db: &ClauseDatabase,
272 conflicts: u64,
273 ) -> Vec<SubsumptionResult> {
274 self.conflicts_since_check = conflicts;
275
276 if !conflicts.is_multiple_of(self.config.periodic_interval) {
277 return vec![];
278 }
279
280 #[cfg(feature = "std")]
281 let start = std::time::Instant::now();
282 let results = Vec::new();
283
284 #[cfg(feature = "std")]
287 {
288 let budget_ms = 10; let deadline = start + std::time::Duration::from_millis(budget_ms);
290
291 for _i in 0..clause_db.len() {
293 if std::time::Instant::now() > deadline {
294 self.stats.checks_timeout += 1;
295 break;
296 }
297
298 }
301
302 self.stats.total_time_us += start.elapsed().as_micros() as u64;
303 }
304 results
305 }
306
307 pub fn clear(&mut self) {
309 self.occurrences.clear();
310 self.conflicts_since_check = 0;
311 }
312}
313
314impl Default for DynamicSubsumption {
315 fn default() -> Self {
316 Self::new()
317 }
318}
319
320fn subsumes(c: &[Lit], d: &[Lit]) -> bool {
324 if c.len() > d.len() {
325 return false; }
327
328 let d_set: FxHashSet<Lit> = d.iter().copied().collect();
330
331 c.iter().all(|&lit| d_set.contains(&lit))
333}
334
335fn find_self_subsumption(c: &[Lit], d: &[Lit]) -> Option<Lit> {
339 for &lit_c in c {
341 let neg_lit_c = !lit_c;
342
343 if !d.contains(&neg_lit_c) {
345 continue;
346 }
347
348 let c_without: SmallVec<[Lit; 8]> = c.iter().copied().filter(|&l| l != lit_c).collect();
350 let d_without: SmallVec<[Lit; 8]> = d.iter().copied().filter(|&l| l != neg_lit_c).collect();
351
352 if subsumes(&c_without, &d_without) {
353 return Some(neg_lit_c); }
355 }
356
357 None
358}
359
360#[cfg(test)]
361mod tests {
362 use super::*;
363
364 fn lit(var: u32, positive: bool) -> Lit {
365 let v = Var::new(var);
366 if positive { Lit::pos(v) } else { Lit::neg(v) }
367 }
368
369 #[test]
370 fn test_subsumption_config_default() {
371 let config = SubsumptionConfig::default();
372 assert!(config.enable_forward);
373 assert!(config.enable_backward);
374 assert!(config.check_on_learn);
375 }
376
377 #[test]
378 fn test_subsumes_basic() {
379 let c = vec![lit(0, false), lit(1, false)]; let d = vec![lit(0, false), lit(1, false), lit(2, false)]; assert!(subsumes(&c, &d)); assert!(!subsumes(&d, &c)); }
385
386 #[test]
387 fn test_subsumes_equal() {
388 let c = vec![lit(0, false), lit(1, false)];
389 let d = vec![lit(0, false), lit(1, false)];
390
391 assert!(subsumes(&c, &d)); assert!(subsumes(&d, &c));
393 }
394
395 #[test]
396 fn test_subsumes_no_overlap() {
397 let c = vec![lit(0, false), lit(1, false)];
398 let d = vec![lit(2, false), lit(3, false)];
399
400 assert!(!subsumes(&c, &d));
401 assert!(!subsumes(&d, &c));
402 }
403
404 #[test]
405 fn test_subsumes_partial_overlap() {
406 let c = vec![lit(0, false), lit(1, false)];
407 let d = vec![lit(1, false), lit(2, false)];
408
409 assert!(!subsumes(&c, &d)); assert!(!subsumes(&d, &c)); }
412
413 #[test]
414 fn test_self_subsumption_basic() {
415 let c = vec![lit(0, false), lit(1, false)];
419 let d = vec![lit(0, false), lit(1, true), lit(2, false)];
420
421 let result = find_self_subsumption(&c, &d);
422 assert_eq!(result, Some(lit(1, true)));
423 }
424
425 #[test]
426 fn test_self_subsumption_none() {
427 let c = vec![lit(0, false), lit(1, false)];
428 let d = vec![lit(2, false), lit(3, false)];
429
430 let result = find_self_subsumption(&c, &d);
431 assert_eq!(result, None);
432 }
433
434 #[test]
435 fn test_dynamic_subsumption_creation() {
436 let ds = DynamicSubsumption::new();
437 assert_eq!(ds.stats().forward_subsumptions, 0);
438 assert_eq!(ds.stats().backward_subsumptions, 0);
439 }
440
441 #[test]
442 fn test_occurrence_tracking() {
443 let mut ds = DynamicSubsumption::new();
444
445 let clause1 = vec![lit(0, false), lit(1, false)];
446 let clause1_id = ClauseId::new(1);
447
448 ds.on_clause_added(clause1_id, &clause1);
449
450 assert!(ds.occurrences.contains_key(&lit(0, false)));
452 assert!(ds.occurrences.contains_key(&lit(1, false)));
453
454 ds.on_clause_removed(clause1_id, &clause1);
455
456 assert!(
458 !ds.occurrences.contains_key(&lit(0, false))
459 || ds.occurrences[&lit(0, false)].is_empty()
460 );
461 }
462
463 #[test]
464 fn test_find_candidates() {
465 let mut ds = DynamicSubsumption::new();
466
467 let clause1 = vec![lit(0, true), lit(1, true)];
468 let clause2 = vec![lit(1, true), lit(2, true)];
469 let clause1_id = ClauseId::new(1);
470 let clause2_id = ClauseId::new(2);
471
472 ds.on_clause_added(clause1_id, &clause1);
473 ds.on_clause_added(clause2_id, &clause2);
474
475 let query = vec![lit(1, true), lit(3, true)];
477 let candidates = ds.find_candidates(&query);
478
479 assert!(candidates.len() <= 2); }
484
485 #[test]
486 fn test_stats_tracking() {
487 let mut ds = DynamicSubsumption::new();
488
489 ds.stats.forward_subsumptions = 10;
490 ds.stats.backward_subsumptions = 5;
491
492 assert_eq!(ds.stats().forward_subsumptions, 10);
493 assert_eq!(ds.stats().backward_subsumptions, 5);
494
495 ds.reset_stats();
496
497 assert_eq!(ds.stats().forward_subsumptions, 0);
498 assert_eq!(ds.stats().backward_subsumptions, 0);
499 }
500
501 #[test]
502 fn test_clear() {
503 let mut ds = DynamicSubsumption::new();
504
505 ds.on_clause_added(ClauseId::new(1), &[lit(0, false)]);
506 ds.conflicts_since_check = 100;
507
508 assert!(!ds.occurrences.is_empty());
509 assert_eq!(ds.conflicts_since_check, 100);
510
511 ds.clear();
512
513 assert!(ds.occurrences.is_empty());
514 assert_eq!(ds.conflicts_since_check, 0);
515 }
516}