1use std::collections::{HashMap, HashSet};
28
29use crate::aig::{Aig, AigLit, Leaf, Node};
30use crate::cdcl::{BudgetedResult, Lit, Solver};
31use crate::lrat::{self, Lrat};
32
33#[derive(Clone, Debug)]
35pub struct Lemma {
36 pub a: AigLit,
37 pub b: AigLit,
38 pub cut: Vec<u32>,
40 pub proof: Lrat,
41}
42
43#[derive(Clone, Debug, Default)]
45pub struct Certificate {
46 pub lemmas: Vec<Lemma>,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub enum Verdict {
52 Equal,
53 Differ { inputs: Vec<bool>, latches: Vec<bool> },
55 Unknown(String),
57}
58
59#[derive(Clone, Debug)]
61pub struct Options {
62 pub sweep: bool,
64 pub sim_words: usize,
66 pub seed: u64,
67 pub sweep_budget: u64,
69 pub final_budget: u64,
71 pub progress: Option<Progress>,
73}
74
75#[derive(Clone, Copy, Debug)]
77pub struct Progress {
78 pub every: usize,
79 pub report: fn(&Stats),
80}
81
82impl Default for Options {
83 fn default() -> Options {
84 Options { sweep: true, sim_words: 8, seed: 0x5eed_c0de_f00d_a16e, sweep_budget: 20_000, final_budget: 0, progress: None }
85 }
86}
87
88#[derive(Clone, Debug, Default, PartialEq, Eq)]
90pub struct Stats {
91 pub sat_calls: usize,
92 pub merges: usize,
93 pub counterexamples: usize,
94 pub unknown: usize,
95 pub hardest: Vec<Hard>,
97 pub by_cut: [CutCost; CUT_KINDS],
100 pub cone: usize,
102 pub swept: usize,
103}
104
105#[derive(Clone, Debug, Default, PartialEq, Eq)]
107pub struct CutCost {
108 pub queries: usize,
109 pub refuted: usize,
110 pub models: usize,
111 pub budget: usize,
112 pub vars: u64,
113 pub clauses: u64,
114 pub conflicts: u64,
115}
116
117#[derive(Clone, Debug, PartialEq, Eq)]
120pub struct Hard {
121 pub a: AigLit,
122 pub b: AigLit,
123 pub cut: usize,
124 pub vars: usize,
125 pub conflicts: u64,
126 pub verdict: &'static str,
127}
128
129pub const HARDEST: usize = 16;
131
132impl Stats {
133 fn note(&mut self, h: Hard) {
134 let at = self.hardest.partition_point(|x| x.conflicts >= h.conflicts);
135 if at < HARDEST {
136 self.hardest.insert(at, h);
137 self.hardest.truncate(HARDEST);
138 }
139 }
140}
141
142#[derive(Clone, Debug)]
145pub struct Query {
146 pub cone: Vec<u32>,
147 pub extra: Vec<u32>,
148 pub num_vars: usize,
149 pub clauses: Vec<Vec<Lit>>,
150}
151
152fn cone_within(aig: &Aig, roots: &[AigLit], cut: &[u32]) -> Vec<u32> {
154 let mut seen: HashSet<u32> = HashSet::new();
155 let mut stack: Vec<u32> = roots.iter().map(|l| l.node()).collect();
156 let mut out = Vec::new();
157 while let Some(n) = stack.pop() {
158 if !seen.insert(n) {
159 continue;
160 }
161 out.push(n);
162 if cut.binary_search(&n).is_ok() {
163 continue;
164 }
165 if let Node::And(x, y) = aig.node(n) {
166 stack.push(x.node());
167 stack.push(y.node());
168 }
169 }
170 out.sort_unstable();
171 out
172}
173
174#[derive(Clone, Debug, Default)]
177pub struct Earlier {
178 pairs: Vec<(AigLit, AigLit)>,
179 by_node: HashMap<u32, Vec<u32>>,
180}
181
182impl Earlier {
183 pub fn new() -> Earlier {
184 Earlier::default()
185 }
186
187 pub fn push(&mut self, a: AigLit, b: AigLit) {
189 let k = self.pairs.len() as u32;
190 self.pairs.push((a, b));
191 self.by_node.entry(a.node()).or_default().push(k);
192 if b.node() != a.node() {
193 self.by_node.entry(b.node()).or_default().push(k);
194 }
195 }
196
197 pub fn pairs(&self) -> &[(AigLit, AigLit)] {
198 &self.pairs
199 }
200
201 fn touching(&self, cone: &[u32]) -> Vec<(AigLit, AigLit)> {
203 let mut at: Vec<u32> = cone.iter().filter_map(|n| self.by_node.get(n)).flatten().copied().collect();
204 at.sort_unstable();
205 at.dedup();
206 at.into_iter().map(|k| self.pairs[k as usize]).collect()
207 }
208}
209
210impl From<&[(AigLit, AigLit)]> for Earlier {
211 fn from(pairs: &[(AigLit, AigLit)]) -> Earlier {
212 let mut e = Earlier::new();
213 for &(a, b) in pairs {
214 e.push(a, b);
215 }
216 e
217 }
218}
219
220pub fn query(aig: &Aig, a: AigLit, b: AigLit, earlier: &[(AigLit, AigLit)]) -> Query {
222 query_with_cut(aig, a, b, &[], earlier)
223}
224
225pub fn query_with_cut(aig: &Aig, a: AigLit, b: AigLit, cut: &[u32], earlier: &[(AigLit, AigLit)]) -> Query {
230 query_indexed(aig, a, b, cut, &Earlier::from(earlier))
231}
232
233fn query_indexed(aig: &Aig, a: AigLit, b: AigLit, cut: &[u32], earlier: &Earlier) -> Query {
235 let cone = cone_within(aig, &[a, b], cut);
236 let inside = |n: u32| cone.binary_search(&n).is_ok();
237 let touching = earlier.touching(&cone);
238 let mut extra: Vec<u32> =
239 touching.iter().flat_map(|(p, q)| [p.node(), q.node()]).filter(|&n| !inside(n)).collect();
240 extra.sort_unstable();
241 extra.dedup();
242 let var = |n: u32| match cone.binary_search(&n) {
243 Ok(k) => k,
244 Err(_) => cone.len() + extra.binary_search(&n).expect("a node of the cone or of a lemma touching it"),
245 };
246 let lit = |l: AigLit| Lit::new(var(l.node()) as u32, !l.is_complemented());
247 let mut clauses = Vec::with_capacity(3 * cone.len() + 2 * touching.len() + 2);
248 for (k, &n) in cone.iter().enumerate() {
249 if cut.binary_search(&n).is_ok() {
250 continue;
251 }
252 match aig.node(n) {
253 Node::False => clauses.push(vec![Lit::neg(k as u32)]),
254 Node::And(x, y) => {
255 let o = Lit::pos(k as u32);
256 clauses.push(vec![o.negated(), lit(x)]);
257 clauses.push(vec![o.negated(), lit(y)]);
258 clauses.push(vec![o, lit(x).negated(), lit(y).negated()]);
259 }
260 Node::Input(_) | Node::Latch(_) => {}
261 }
262 }
263 for (p, q) in touching {
264 clauses.push(vec![lit(p).negated(), lit(q)]);
265 clauses.push(vec![lit(p), lit(q).negated()]);
266 }
267 clauses.push(vec![lit(a), lit(b)]);
268 clauses.push(vec![lit(a).negated(), lit(b).negated()]);
269 Query { num_vars: cone.len() + extra.len(), cone, extra, clauses }
270}
271
272fn frontier(aig: &Aig, roots: &[AigLit], depth: usize) -> Vec<u32> {
275 let mut level: HashMap<u32, usize> = HashMap::new();
276 let mut current: Vec<u32> = roots.iter().map(|l| l.node()).collect();
277 for &n in ¤t {
278 level.insert(n, 0);
279 }
280 for d in 1..=depth {
281 let mut next = Vec::new();
282 for &n in ¤t {
283 if let Node::And(x, y) = aig.node(n) {
284 for c in [x.node(), y.node()] {
285 if !level.contains_key(&c) {
286 level.insert(c, d);
287 next.push(c);
288 }
289 }
290 }
291 }
292 current = next;
293 }
294 let mut cut: Vec<u32> = current.into_iter().filter(|&n| matches!(aig.node(n), Node::And(..))).collect();
295 cut.sort_unstable();
296 cut
297}
298
299pub(crate) enum Outcome {
300 Equal(Lrat),
301 Differ(Vec<bool>, Vec<bool>),
302 Unknown(String),
303}
304
305pub(crate) fn prove(aig: &Aig, a: AigLit, b: AigLit, earlier: &[(AigLit, AigLit)], budget: u64) -> Outcome {
307 prove_noted(aig, a, b, &[], &Earlier::from(earlier), budget, None)
308}
309
310fn prove_noted(
313 aig: &Aig,
314 a: AigLit,
315 b: AigLit,
316 cut: &[u32],
317 earlier: &Earlier,
318 budget: u64,
319 noted: Option<(&mut Stats, usize)>,
320) -> Outcome {
321 let q = query_indexed(aig, a, b, cut, earlier);
322 let mut solver = Solver::new(q.num_vars);
323 for c in &q.clauses {
324 solver.add_clause(c.clone());
325 }
326 let result = solver.solve_budgeted(budget);
327 if let Some((stats, kind)) = noted {
328 let cost = &mut stats.by_cut[kind];
329 cost.queries += 1;
330 cost.vars += q.num_vars as u64;
331 cost.clauses += q.clauses.len() as u64;
332 cost.conflicts += solver.conflicts();
333 let verdict = match &result {
334 BudgetedResult::Sat(_) => {
335 cost.models += 1;
336 "model"
337 }
338 BudgetedResult::Unsat => {
339 cost.refuted += 1;
340 "refuted"
341 }
342 BudgetedResult::Budget => {
343 cost.budget += 1;
344 "budget"
345 }
346 };
347 stats.note(Hard { a, b, cut: cut.len(), vars: q.num_vars, conflicts: solver.conflicts(), verdict });
348 }
349 match result {
350 BudgetedResult::Sat(model) => {
351 let mut inputs = vec![false; aig.inputs().len()];
352 let mut latches = vec![false; aig.latches().len()];
353 for (k, &n) in q.cone.iter().enumerate() {
354 match aig.node(n) {
355 Node::Input(i) => inputs[i] = model[k],
356 Node::Latch(j) => latches[j] = model[k],
357 _ => {}
358 }
359 }
360 let v = aig.eval(&inputs, &latches);
361 if Aig::value(&v, a) != Aig::value(&v, b) {
362 Outcome::Differ(inputs, latches)
363 } else {
364 Outcome::Unknown("the solver's model does not separate the pair".into())
365 }
366 }
367 BudgetedResult::Unsat => {
368 let learned: Vec<Vec<Lit>> = solver.learned().iter().map(|c| c.lits.clone()).collect();
369 match lrat::refutation(q.num_vars, &q.clauses, &learned) {
370 Some(proof) if lrat::check(q.num_vars, &q.clauses, &proof) => Outcome::Equal(proof),
371 _ => Outcome::Unknown("the solver's refutation does not replay".into()),
372 }
373 }
374 BudgetedResult::Budget => Outcome::Unknown(format!("no verdict within {budget} conflicts")),
375 }
376}
377
378pub const CUT_DEPTHS: [usize; 2] = [6, 16];
380
381pub const CUT_KINDS: usize = CUT_DEPTHS.len() + 2;
383
384fn meeting(aig: &Aig, a: AigLit, b: AigLit, repr: &[AigLit]) -> Vec<u32> {
389 let class = |n: u32| repr[n as usize].node();
390 let classes = |r: AigLit| -> HashSet<u32> { cone_within(aig, &[r], &[]).into_iter().map(class).collect() };
391 let (left, right) = (classes(a), classes(b));
392 let met = |n: u32| {
393 let c = class(n);
394 left.contains(&c) && right.contains(&c)
395 };
396 let mut cut = Vec::new();
397 let mut seen: HashSet<u32> = HashSet::new();
398 let mut stack = vec![a.node(), b.node()];
399 while let Some(n) = stack.pop() {
400 if !seen.insert(n) {
401 continue;
402 }
403 let Node::And(x, y) = aig.node(n) else { continue };
404 if n != a.node() && n != b.node() && met(n) {
405 cut.push(n);
406 continue;
407 }
408 stack.push(x.node());
409 stack.push(y.node());
410 }
411 cut.sort_unstable();
412 cut
413}
414
415fn prove_local(
420 aig: &Aig,
421 a: AigLit,
422 b: AigLit,
423 earlier: &Earlier,
424 repr: &[AigLit],
425 budget: u64,
426 stats: &mut Stats,
427) -> (Outcome, Vec<u32>) {
428 let mut tried: Vec<Vec<u32>> = Vec::new();
429 for (kind, &depth) in CUT_DEPTHS.iter().enumerate() {
430 let cut = frontier(aig, &[a, b], depth);
431 if cut.is_empty() {
432 break;
433 }
434 match prove_noted(aig, a, b, &cut, earlier, budget, Some((&mut *stats, kind))) {
435 Outcome::Equal(proof) => return (Outcome::Equal(proof), cut),
436 Outcome::Differ(inputs, latches) => return (Outcome::Differ(inputs, latches), Vec::new()),
437 Outcome::Unknown(_) => tried.push(cut),
438 }
439 }
440 let met = meeting(aig, a, b, repr);
441 if !met.is_empty() && !tried.contains(&met) {
442 match prove_noted(aig, a, b, &met, earlier, budget, Some((&mut *stats, CUT_DEPTHS.len()))) {
443 Outcome::Equal(proof) => return (Outcome::Equal(proof), met),
444 Outcome::Differ(inputs, latches) => return (Outcome::Differ(inputs, latches), Vec::new()),
445 Outcome::Unknown(_) => {}
446 }
447 }
448 (prove_noted(aig, a, b, &[], earlier, budget, Some((stats, CUT_KINDS - 1))), Vec::new())
449}
450
451fn tick(stats: &Stats, opts: &Options) {
453 if let Some(p) = opts.progress {
454 if p.every > 0 && stats.sat_calls % p.every == 0 {
455 (p.report)(stats);
456 }
457 }
458}
459
460pub(crate) fn witness(aig: &Aig, lit: AigLit, budget: u64) -> Result<Option<(Vec<bool>, Vec<bool>)>, String> {
463 let q = query(aig, lit, AigLit::FALSE, &[]);
464 let mut solver = Solver::new(q.num_vars);
465 for c in &q.clauses {
466 solver.add_clause(c.clone());
467 }
468 match solver.solve_budgeted(budget) {
469 BudgetedResult::Sat(model) => {
470 let mut inputs = vec![false; aig.inputs().len()];
471 let mut latches = vec![false; aig.latches().len()];
472 for (k, &n) in q.cone.iter().enumerate() {
473 match aig.node(n) {
474 Node::Input(i) => inputs[i] = model[k],
475 Node::Latch(j) => latches[j] = model[k],
476 _ => {}
477 }
478 }
479 let v = aig.eval(&inputs, &latches);
480 if Aig::value(&v, lit) {
481 Ok(Some((inputs, latches)))
482 } else {
483 Err("the solver's model does not make the literal true".into())
484 }
485 }
486 BudgetedResult::Unsat => Ok(None),
487 BudgetedResult::Budget => Err(format!("no verdict within {budget} conflicts")),
488 }
489}
490
491#[derive(Clone, Debug)]
493pub struct Decided {
494 pub verdicts: Vec<Verdict>,
495 pub certificate: Certificate,
496 pub stats: Stats,
497}
498
499pub fn decide(aig: &Aig, pairs: &[(AigLit, AigLit)], opts: &Options) -> Decided {
501 let mut s = Sweeper {
502 aig,
503 repr: (0..aig.num_nodes() as u32).map(|n| AigLit::new(n, false)).collect(),
504 lemmas: Vec::new(),
505 earlier: Earlier::new(),
506 stats: Stats::default(),
507 };
508 if opts.sweep {
509 s.sweep(pairs, opts);
510 }
511 let mut verdicts = Vec::with_capacity(pairs.len());
512 for &(a, b) in pairs {
513 if s.resolve(a) == s.resolve(b) {
514 verdicts.push(Verdict::Equal);
515 continue;
516 }
517 s.stats.sat_calls += 1;
518 let (outcome, cut) = prove_local(aig, a, b, &s.earlier, &s.repr, opts.final_budget, &mut s.stats);
519 tick(&s.stats, opts);
520 verdicts.push(match outcome {
521 Outcome::Equal(proof) => {
522 s.learn(a, b, cut, proof);
523 Verdict::Equal
524 }
525 Outcome::Differ(inputs, latches) => Verdict::Differ { inputs, latches },
526 Outcome::Unknown(why) => {
527 s.stats.unknown += 1;
528 Verdict::Unknown(why)
529 }
530 });
531 }
532 Decided { verdicts, certificate: Certificate { lemmas: s.lemmas }, stats: s.stats }
533}
534
535const ATTEMPTS: usize = 8;
537
538struct Sweeper<'a> {
539 aig: &'a Aig,
540 repr: Vec<AigLit>,
542 lemmas: Vec<Lemma>,
543 earlier: Earlier,
544 stats: Stats,
545}
546
547struct Patterns {
549 inputs: Vec<u64>,
550 latches: Vec<u64>,
551 words: usize,
552 used: usize,
554}
555
556impl Patterns {
557 fn random(aig: &Aig, words: usize, seed: u64) -> Patterns {
558 let mut state = seed;
559 let mut next = || {
560 state = state.wrapping_add(0x9e37_79b9_7f4a_7c15);
561 let mut z = state;
562 z = (z ^ (z >> 30)).wrapping_mul(0xbf58_476d_1ce4_e5b9);
563 z = (z ^ (z >> 27)).wrapping_mul(0x94d0_49bb_1331_11eb);
564 z ^ (z >> 31)
565 };
566 let inputs = (0..aig.inputs().len() * words).map(|_| next()).collect();
567 let latches = (0..aig.latches().len() * words).map(|_| next()).collect();
568 Patterns { inputs, latches, words, used: 64 }
569 }
570
571 fn add(&mut self, inputs: &[bool], latches: &[bool]) {
573 if self.used == 64 {
574 let grow = |old: &[u64], values: &[bool], words: usize| -> Vec<u64> {
575 let mut out = Vec::with_capacity(values.len() * (words + 1));
576 for (i, &v) in values.iter().enumerate() {
577 out.extend_from_slice(&old[i * words..(i + 1) * words]);
578 out.push(if v { !0 } else { 0 });
579 }
580 out
581 };
582 self.inputs = grow(&self.inputs, inputs, self.words);
583 self.latches = grow(&self.latches, latches, self.words);
584 self.words += 1;
585 self.used = 1;
586 return;
587 }
588 let (words, bit) = (self.words, self.used);
589 for (store, values) in [(&mut self.inputs, inputs), (&mut self.latches, latches)] {
590 for (i, &v) in values.iter().enumerate() {
591 let cell = &mut store[i * words + words - 1];
592 *cell = (*cell & !(1 << bit)) | ((v as u64) << bit);
593 }
594 }
595 self.used += 1;
596 }
597}
598
599fn signature(sim: &[u64], words: usize, n: u32) -> (Vec<u64>, bool) {
601 let row = &sim[n as usize * words..(n as usize + 1) * words];
602 let phase = row[0] & 1 == 1;
603 (row.iter().map(|&w| if phase { !w } else { w }).collect(), phase)
604}
605
606impl Sweeper<'_> {
607 fn resolve(&self, l: AigLit) -> AigLit {
608 self.repr[l.node() as usize].with_phase(l.is_complemented())
609 }
610
611 fn learn(&mut self, a: AigLit, b: AigLit, cut: Vec<u32>, proof: Lrat) {
612 self.lemmas.push(Lemma { a, b, cut, proof });
613 self.earlier.push(a, b);
614 }
615
616 fn sweep(&mut self, pairs: &[(AigLit, AigLit)], opts: &Options) {
617 let aig = self.aig;
618 let roots: Vec<AigLit> = pairs.iter().flat_map(|&(a, b)| [a, b]).collect();
619 let mut cone = aig.cone(&roots);
620 if cone.first() != Some(&0) {
621 cone.insert(0, 0);
622 }
623 let left: HashSet<u32> = aig.cone(&pairs.iter().map(|p| p.0).collect::<Vec<_>>()).into_iter().collect();
626 let right: HashSet<u32> = aig.cone(&pairs.iter().map(|p| p.1).collect::<Vec<_>>()).into_iter().collect();
627 let mut patterns = Patterns::random(aig, opts.sim_words.max(1), opts.seed);
628 let mut sim = aig.simulate(patterns.words, &patterns.inputs, &patterns.latches);
629 let mut classes: HashMap<Vec<u64>, u32> = HashMap::new();
630 let mut apart: Vec<u32> = Vec::new();
631 self.stats.cone = cone.len();
632 for &n in &cone {
633 self.stats.swept += 1;
634 let is_and = matches!(aig.node(n), Node::And(..)) && !(left.contains(&n) && right.contains(&n));
635 let mut attempts = 0;
636 loop {
637 let (key, phase) = signature(&sim, patterns.words, n);
638 let Some(&r) = classes.get(&key) else {
639 classes.insert(key, n);
640 break;
641 };
642 if !is_and || attempts == ATTEMPTS {
643 break;
644 }
645 attempts += 1;
646 let (_, phase_r) = signature(&sim, patterns.words, r);
647 let target = AigLit::new(r, phase ^ phase_r);
648 self.stats.sat_calls += 1;
649 let (outcome, cut) =
650 prove_local(aig, AigLit::new(n, false), target, &self.earlier, &self.repr, opts.sweep_budget, &mut self.stats);
651 tick(&self.stats, opts);
652 match outcome {
653 Outcome::Equal(proof) => {
654 self.learn(AigLit::new(n, false), target, cut, proof);
655 self.repr[n as usize] = target;
656 self.stats.merges += 1;
657 break;
658 }
659 Outcome::Differ(inputs, latches) => {
660 self.stats.counterexamples += 1;
661 patterns.add(&inputs, &latches);
662 sim = aig.simulate(patterns.words, &patterns.inputs, &patterns.latches);
663 classes.clear();
664 for &m in &apart {
665 classes.entry(signature(&sim, patterns.words, m).0).or_insert(m);
666 }
667 }
668 Outcome::Unknown(_) => {
669 self.stats.unknown += 1;
670 break;
671 }
672 }
673 }
674 if self.repr[n as usize] == AigLit::new(n, false) {
675 apart.push(n);
676 }
677 }
678 }
679}
680
681struct Parity {
683 parent: Vec<u32>,
684 flip: Vec<bool>,
685}
686
687impl Parity {
688 fn new(n: usize) -> Parity {
689 Parity { parent: (0..n as u32).collect(), flip: vec![false; n] }
690 }
691
692 fn find(&mut self, n: u32) -> (u32, bool) {
693 let mut path = Vec::new();
694 let mut x = n;
695 while self.parent[x as usize] != x {
696 path.push(x);
697 x = self.parent[x as usize];
698 }
699 let root = x;
700 for &p in path.iter().rev() {
701 let up = self.parent[p as usize];
702 if up != root {
703 self.flip[p as usize] ^= self.flip[up as usize];
704 }
705 self.parent[p as usize] = root;
706 }
707 (root, self.flip[n as usize] && n != root)
708 }
709
710 fn lit(&mut self, l: AigLit) -> (u32, bool) {
711 let (r, p) = self.find(l.node());
712 (r, p ^ l.is_complemented())
713 }
714
715 fn union(&mut self, a: AigLit, b: AigLit) {
716 let (ra, pa) = self.lit(a);
717 let (rb, pb) = self.lit(b);
718 if ra != rb {
719 self.parent[ra as usize] = rb;
720 self.flip[ra as usize] = pa ^ pb;
721 }
722 }
723}
724
725pub fn check(aig: &Aig, certificate: &Certificate, claims: &[(AigLit, AigLit)]) -> bool {
728 let n = aig.num_nodes();
729 let in_graph = |l: AigLit| (l.node() as usize) < n;
730 let mut earlier = Earlier::new();
731 for lemma in &certificate.lemmas {
732 if !in_graph(lemma.a) || !in_graph(lemma.b) {
733 return false;
734 }
735 if lemma.cut.windows(2).any(|w| w[0] >= w[1]) || lemma.cut.iter().any(|&c| c as usize >= n) {
736 return false;
737 }
738 let q = query_indexed(aig, lemma.a, lemma.b, &lemma.cut, &earlier);
739 if !lrat::check(q.num_vars, &q.clauses, &lemma.proof) {
740 return false;
741 }
742 earlier.push(lemma.a, lemma.b);
743 }
744 let mut uf = Parity::new(n);
745 for &(a, b) in earlier.pairs() {
746 uf.union(a, b);
747 }
748 claims.iter().all(|&(a, b)| in_graph(a) && in_graph(b) && (a == b || uf.lit(a) == uf.lit(b)))
749}
750
751#[derive(Clone, Debug)]
754pub struct Miter {
755 pub aig: Aig,
756 pub pairs: Vec<(String, AigLit, AigLit)>,
758}
759
760#[derive(Clone, Debug, PartialEq, Eq)]
762pub enum MiterError {
763 Sequential,
765 Unnamed,
767 Unmatched(String),
769 Duplicate(String),
771}
772
773pub fn miter(left: &Aig, right: &Aig) -> Result<Miter, MiterError> {
774 if !left.latches().is_empty() || !right.latches().is_empty() {
775 return Err(MiterError::Sequential);
776 }
777 for g in [left, right] {
778 let mut seen = std::collections::HashSet::new();
779 for o in g.outputs() {
780 let name = o.name.as_ref().ok_or(MiterError::Unnamed)?;
781 if !seen.insert(name) {
782 return Err(MiterError::Duplicate(name.clone()));
783 }
784 }
785 }
786 let mut aig = Aig::new();
787 let mut by_name: HashMap<String, AigLit> = HashMap::new();
788 let left_inputs: Vec<AigLit> = left
789 .inputs()
790 .iter()
791 .map(|p| {
792 let l = aig.add_input(p.name.clone());
793 if let Some(n) = &p.name {
794 by_name.insert(n.clone(), l);
795 }
796 l
797 })
798 .collect();
799 let right_inputs: Vec<AigLit> = right
800 .inputs()
801 .iter()
802 .map(|p| match p.name.as_ref().and_then(|n| by_name.get(n)) {
803 Some(&l) => l,
804 None => aig.add_input(p.name.clone()),
805 })
806 .collect();
807 let leaf = |inputs: &[AigLit], l: Leaf| match l {
808 Leaf::Input(i) => inputs[i],
809 Leaf::Latch(_) => unreachable!("a miter's circuits are combinational"),
810 };
811 let lmap = aig.import(left, &mut |l| leaf(&left_inputs, l));
812 let rmap = aig.import(right, &mut |l| leaf(&right_inputs, l));
813 let at = |map: &[AigLit], l: AigLit| map[l.node() as usize].with_phase(l.is_complemented());
814 let mut pairs = Vec::with_capacity(left.outputs().len());
815 for o in left.outputs() {
816 let name = o.name.clone().expect("named above");
817 let k = right.output_index(&name).ok_or_else(|| MiterError::Unmatched(name.clone()))?;
818 pairs.push((name, at(&lmap, o.lit), at(&rmap, right.outputs()[k].lit)));
819 }
820 if let Some(o) = right.outputs().iter().find(|o| left.output_index(o.name.as_deref().unwrap()).is_none()) {
821 return Err(MiterError::Unmatched(o.name.clone().unwrap()));
822 }
823 for (name, a, b) in &pairs {
824 let d = aig.xor(*a, *b);
825 aig.add_output(d, Some(name.clone()));
826 }
827 let operands: Vec<AigLit> = pairs.iter().flat_map(|(_, a, b)| [*a, *b]).collect();
829 let (aig, map) = aig.compact_keeping(&operands);
830 let moved = |l: AigLit| map[l.node() as usize].expect("an operand is kept").with_phase(l.is_complemented());
831 let pairs = pairs.into_iter().map(|(name, a, b)| (name, moved(a), moved(b))).collect();
832 Ok(Miter { aig, pairs })
833}
834
835#[derive(Clone, Debug)]
837pub struct Report {
838 pub miter: Miter,
839 pub verdicts: Vec<(String, Verdict)>,
840 pub certificate: Certificate,
841 pub stats: Stats,
842}
843
844impl Report {
845 pub fn all_equal(&self) -> bool {
847 self.verdicts.iter().all(|(_, v)| *v == Verdict::Equal)
848 }
849
850 pub fn equal_pairs(&self) -> Vec<(AigLit, AigLit)> {
852 self.miter
853 .pairs
854 .iter()
855 .zip(&self.verdicts)
856 .filter(|(_, (_, v))| *v == Verdict::Equal)
857 .map(|((_, a, b), _)| (*a, *b))
858 .collect()
859 }
860
861 pub fn check(&self) -> bool {
863 let differences_replay = self.miter.pairs.iter().zip(&self.verdicts).all(|((_, a, b), (_, v))| match v {
864 Verdict::Differ { inputs, latches } => {
865 let values = self.miter.aig.eval(inputs, latches);
866 Aig::value(&values, *a) != Aig::value(&values, *b)
867 }
868 _ => true,
869 });
870 differences_replay && check(&self.miter.aig, &self.certificate, &self.equal_pairs())
871 }
872
873 pub fn named_inputs(&self, inputs: &[bool]) -> Vec<(String, bool)> {
875 self.miter
876 .aig
877 .inputs()
878 .iter()
879 .zip(inputs)
880 .filter_map(|(p, &v)| p.name.clone().map(|n| (n, v)))
881 .collect()
882 }
883}
884
885pub fn separating_input(left: &Aig, right: &Aig, budget: u64) -> Result<Option<(Miter, Vec<bool>)>, String> {
889 let mut m = miter(left, right).map_err(|e| format!("{e:?}"))?;
890 let differences: Vec<AigLit> = m.aig.outputs().iter().map(|o| o.lit).collect();
891 let any = differences.into_iter().fold(AigLit::FALSE, |acc, d| m.aig.or(acc, d));
892 match witness(&m.aig, any, budget)? {
893 Some((inputs, _)) => Ok(Some((m, inputs))),
894 None => Ok(None),
895 }
896}
897
898pub fn equivalent(left: &Aig, right: &Aig, opts: &Options) -> Result<Report, MiterError> {
900 let miter = miter(left, right)?;
901 let pairs: Vec<(AigLit, AigLit)> = miter.pairs.iter().map(|(_, a, b)| (*a, *b)).collect();
902 let decided = decide(&miter.aig, &pairs, opts);
903 let verdicts = miter.pairs.iter().map(|p| p.0.clone()).zip(decided.verdicts).collect();
904 Ok(Report { miter, verdicts, certificate: decided.certificate, stats: decided.stats })
905}
906
907#[derive(Clone, Debug)]
909pub struct Distinction {
910 pub classes: Vec<Vec<usize>>,
912 pub proofs: Vec<(usize, usize, Report)>,
914 pub inputs: Vec<String>,
916 pub outputs: Vec<String>,
918 pub vectors: Vec<Vec<bool>>,
920 pub cover: Vec<usize>,
922 pub table: Vec<Vec<Vec<bool>>>,
924}
925
926impl Distinction {
927 pub fn check(&self, members: &[Aig]) -> bool {
930 let mut seen = vec![false; members.len()];
931 for &m in self.classes.iter().flatten() {
932 if m >= members.len() || std::mem::replace(&mut seen[m], true) {
933 return false;
934 }
935 }
936 if seen.contains(&false) || self.classes.iter().any(|c| c.is_empty()) {
937 return false;
938 }
939 let proven: HashSet<(usize, usize)> = self.proofs.iter().map(|(r, m, _)| (*r, *m)).collect();
940 if self.classes.iter().any(|c| c[1..].iter().any(|&m| !proven.contains(&(c[0], m)))) {
941 return false;
942 }
943 for (rep, m, report) in &self.proofs {
944 let bound = miter(&members[*rep], &members[*m]).is_ok_and(|again| again.aig.to_binary() == report.miter.aig.to_binary());
945 if !bound || !report.all_equal() || !report.check() {
946 return false;
947 }
948 }
949 let Ok(seen) = Outputs::of(members, &self.inputs, &self.outputs) else { return false };
950 if self.table.len() != self.classes.len() || self.cover.iter().any(|&v| v >= self.vectors.len()) {
951 return false;
952 }
953 let on: Vec<Vec<Vec<bool>>> = self.classes.iter().map(|c| self.cover.iter().map(|&v| seen.eval(c[0], &self.vectors[v])).collect()).collect();
954 let agree = self.classes.iter().zip(&on).all(|(c, rows)| c.iter().all(|&m| self.cover.iter().zip(rows).all(|(&v, row)| seen.eval(m, &self.vectors[v]) == *row)));
955 let apart = (0..on.len()).all(|i| (i + 1..on.len()).all(|j| on[i] != on[j]));
956 agree && apart && on == self.table
957 }
958}
959
960struct Outputs<'a> {
962 members: &'a [Aig],
963 places: Vec<Vec<Option<usize>>>,
965 lits: Vec<Vec<AigLit>>,
967}
968
969impl<'a> Outputs<'a> {
970 fn of(members: &'a [Aig], inputs: &[String], outputs: &[String]) -> Result<Outputs<'a>, String> {
971 let mut places = Vec::with_capacity(members.len());
972 let mut lits = Vec::with_capacity(members.len());
973 for (k, g) in members.iter().enumerate() {
974 if !g.latches().is_empty() {
975 return Err(format!("member {k} holds latches"));
976 }
977 let read: HashSet<u32> = g.cone(&g.outputs().iter().map(|o| o.lit).collect::<Vec<_>>()).into_iter().collect();
978 let place = g
979 .inputs()
980 .iter()
981 .map(|p| match &p.name {
982 Some(n) => inputs.iter().position(|i| i == n).map(Some).ok_or_else(|| format!("member {k}: input {n} is not among the inputs")),
983 None if read.contains(&p.lit.node()) => Err(format!("member {k}: an input without a name that an output reads, which no vector can assign")),
984 None => Ok(None),
985 })
986 .collect::<Result<Vec<Option<usize>>, String>>()?;
987 if g.outputs().len() != outputs.len() {
988 return Err(format!("member {k} has {} outputs, the first {}", g.outputs().len(), outputs.len()));
989 }
990 let lit = outputs
991 .iter()
992 .map(|o| g.output_index(o).map(|i| g.outputs()[i].lit).ok_or_else(|| format!("member {k} has no output {o}")))
993 .collect::<Result<Vec<AigLit>, String>>()?;
994 places.push(place);
995 lits.push(lit);
996 }
997 Ok(Outputs { members, places, lits })
998 }
999
1000 fn eval(&self, m: usize, vector: &[bool]) -> Vec<bool> {
1001 let inputs: Vec<bool> = self.places[m].iter().map(|&p| p.is_some_and(|p| vector[p])).collect();
1002 let values = self.members[m].eval(&inputs, &[]);
1003 self.lits[m].iter().map(|&l| Aig::value(&values, l)).collect()
1004 }
1005
1006 fn signature(&self, m: usize, vectors: &[Vec<bool>]) -> Vec<u64> {
1008 let words = vectors.len().div_ceil(64);
1009 if words == 0 {
1010 return Vec::new();
1011 }
1012 let g = &self.members[m];
1013 let mut inputs = vec![0u64; g.inputs().len() * words];
1014 for (k, v) in vectors.iter().enumerate() {
1015 for (i, &p) in self.places[m].iter().enumerate() {
1016 if p.is_some_and(|p| v[p]) {
1017 inputs[i * words + k / 64] |= 1 << (k % 64);
1018 }
1019 }
1020 }
1021 let sim = g.simulate(words, &inputs, &[]);
1022 let tail = if vectors.len() % 64 == 0 { u64::MAX } else { (1u64 << (vectors.len() % 64)) - 1 };
1023 self.lits[m]
1024 .iter()
1025 .flat_map(|&l| (0..words).map(move |w| (l, w)))
1026 .map(|(l, w)| Aig::word(&sim, words, l, w) & if w + 1 == words { tail } else { u64::MAX })
1027 .collect()
1028 }
1029}
1030
1031pub fn distinguish(members: &[Aig], seeds: &[Vec<(String, bool)>], budget: u64, opts: &Options) -> Result<Distinction, String> {
1040 let mut inputs: Vec<String> = Vec::new();
1041 for g in members {
1042 for n in g.inputs().iter().filter_map(|p| p.name.as_ref()) {
1043 if !inputs.contains(n) {
1044 inputs.push(n.clone());
1045 }
1046 }
1047 }
1048 let outputs: Vec<String> = match members.first() {
1049 Some(g) => g.outputs().iter().map(|o| o.name.clone().ok_or("an output without a name")).collect::<Result<_, _>>()?,
1050 None => Vec::new(),
1051 };
1052 let seen = Outputs::of(members, &inputs, &outputs)?;
1053 let over = |named: &[(String, bool)]| -> Vec<bool> { inputs.iter().map(|i| named.iter().any(|(n, v)| n == i && *v)).collect() };
1054 let mut vectors: Vec<Vec<bool>> = seeds.iter().map(|s| over(s)).collect();
1055 let mut proofs: Vec<(usize, usize, Report)> = Vec::new();
1056 let mut proven = vec![false; members.len()];
1057 let groups = loop {
1058 let signatures: Vec<Vec<u64>> = (0..members.len()).map(|m| seen.signature(m, &vectors)).collect();
1059 let mut groups: Vec<Vec<usize>> = Vec::new();
1060 for m in 0..members.len() {
1061 match groups.iter_mut().find(|g| signatures[g[0]] == signatures[m]) {
1062 Some(g) => g.push(m),
1063 None => groups.push(vec![m]),
1064 }
1065 }
1066 let mut split = None;
1067 'groups: for g in &groups {
1068 for &m in &g[1..] {
1069 if proven[m] {
1070 continue;
1071 }
1072 match separating_input(&members[g[0]], &members[m], budget)? {
1073 Some((found, assignment)) => {
1074 let named: Vec<(String, bool)> =
1075 found.aig.inputs().iter().zip(&assignment).filter_map(|(p, &v)| p.name.clone().map(|n| (n, v))).collect();
1076 split = Some(over(&named));
1077 break 'groups;
1078 }
1079 None => {
1080 let report = equivalent(&members[g[0]], &members[m], opts).map_err(|e| format!("{e:?}"))?;
1081 if !report.all_equal() || !report.check() {
1082 return Err(format!("members {} and {m}: no input separates them, yet no certified proof", g[0]));
1083 }
1084 proven[m] = true;
1085 proofs.push((g[0], m, report));
1086 }
1087 }
1088 }
1089 }
1090 match split {
1091 Some(v) => vectors.push(v),
1092 None => break groups,
1093 }
1094 };
1095 let on: Vec<Vec<Vec<bool>>> = groups.iter().map(|g| vectors.iter().map(|v| seen.eval(g[0], v)).collect()).collect();
1096 let mut apart: Vec<(usize, usize)> = (0..groups.len()).flat_map(|i| (i + 1..groups.len()).map(move |j| (i, j))).collect();
1097 let mut cover: Vec<usize> = Vec::new();
1098 while !apart.is_empty() {
1099 let tells = |v: usize| apart.iter().filter(|&&(i, j)| on[i][v] != on[j][v]).count();
1100 let best = (0..vectors.len()).max_by_key(|&v| (tells(v), std::cmp::Reverse(v))).filter(|&v| tells(v) > 0);
1101 let Some(best) = best else { return Err("two groups the vectors do not tell apart".into()) };
1102 cover.push(best);
1103 apart.retain(|&(i, j)| on[i][best] == on[j][best]);
1104 }
1105 let table = on.iter().map(|rows| cover.iter().map(|&v| rows[v].clone()).collect()).collect();
1106 Ok(Distinction { classes: groups, proofs, inputs, outputs, vectors, cover, table })
1107}
1108
1109#[cfg(test)]
1110#[path = "../../logicaffeine_tests/unit/logicaffeine_proof/sweep/tests.rs"]
1111mod tests;