1use crate::clause::ClauseDatabase;
16use crate::literal::Lit;
17#[allow(unused_imports)]
18use crate::prelude::*;
19
20#[derive(Debug, Clone, Default)]
22pub struct BigStats {
23 pub binary_clauses_analyzed: usize,
25 pub redundant_removed: usize,
27 pub transitive_found: usize,
29 pub sccs_found: usize,
31 pub equivalences_from_sccs: usize,
33}
34
35impl BigStats {
36 pub fn display(&self) {
38 println!("Binary Implication Graph Statistics:");
39 println!(
40 " Binary clauses analyzed: {}",
41 self.binary_clauses_analyzed
42 );
43 println!(" Redundant clauses removed: {}", self.redundant_removed);
44 println!(" Transitive implications: {}", self.transitive_found);
45 println!(" SCCs found: {}", self.sccs_found);
46 println!(" Equivalences from SCCs: {}", self.equivalences_from_sccs);
47 }
48}
49
50#[derive(Debug)]
52pub struct BinaryImplicationGraph {
53 implications: Vec<HashSet<Lit>>,
55 reverse_implications: Vec<HashSet<Lit>>,
57 stats: BigStats,
59}
60
61impl BinaryImplicationGraph {
62 #[must_use]
64 pub fn new(num_vars: usize) -> Self {
65 let size = num_vars * 2;
66 Self {
67 implications: vec![HashSet::new(); size],
68 reverse_implications: vec![HashSet::new(); size],
69 stats: BigStats::default(),
70 }
71 }
72
73 pub fn build(&mut self, clauses: &ClauseDatabase) {
75 for imp in &mut self.implications {
77 imp.clear();
78 }
79 for imp in &mut self.reverse_implications {
80 imp.clear();
81 }
82 self.stats.binary_clauses_analyzed = 0;
83
84 for cid in clauses.iter_ids() {
86 if let Some(clause) = clauses.get(cid)
87 && clause.len() == 2
88 {
89 self.stats.binary_clauses_analyzed += 1;
90
91 let a = clause.lits[0];
92 let b = clause.lits[1];
93
94 self.add_implication(!a, b);
96 self.add_implication(!b, a);
97 }
98 }
99 }
100
101 fn add_implication(&mut self, from: Lit, to: Lit) {
103 let from_idx = from.code() as usize;
104 let to_idx = to.code() as usize;
105
106 while from_idx >= self.implications.len() {
108 self.implications.push(HashSet::new());
109 self.reverse_implications.push(HashSet::new());
110 }
111 while to_idx >= self.implications.len() {
112 self.implications.push(HashSet::new());
113 self.reverse_implications.push(HashSet::new());
114 }
115
116 self.implications[from_idx].insert(to);
117 self.reverse_implications[to_idx].insert(from);
118 }
119
120 pub fn transitive_reduction(&mut self) -> Vec<(Lit, Lit)> {
124 let mut redundant = Vec::new();
125 let num_lits = self.implications.len();
126
127 for lit_idx in 0..num_lits {
128 let lit = Lit::from_code(lit_idx as u32);
129 let direct_implications: Vec<_> = self.implications[lit_idx].iter().copied().collect();
130
131 for &implied in &direct_implications {
132 if self.has_alternative_path(lit, implied) {
134 redundant.push((lit, implied));
135 self.stats.redundant_removed += 1;
136 }
137 }
138 }
139
140 for (from, to) in &redundant {
142 let from_idx = from.code() as usize;
143 let to_idx = to.code() as usize;
144 self.implications[from_idx].remove(to);
145 self.reverse_implications[to_idx].remove(from);
146 }
147
148 redundant
149 }
150
151 fn has_alternative_path(&self, from: Lit, to: Lit) -> bool {
153 let from_idx = from.code() as usize;
154 if from_idx >= self.implications.len() {
155 return false;
156 }
157
158 let mut visited = HashSet::new();
159 let mut queue = Vec::new();
160
161 for &implied in &self.implications[from_idx] {
163 if implied != to {
164 queue.push(implied);
165 }
166 }
167
168 while let Some(lit) = queue.pop() {
169 if lit == to {
170 return true; }
172
173 let lit_idx = lit.code() as usize;
174 if visited.contains(&lit_idx) || lit_idx >= self.implications.len() {
175 continue;
176 }
177
178 visited.insert(lit_idx);
179
180 for &next in &self.implications[lit_idx] {
181 if !visited.contains(&(next.code() as usize)) {
182 queue.push(next);
183 }
184 }
185 }
186
187 false
188 }
189
190 #[allow(dead_code)]
194 pub fn find_sccs(&mut self) -> Vec<Vec<Lit>> {
195 let num_lits = self.implications.len();
196 let mut index = vec![None; num_lits];
197 let mut lowlink = vec![0; num_lits];
198 let mut on_stack = vec![false; num_lits];
199 let mut stack = Vec::new();
200 let mut sccs = Vec::new();
201 let mut current_index = 0;
202
203 for lit_idx in 0..num_lits {
204 if index[lit_idx].is_none() {
205 self.tarjan_scc(
206 lit_idx,
207 &mut index,
208 &mut lowlink,
209 &mut on_stack,
210 &mut stack,
211 &mut sccs,
212 &mut current_index,
213 );
214 }
215 }
216
217 self.stats.sccs_found = sccs.iter().filter(|scc| scc.len() > 1).count();
219 sccs.into_iter().filter(|scc| scc.len() > 1).collect()
220 }
221
222 #[allow(clippy::too_many_arguments)]
224 fn tarjan_scc(
225 &self,
226 lit_idx: usize,
227 index: &mut Vec<Option<usize>>,
228 lowlink: &mut Vec<usize>,
229 on_stack: &mut Vec<bool>,
230 stack: &mut Vec<usize>,
231 sccs: &mut Vec<Vec<Lit>>,
232 current_index: &mut usize,
233 ) {
234 index[lit_idx] = Some(*current_index);
235 lowlink[lit_idx] = *current_index;
236 *current_index += 1;
237 stack.push(lit_idx);
238 on_stack[lit_idx] = true;
239
240 if lit_idx < self.implications.len() {
242 for &implied in &self.implications[lit_idx] {
243 let impl_idx = implied.code() as usize;
244
245 if index[impl_idx].is_none() {
246 self.tarjan_scc(
247 impl_idx,
248 index,
249 lowlink,
250 on_stack,
251 stack,
252 sccs,
253 current_index,
254 );
255 lowlink[lit_idx] = lowlink[lit_idx].min(lowlink[impl_idx]);
256 } else if on_stack[impl_idx] {
257 lowlink[lit_idx] = lowlink[lit_idx]
258 .min(index[impl_idx].expect("index set when on_stack is true"));
259 }
260 }
261 }
262
263 if lowlink[lit_idx] == index[lit_idx].expect("index set for lit_idx in SCC") {
265 let mut scc = Vec::new();
266 loop {
267 let node = stack.pop().expect("stack non-empty in SCC formation");
268 on_stack[node] = false;
269 scc.push(Lit::from_code(node as u32));
270 if node == lit_idx {
271 break;
272 }
273 }
274 sccs.push(scc);
275 }
276 }
277
278 pub fn optimize(&mut self, clauses: &mut ClauseDatabase) {
282 self.build(clauses);
284
285 let redundant = self.transitive_reduction();
287
288 let clause_ids: Vec<_> = clauses.iter_ids().collect();
290 for cid in clause_ids {
291 if let Some(clause) = clauses.get(cid)
292 && clause.len() == 2
293 {
294 let a = clause.lits[0];
295 let b = clause.lits[1];
296
297 if redundant.contains(&(!a, b)) || redundant.contains(&(!b, a)) {
299 clauses.remove(cid);
300 }
301 }
302 }
303 }
304
305 #[must_use]
307 pub fn get_implications(&self, lit: Lit) -> Vec<Lit> {
308 let idx = lit.code() as usize;
309 if idx < self.implications.len() {
310 self.implications[idx].iter().copied().collect()
311 } else {
312 Vec::new()
313 }
314 }
315
316 #[must_use]
318 pub fn implies(&self, a: Lit, b: Lit) -> bool {
319 let idx = a.code() as usize;
320 if idx < self.implications.len() {
321 self.implications[idx].contains(&b)
322 } else {
323 false
324 }
325 }
326
327 #[must_use]
329 pub fn stats(&self) -> &BigStats {
330 &self.stats
331 }
332
333 pub fn reset_stats(&mut self) {
335 self.stats = BigStats::default();
336 }
337}
338
339#[cfg(test)]
340mod tests {
341 use super::*;
342 use crate::literal::Var;
343
344 #[test]
345 fn test_big_creation() {
346 let big = BinaryImplicationGraph::new(10);
347 assert_eq!(big.stats().binary_clauses_analyzed, 0);
348 }
349
350 #[test]
351 fn test_build_from_clauses() {
352 let mut big = BinaryImplicationGraph::new(10);
353 let mut db = ClauseDatabase::new();
354
355 let a = Lit::pos(Var::new(0));
356 let b = Lit::pos(Var::new(1));
357
358 db.add_original(vec![a, b]);
359 big.build(&db);
360
361 assert_eq!(big.stats().binary_clauses_analyzed, 1);
362 assert!(big.implies(!a, b));
364 assert!(big.implies(!b, a));
365 }
366
367 #[test]
368 fn test_transitive_reduction() {
369 let mut big = BinaryImplicationGraph::new(10);
370
371 let a = Lit::pos(Var::new(0));
372 let b = Lit::pos(Var::new(1));
373 let c = Lit::pos(Var::new(2));
374
375 big.add_implication(a, b);
377 big.add_implication(b, c);
378 big.add_implication(a, c);
379
380 let redundant = big.transitive_reduction();
381
382 assert!(!redundant.is_empty());
384 assert!(redundant.contains(&(a, c)));
385 }
386
387 #[test]
388 fn test_find_sccs() {
389 let mut big = BinaryImplicationGraph::new(10);
390
391 let a = Lit::pos(Var::new(0));
392 let b = Lit::pos(Var::new(1));
393
394 big.add_implication(a, b);
396 big.add_implication(b, a);
397
398 let sccs = big.find_sccs();
399
400 assert!(!sccs.is_empty());
402 let scc = &sccs[0];
403 assert!(scc.contains(&a));
404 assert!(scc.contains(&b));
405 }
406
407 #[test]
408 fn test_get_implications() {
409 let mut big = BinaryImplicationGraph::new(10);
410
411 let a = Lit::pos(Var::new(0));
412 let b = Lit::pos(Var::new(1));
413
414 big.add_implication(a, b);
415
416 let implications = big.get_implications(a);
417 assert!(implications.contains(&b));
418 }
419
420 #[test]
421 fn test_optimize() {
422 let mut big = BinaryImplicationGraph::new(10);
423 let mut db = ClauseDatabase::new();
424
425 let a = Lit::pos(Var::new(0));
426 let b = Lit::pos(Var::new(1));
427 let c = Lit::pos(Var::new(2));
428
429 db.add_original(vec![a, b]); db.add_original(vec![b, c]); db.add_original(vec![a, c]); let before = db.len();
435 big.optimize(&mut db);
436 let after = db.len();
437
438 assert!(after <= before);
440 }
441}