1use std::collections::{HashMap, HashSet};
32
33use crate::aig::{Aig, AigLit, Node};
34use crate::cdcl::{BudgetedResult, Lit, Solver};
35use crate::lrat::{self, Lrat};
36use crate::sweep::{miter, Miter, MiterError, Verdict};
37
38#[derive(Clone, Copy, Debug)]
40pub struct Options {
41 pub sim_words: usize,
43 pub seed: u64,
44 pub budget: u64,
46 pub final_budget: u64,
48 pub rounds: usize,
53 pub attempts: usize,
55 pub recycle: usize,
57 pub collect: usize,
60 pub progress: Option<Progress>,
62}
63
64impl Default for Options {
65 fn default() -> Options {
66 Options {
67 sim_words: 16,
68 seed: 0x5eed_c0de_f00d_a16e,
69 budget: 30,
70 final_budget: 0,
71 attempts: 4,
72 rounds: 3,
73 recycle: 100_000,
74 collect: 100_000,
75 progress: None,
76 }
77 }
78}
79
80#[derive(Clone, Copy, Debug)]
83pub struct Progress {
84 pub every: usize,
85 pub report: fn(&Stats),
86}
87
88#[derive(Clone, Debug, Default, PartialEq, Eq)]
90pub struct Stats {
91 pub nodes: usize,
94 pub cone: usize,
95 pub swept: usize,
96 pub rounds: usize,
98 pub structural: usize,
100 pub queries: usize,
102 pub solvers: usize,
104 pub merges: usize,
105 pub counterexamples: usize,
106 pub unknown: usize,
107 pub conflicts: u64,
108 pub by_conflicts: [usize; 5],
110 pub learned: usize,
113 pub forgotten: usize,
114 pub log: usize,
115 pub hints: usize,
116 pub proof: usize,
117}
118
119#[derive(Clone, Debug)]
121pub struct Report {
122 pub miter: Miter,
123 pub verdicts: Vec<(String, Verdict)>,
124 pub proof: Lrat,
125 pub stats: Stats,
126}
127
128impl Report {
129 pub fn all_equal(&self) -> bool {
131 self.verdicts.iter().all(|(_, v)| *v == Verdict::Equal)
132 }
133
134 pub fn check(&self) -> bool {
136 let aig = &self.miter.aig;
137 let mut claims: Vec<Vec<Lit>> = Vec::new();
138 for ((_, a, b), (_, v)) in self.miter.pairs.iter().zip(&self.verdicts) {
139 match v {
140 Verdict::Equal if a == b => {}
141 Verdict::Equal if *a == !*b => return false,
142 Verdict::Equal => claims.extend(claim(*a, *b)),
143 Verdict::Differ { inputs, latches } => {
144 if inputs.len() != aig.inputs().len() || latches.len() != aig.latches().len() {
145 return false;
146 }
147 let values = aig.eval(inputs, latches);
148 if Aig::value(&values, *a) == Aig::value(&values, *b) {
149 return false;
150 }
151 }
152 Verdict::Unknown(_) => {}
153 }
154 }
155 lrat::check_derivation(aig.num_nodes(), &formula(aig), &self.proof, &claims)
156 }
157}
158
159fn lit(l: AigLit) -> Lit {
161 Lit::new(l.node(), !l.is_complemented())
162}
163
164pub(crate) fn claim(a: AigLit, b: AigLit) -> [Vec<Lit>; 2] {
166 [vec![lit(a).negated(), lit(b)], vec![lit(a), lit(b).negated()]]
167}
168
169pub fn formula(aig: &Aig) -> Vec<Vec<Lit>> {
171 let mut clauses = vec![vec![Lit::neg(0)]];
172 for n in 0..aig.num_nodes() as u32 {
173 if let Node::And(x, y) = aig.node(n) {
174 let o = Lit::pos(n);
175 clauses.push(vec![o.negated(), lit(x)]);
176 clauses.push(vec![o.negated(), lit(y)]);
177 clauses.push(vec![o, lit(x).negated(), lit(y).negated()]);
178 }
179 }
180 clauses
181}
182
183pub fn equivalent(left: &Aig, right: &Aig, opts: &Options) -> Result<Report, MiterError> {
185 let miter = miter(left, right)?;
186 let pairs: Vec<(AigLit, AigLit)> = miter.pairs.iter().map(|(_, a, b)| (*a, *b)).collect();
187 let (verdicts, proof, stats) = decide(&miter.aig, &pairs, opts);
188 let verdicts = miter.pairs.iter().map(|p| p.0.clone()).zip(verdicts).collect();
189 Ok(Report { miter, verdicts, proof, stats })
190}
191
192pub fn decide(aig: &Aig, pairs: &[(AigLit, AigLit)], opts: &Options) -> (Vec<Verdict>, Lrat, Stats) {
195 let rounds = opts.rounds.max(1);
196 let mut budget = opts.budget;
197 for round in 1..=rounds {
198 let last = round == rounds;
199 let pair_budget = if last { opts.final_budget } else { budget.saturating_mul(100) };
200 let (verdicts, proof, stats) = sweep_round(aig, pairs, opts, round, budget, pair_budget);
201 if last || !verdicts.iter().any(|v| matches!(v, Verdict::Unknown(_))) {
202 return (verdicts, proof, stats);
203 }
204 budget = budget.saturating_mul(10);
205 }
206 unreachable!("the last round returns")
207}
208
209fn sweep_round(aig: &Aig, pairs: &[(AigLit, AigLit)], opts: &Options, round: usize, budget: u64, pair_budget: u64) -> (Vec<Verdict>, Lrat, Stats) {
211 let mut e = Engine::new(aig, pairs, opts);
212 e.stats.rounds = round;
213 e.sweep(opts, budget);
214 e.report(opts);
215 let mut verdicts = Vec::with_capacity(pairs.len());
216 let mut claims: Vec<u64> = Vec::new();
217 for &(a, b) in pairs {
218 verdicts.push(e.decide_pair(a, b, pair_budget, &mut claims));
219 }
220 e.sync();
221 e.report(opts);
222 e.stats.log = e.log.len();
223 e.stats.conflicts += e.solver.conflicts();
224 let proof = e.derivation(&claims);
225 e.stats.proof = proof.steps.len();
226 (verdicts, proof, e.stats)
227}
228
229struct Sim {
232 words: usize,
233 inputs: Vec<u64>,
234 latches: Vec<u64>,
235 nodes: Vec<u32>,
237 row: Vec<u32>,
238 values: Vec<u64>,
239 used: usize,
241}
242
243impl Sim {
244 fn random(aig: &Aig, words: usize, seed: u64, nodes: Vec<u32>) -> Sim {
247 let mut state = seed;
248 let mut next = || {
249 state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
250 let mut z = state;
251 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
252 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
253 z ^ (z >> 31)
254 };
255 let inputs: Vec<u64> = (0..aig.inputs().len() * words).map(|_| next()).collect();
256 let latches: Vec<u64> = (0..aig.latches().len() * words).map(|_| next()).collect();
257 let mut row = vec![u32::MAX; aig.num_nodes()];
258 for (i, &n) in nodes.iter().enumerate() {
259 row[n as usize] = i as u32;
260 }
261 let mut sim = Sim { words, inputs, latches, nodes, row, values: Vec::new(), used: 64 };
262 sim.run(aig);
263 sim
264 }
265
266 fn run(&mut self, aig: &Aig) {
268 let words = self.words;
269 let mut v = vec![0u64; self.nodes.len() * words];
270 for (i, &n) in self.nodes.iter().enumerate() {
271 match aig.node(n) {
272 Node::False => {}
273 Node::Input(k) => v[i * words..(i + 1) * words].copy_from_slice(&self.inputs[k * words..(k + 1) * words]),
274 Node::Latch(k) => v[i * words..(i + 1) * words].copy_from_slice(&self.latches[k * words..(k + 1) * words]),
275 Node::And(a, b) => {
276 let (ra, rb) = (self.row[a.node() as usize] as usize, self.row[b.node() as usize] as usize);
277 let (ma, mb) = (if a.is_complemented() { !0 } else { 0 }, if b.is_complemented() { !0 } else { 0 });
278 for w in 0..words {
279 v[i * words + w] = (v[ra * words + w] ^ ma) & (v[rb * words + w] ^ mb);
280 }
281 }
282 }
283 }
284 self.values = v;
285 }
286
287 fn add(&mut self, aig: &Aig, inputs: &[bool], latches: &[bool]) {
290 if self.used == 64 {
291 let grow = |old: &[u64], values: &[bool], words: usize| -> Vec<u64> {
292 let mut out = Vec::with_capacity(values.len() * (words + 1));
293 for (i, &v) in values.iter().enumerate() {
294 out.extend_from_slice(&old[i * words..(i + 1) * words]);
295 out.push(if v { !0 } else { 0 });
296 }
297 out
298 };
299 self.inputs = grow(&self.inputs, inputs, self.words);
300 self.latches = grow(&self.latches, latches, self.words);
301 self.words += 1;
302 self.used = 1;
303 } else {
304 let (words, bit) = (self.words, self.used);
305 for (store, values) in [(&mut self.inputs, inputs), (&mut self.latches, latches)] {
306 for (i, &v) in values.iter().enumerate() {
307 let cell = &mut store[i * words + words - 1];
308 *cell = (*cell & !(1 << bit)) | ((v as u64) << bit);
309 }
310 }
311 self.used += 1;
312 }
313 self.run(aig);
314 }
315
316 fn signature(&self, n: u32) -> (Vec<u64>, bool) {
318 let at = self.row[n as usize] as usize;
319 let row = &self.values[at * self.words..(at + 1) * self.words];
320 let phase = row[0] & 1 == 1;
321 (row.iter().map(|&w| if phase { !w } else { w }).collect(), phase)
322 }
323}
324
325const LEARNED_IDS: u64 = 1 << 62;
327
328
329struct Step {
332 id: u64,
333 clause: Vec<Lit>,
334 hints: Vec<u64>,
335 learned: bool,
336}
337
338struct Engine<'a> {
339 aig: &'a Aig,
340 formula: Vec<Vec<Lit>>,
341 def_id: Vec<u64>,
343 solver: Solver,
344 log: Vec<Step>,
347 next_id: u64,
348 at: Vec<u32>,
349 segment: usize,
352 learned: HashMap<u64, u64>,
353 collect: usize,
354 collect_at: usize,
355 defs: Vec<Option<[u64; 3]>>,
358 lemmas: Vec<Option<[u64; 2]>>,
360 local: Vec<u32>,
362 defined: Vec<bool>,
363 nodes: Vec<u32>,
365 recycle: usize,
366 repr: Vec<AigLit>,
368 strash: HashMap<(AigLit, AigLit), u32>,
370 shared: HashSet<u32>,
373 reached: Vec<bool>,
375 sim: Sim,
376 stats: Stats,
377}
378
379impl<'a> Engine<'a> {
380 fn new(aig: &'a Aig, pairs: &[(AigLit, AigLit)], opts: &Options) -> Engine<'a> {
381 let left: HashSet<u32> = aig.cone(&pairs.iter().map(|p| p.0).collect::<Vec<_>>()).into_iter().collect();
382 let right: HashSet<u32> = aig.cone(&pairs.iter().map(|p| p.1).collect::<Vec<_>>()).into_iter().collect();
383 let mut reached = vec![false; aig.num_nodes()];
384 for &n in left.iter().chain(&right) {
385 reached[n as usize] = true;
386 }
387 let cone: Vec<u32> = (0..aig.num_nodes() as u32).filter(|&n| reached[n as usize]).collect();
388 let formula = formula(aig);
389 let mut def_id = vec![0u64; aig.num_nodes()];
390 let mut next = 2u64;
391 for n in 0..aig.num_nodes() as u32 {
392 if let Node::And(..) = aig.node(n) {
393 def_id[n as usize] = next;
394 next += 3;
395 }
396 }
397 let mut e = Engine {
398 aig,
399 next_id: formula.len() as u64 + 1,
400 at: Vec::new(),
401 formula,
402 def_id,
403 solver: Solver::new(0),
404 log: Vec::new(),
405 segment: 0,
406 learned: HashMap::new(),
407 collect: opts.collect.max(1),
408 collect_at: opts.collect.max(1),
409 defs: vec![None; aig.num_nodes()],
410 lemmas: vec![None; aig.num_nodes()],
411 local: vec![0; aig.num_nodes()],
412 defined: vec![false; aig.num_nodes()],
413 nodes: Vec::new(),
414 recycle: opts.recycle.max(1),
415 repr: (0..aig.num_nodes() as u32).map(|n| AigLit::new(n, false)).collect(),
416 strash: HashMap::new(),
417 shared: left.intersection(&right).copied().collect(),
418 stats: Stats { nodes: aig.num_nodes(), cone: cone.len(), ..Stats::default() },
419 reached,
420 sim: Sim::random(aig, opts.sim_words.max(1), opts.seed, cone),
421 };
422 e.fresh();
423 e
424 }
425
426 fn place(&self, id: u64) -> Option<usize> {
428 let k = id.checked_sub(self.formula.len() as u64 + 1)?;
429 match self.at.get(k as usize) {
430 Some(&p) if p > 0 => Some(p as usize - 1),
431 _ => None,
432 }
433 }
434
435 fn reached(&self, from: usize, roots: impl IntoIterator<Item = u64>) -> Vec<bool> {
438 let mut keep = vec![false; self.log.len() - from];
439 let mut stack: Vec<u64> = roots.into_iter().collect();
440 while let Some(id) = stack.pop() {
441 if let Some(k) = self.place(id).and_then(|p| p.checked_sub(from)) {
442 if !std::mem::replace(&mut keep[k], true) {
443 stack.extend(&self.log[from + k].hints);
444 }
445 }
446 }
447 keep
448 }
449
450 fn collect(&mut self, replacing: bool) {
453 let start = self.segment;
454 let mut roots: Vec<u64> = self.log[start..].iter().filter(|s| !s.learned).map(|s| s.id).collect();
455 let live: HashSet<u64> = if replacing { HashSet::new() } else { self.solver.live_ids().iter().copied().collect() };
456 self.learned.retain(|local, _| live.contains(local));
457 roots.extend(self.learned.values());
458 let mut keep = self.reached(start, roots).into_iter();
459 let tail = self.log.split_off(start);
460 let held = tail.len();
461 let base = self.formula.len() as u64 + 1;
462 for (step, kept) in tail.into_iter().zip(&mut keep) {
463 let k = (step.id - base) as usize;
464 if kept {
465 self.at[k] = self.log.len() as u32 + 1;
466 self.log.push(step);
467 } else {
468 self.at[k] = 0;
469 self.stats.hints -= step.hints.len();
470 }
471 }
472 self.stats.forgotten += held - (self.log.len() - start);
473 self.collect_at = (2 * (self.log.len() - start)).saturating_add(self.collect);
474 }
475
476 fn fresh(&mut self) {
479 self.sync();
480 self.collect(true);
481 self.segment = self.log.len();
482 self.collect_at = self.collect;
483 self.stats.conflicts += self.solver.conflicts();
484 for &n in &self.nodes {
485 self.local[n as usize] = 0;
486 self.defined[n as usize] = false;
487 }
488 self.nodes.clear();
489 self.learned.clear();
490 self.solver = Solver::new(0);
491 self.solver.set_lrat(true);
492 self.solver.set_next_clause_id(LEARNED_IDS);
493 self.solver.set_decision_vars(&[]);
494 let zero = self.var(0);
495 self.solver.add_clause_with_id(vec![Lit::neg(zero)], 1);
496 self.defined[0] = true;
497 self.stats.solvers += 1;
498 }
499
500 fn var(&mut self, n: u32) -> u32 {
502 if self.local[n as usize] == 0 {
503 let v = self.solver.add_vars(1);
504 self.solver.add_decision_vars(&[v]);
505 self.local[n as usize] = v as u32 + 1;
506 self.nodes.push(n);
507 }
508 self.local[n as usize] - 1
509 }
510
511 fn to_local(&mut self, l: Lit) -> Lit {
513 Lit::new(self.var(l.var()), l.is_positive())
514 }
515
516 fn clause_of(&self, id: u64) -> Option<&[Lit]> {
518 if id == 0 {
519 return None;
520 }
521 match self.formula.get(id as usize - 1) {
522 Some(c) => Some(c),
523 None => self.place(id).map(|k| self.log[k].clause.as_slice()),
524 }
525 }
526
527 fn give(&mut self, id: u64) {
529 let Some(clause) = self.clause_of(id).map(<[Lit]>::to_vec) else { return };
530 let local: Vec<Lit> = clause.into_iter().map(|l| self.to_local(l)).collect();
531 self.solver.add_clause_with_id(local, id);
532 }
533
534 fn push(&mut self, clause: Vec<Lit>, hints: Vec<u64>, learned: bool) -> u64 {
536 let id = self.next_id;
537 self.next_id += 1;
538 self.at.push(self.log.len() as u32 + 1);
539 self.stats.hints += hints.len();
540 self.log.push(Step { id, clause, hints, learned });
541 id
542 }
543
544 fn global(&self, hint: u64) -> u64 {
547 if hint >= LEARNED_IDS {
548 self.learned.get(&hint).copied().unwrap_or(0)
549 } else {
550 hint
551 }
552 }
553
554 fn sync(&mut self) {
557 let learned = self.solver.take_learned();
558 self.stats.learned += learned.len();
559 for c in learned {
560 let mut clause = c.lits;
561 for l in &mut clause {
562 *l = Lit::new(self.nodes[l.var() as usize], l.is_positive());
563 }
564 let mut hints = c.hints;
565 for h in &mut hints {
566 *h = self.global(*h);
567 }
568 let id = self.push(clause, hints, true);
569 self.learned.insert(c.id, id);
570 }
571 if self.log.len() - self.segment >= self.collect_at {
572 self.collect(false);
573 }
574 }
575
576 fn chain(&self, clause: &[Lit], candidates: &[u64]) -> Option<Vec<u64>> {
579 let mut value: HashMap<u32, bool> = clause.iter().map(|l| (l.var(), !l.is_positive())).collect();
580 let mut used = vec![false; candidates.len()];
581 let mut hints = Vec::new();
582 loop {
583 let mut progressed = false;
584 for (k, &id) in candidates.iter().enumerate() {
585 let Some(c) = self.clause_of(id).filter(|_| !used[k]) else { continue };
586 let mut open: Option<Lit> = None;
587 let mut quiet = false;
588 for &l in c {
589 match value.get(&l.var()) {
590 Some(&v) if v == l.is_positive() => quiet = true,
591 Some(_) => {}
592 None if open.is_none() || open == Some(l) => open = Some(l),
593 None => quiet = true,
594 }
595 if quiet {
596 break;
597 }
598 }
599 if quiet {
600 continue;
601 }
602 used[k] = true;
603 hints.push(id);
604 let Some(u) = open else { return Some(hints) };
605 value.insert(u.var(), u.is_positive());
606 progressed = true;
607 }
608 if !progressed {
609 return None;
610 }
611 }
612 }
613
614 fn derive(&mut self, clause: Vec<Lit>, candidates: &[u64]) -> u64 {
617 let hints = self.chain(&clause, candidates).unwrap_or_default();
618 self.sync();
619 self.push(clause, hints, false)
620 }
621
622 fn about(&self, n: u32) -> impl Iterator<Item = u64> + '_ {
624 let f = self.def_id[n as usize];
625 let definition = matches!(self.aig.node(n), Node::And(..)).then_some([f, f + 1, f + 2]);
626 definition.into_iter().flatten().chain(self.lemmas[n as usize].into_iter().flatten())
627 }
628
629 fn around(&self, nodes: &[u32]) -> Vec<u64> {
631 let mut near: Vec<u32> = nodes.to_vec();
632 for &n in nodes {
633 if let Node::And(x, y) = self.aig.node(n) {
634 near.extend([x.node(), y.node()]);
635 }
636 }
637 near.sort_unstable();
638 near.dedup();
639 let mut ids = vec![1];
640 for n in near {
641 ids.extend(self.about(n));
642 }
643 ids
644 }
645
646 fn encode(&mut self, roots: &[AigLit]) {
649 let mut stack: Vec<u32> = roots.iter().map(|l| l.node()).collect();
650 while let Some(n) = stack.pop() {
651 self.var(n);
652 if std::mem::replace(&mut self.defined[n as usize], true) {
653 continue;
654 }
655 if let Node::And(x, y) = self.aig.node(n) {
656 let (rx, ry) = (self.resolve(x), self.resolve(y));
657 let ids = match self.defs[n as usize] {
658 Some(ids) => ids,
659 None => {
660 let o = Lit::pos(n);
661 let near = self.around(&[n]);
662 let ids = [
663 self.derive(vec![o.negated(), lit(rx)], &near),
664 self.derive(vec![o.negated(), lit(ry)], &near),
665 self.derive(vec![o, lit(rx).negated(), lit(ry).negated()], &near),
666 ];
667 self.defs[n as usize] = Some(ids);
668 ids
669 }
670 };
671 for id in ids {
672 self.give(id);
673 }
674 stack.push(rx.node());
675 stack.push(ry.node());
676 }
677 }
678 }
679
680 fn report(&self, opts: &Options) {
682 if let Some(p) = opts.progress {
683 let mut so_far = self.stats.clone();
684 so_far.conflicts += self.solver.conflicts();
685 so_far.log = self.log.len();
686 (p.report)(&so_far);
687 }
688 }
689
690 fn resolve(&self, l: AigLit) -> AigLit {
691 self.repr[l.node() as usize].with_phase(l.is_complemented())
692 }
693
694 fn merge(&mut self, n: u32, t: AigLit, m: Option<u32>) {
698 let near = self.around(&[n].into_iter().chain(m).collect::<Vec<_>>());
699 let [a, b] = claim(AigLit::new(n, false), t);
700 self.lemmas[n as usize] = Some([self.derive(a, &near), self.derive(b, &near)]);
701 self.repr[n as usize] = t;
702 }
703
704 fn prove(&mut self, a: AigLit, b: AigLit, budget: u64) -> Proved {
708 if self.nodes.len() >= self.recycle {
709 self.fresh();
710 }
711 self.encode(&[a, b]);
712 let mut ids = [0u64; 2];
713 for (k, (x, y)) in [(a, !b), (!a, b)].into_iter().enumerate() {
714 self.stats.queries += 1;
715 let before = self.solver.conflicts();
716 let assumptions = [self.to_local(lit(x)), self.to_local(lit(y))];
717 let result = self.solver.solve_under_assumptions_budgeted(&assumptions, budget);
718 let took = self.solver.conflicts() - before;
719 self.stats.by_conflicts[[0, 10, 100, 1000].iter().take_while(|&&b| took > b).count()] += 1;
720 match result {
721 BudgetedResult::Unsat => {
722 self.sync();
723 let hints: Vec<u64> = self.solver.unsat_hints().iter().map(|&h| self.global(h)).collect();
724 ids[k] = self.push(vec![lit(x).negated(), lit(y).negated()], hints, false);
725 self.give(ids[k]);
726 }
727 BudgetedResult::Sat(model) => {
728 let value = |n: u32| self.local[n as usize] > 0 && model[self.local[n as usize] as usize - 1];
729 let inputs: Vec<bool> = self.aig.inputs().iter().map(|p| value(p.lit.node())).collect();
730 let latches: Vec<bool> = (0..self.aig.latches().len()).map(|j| value(self.aig.latch_lit(j).node())).collect();
731 let values = self.aig.eval(&inputs, &latches);
732 return if Aig::value(&values, a) != Aig::value(&values, b) {
733 Proved::Differ(inputs, latches)
734 } else {
735 Proved::Unknown("the solver's model does not separate the pair".into())
736 };
737 }
738 BudgetedResult::Budget => return Proved::Unknown(format!("no verdict within {budget} conflicts")),
739 }
740 }
741 Proved::Equal(ids)
742 }
743
744 fn structural(&mut self, n: u32, x: AigLit, y: AigLit) -> Option<(AigLit, Option<u32>)> {
747 let (rx, ry) = (self.resolve(x), self.resolve(y));
748 if rx == AigLit::FALSE || ry == AigLit::FALSE || rx == !ry {
749 return Some((AigLit::FALSE, None));
750 }
751 if rx == AigLit::TRUE || rx == ry {
752 return Some((ry, None));
753 }
754 if ry == AigLit::TRUE {
755 return Some((rx, None));
756 }
757 let key = if rx.code() <= ry.code() { (rx, ry) } else { (ry, rx) };
758 match self.strash.get(&key) {
759 Some(&m) => Some((self.resolve(AigLit::new(m, false)), Some(m))),
760 None => {
761 self.strash.insert(key, n);
762 None
763 }
764 }
765 }
766
767 fn sweep(&mut self, opts: &Options, budget: u64) {
768 let aig = self.aig;
769 let mut classes: HashMap<Vec<u64>, u32> = HashMap::new();
771 let mut reps: Vec<u32> = Vec::new();
772 for n in 0..aig.num_nodes() as u32 {
773 if !self.reached[n as usize] {
774 continue;
775 }
776 self.stats.swept += 1;
777 if opts.progress.is_some_and(|p| p.every > 0 && self.stats.swept % p.every == 0) {
778 self.report(opts);
779 }
780 if let Node::And(x, y) = aig.node(n) {
781 if let Some((t, m)) = self.structural(n, x, y) {
782 self.merge(n, t, m);
783 self.stats.structural += 1;
784 continue;
785 }
786 }
787 let candidate = matches!(aig.node(n), Node::And(..)) && !self.shared.contains(&n);
788 let mut attempts = 0;
789 loop {
790 let (key, phase) = self.sim.signature(n);
791 let Some(&r) = classes.get(&key) else {
792 classes.insert(key, n);
793 break;
794 };
795 if !candidate || attempts == opts.attempts {
796 break;
797 }
798 attempts += 1;
799 let (_, phase_r) = self.sim.signature(r);
800 let target = AigLit::new(r, phase ^ phase_r);
801 match self.prove(AigLit::new(n, false), target, budget) {
802 Proved::Equal(ids) => {
803 self.lemmas[n as usize] = Some(ids);
804 self.repr[n as usize] = target;
805 self.stats.merges += 1;
806 break;
807 }
808 Proved::Differ(inputs, latches) => {
809 self.stats.counterexamples += 1;
810 self.sim.add(aig, &inputs, &latches);
811 classes.clear();
812 for &m in &reps {
813 classes.entry(self.sim.signature(m).0).or_insert(m);
814 }
815 }
816 Proved::Unknown(_) => {
817 self.stats.unknown += 1;
818 break;
819 }
820 }
821 }
822 if self.repr[n as usize] == AigLit::new(n, false) {
823 reps.push(n);
824 }
825 }
826 }
827
828 fn decide_pair(&mut self, a: AigLit, b: AigLit, budget: u64, claims: &mut Vec<u64>) -> Verdict {
831 if a == b {
832 return Verdict::Equal;
833 }
834 let (ra, rb) = (self.resolve(a), self.resolve(b));
835 let mut near: Vec<u64> = vec![1];
836 near.extend(self.lemmas[a.node() as usize].into_iter().flatten());
837 near.extend(self.lemmas[b.node() as usize].into_iter().flatten());
838 if ra != rb {
839 match self.prove(ra, rb, budget) {
840 Proved::Equal(ids) => near.extend(ids),
841 Proved::Differ(inputs, latches) => return Verdict::Differ { inputs, latches },
842 Proved::Unknown(why) => {
843 self.stats.unknown += 1;
844 return Verdict::Unknown(why);
845 }
846 }
847 }
848 let [x, y] = claim(a, b);
849 claims.push(self.derive(x, &near));
850 claims.push(self.derive(y, &near));
851 Verdict::Equal
852 }
853
854 fn derivation(&self, claims: &[u64]) -> Lrat {
856 let keep = self.reached(0, claims.iter().copied());
857 let steps = self
858 .log
859 .iter()
860 .zip(keep)
861 .filter(|(_, k)| *k)
862 .map(|(s, _)| lrat::LratStep { id: s.id, clause: s.clause.clone(), hints: s.hints.clone() })
863 .collect();
864 Lrat { steps }
865 }
866}
867
868enum Proved {
870 Equal([u64; 2]),
872 Differ(Vec<bool>, Vec<bool>),
873 Unknown(String),
874}
875
876#[cfg(test)]
877#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/fraig/tests.rs"]
878mod tests;