use rustc_hash::FxHashSet;
use crate::cnf::VarId;
use crate::cnf::occ;
use crate::cnf::{Clause, CnfFormula};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(super) enum GateType {
And,
Or,
Xor,
Xnor,
Ite,
}
#[derive(Clone, Debug)]
pub(super) struct Gate {
pub gate_type: GateType,
pub clause_indices: Vec<usize>,
}
#[derive(Clone, Debug)]
pub(super) struct GateMapping {
pub gates: Vec<Gate>,
pub eliminated: FxHashSet<VarId>,
}
impl GateMapping {
pub(super) fn is_empty(&self) -> bool {
self.gates.is_empty()
}
pub(super) fn num_eliminated(&self) -> usize {
self.gates.len()
}
}
pub(super) fn detect_gates(formula: &CnfFormula) -> GateMapping {
let num_vars = formula.num_vars as usize;
let (pos_occs, neg_occs) = occ::occurrence_lists(&formula.clauses, num_vars);
let mut gates = Vec::new();
let mut eliminated: FxHashSet<VarId> = FxHashSet::default();
let mut consumed_clauses: FxHashSet<usize> = FxHashSet::default();
loop {
let mut changed = false;
for v in 0..num_vars {
let var = VarId(v as u32);
if eliminated.contains(&var) {
continue;
}
if pos_occs[v]
.iter()
.chain(&neg_occs[v])
.all(|ci| consumed_clauses.contains(ci))
{
continue;
}
let active_pos = filter_active(&pos_occs[v], &consumed_clauses);
let active_neg = filter_active(&neg_occs[v], &consumed_clauses);
let ctx = GateCtx {
var,
pos: &active_pos,
neg: &active_neg,
clauses: &formula.clauses,
eliminated: &eliminated,
};
if let Some(gate) = try_detect_gate(&ctx) {
consumed_clauses.extend(gate.clause_indices.iter().copied());
eliminated.insert(var);
gates.push(gate);
changed = true;
}
}
if !changed {
break;
}
}
GateMapping { gates, eliminated }
}
fn filter_active(indices: &[usize], consumed: &FxHashSet<usize>) -> Vec<usize> {
indices
.iter()
.copied()
.filter(|ci| !consumed.contains(ci))
.collect()
}
struct GateCtx<'a> {
var: VarId,
pos: &'a [usize],
neg: &'a [usize],
clauses: &'a [Clause],
eliminated: &'a FxHashSet<VarId>,
}
impl GateCtx<'_> {
fn is_ternary_shaped(&self) -> bool {
self.pos.len() == 2 && self.neg.len() == 2
}
fn all_indices(&self) -> Vec<usize> {
self.pos.iter().chain(self.neg.iter()).copied().collect()
}
}
fn try_detect_gate(ctx: &GateCtx<'_>) -> Option<Gate> {
try_and_or_gate(ctx, GateType::And)
.or_else(|| try_and_or_gate(ctx, GateType::Or))
.or_else(|| try_xor_gate(ctx))
.or_else(|| try_ite_gate(ctx))
}
fn try_and_or_gate(ctx: &GateCtx<'_>, kind: GateType) -> Option<Gate> {
let y_pos_in_long = match kind {
GateType::And => true,
GateType::Or => false,
_ => return None,
};
let (binary_indices, long_indices) = if y_pos_in_long {
(ctx.neg, ctx.pos)
} else {
(ctx.pos, ctx.neg)
};
if long_indices.len() != 1 || binary_indices.is_empty() {
return None;
}
let mut inputs_from_binary = collect_and_or_binary_inputs(ctx, binary_indices, y_pos_in_long)?;
let long_clause = &ctx.clauses[long_indices[0]];
if long_clause.literals.len() != 1 + inputs_from_binary.len() {
return None;
}
let mut inputs_from_long: Vec<VarId> = Vec::with_capacity(inputs_from_binary.len());
for lit in &long_clause.literals {
if lit.var == ctx.var {
if lit.positive != y_pos_in_long {
return None;
}
} else {
if lit.positive == y_pos_in_long {
return None; }
inputs_from_long.push(lit.var);
}
}
inputs_from_binary.sort_unstable();
inputs_from_long.sort_unstable();
if inputs_from_binary != inputs_from_long {
return None;
}
Some(Gate {
gate_type: kind,
clause_indices: ctx.all_indices(),
})
}
fn collect_and_or_binary_inputs(
ctx: &GateCtx<'_>,
binary_indices: &[usize],
y_pos_in_long: bool,
) -> Option<Vec<VarId>> {
let mut inputs = Vec::with_capacity(binary_indices.len());
for &ci in binary_indices {
let clause = &ctx.clauses[ci];
if clause.literals.len() != 2 {
return None;
}
let other = clause.literals.iter().find(|l| l.var != ctx.var)?;
if other.positive != y_pos_in_long || ctx.eliminated.contains(&other.var) {
return None;
}
inputs.push(other.var);
}
Some(inputs)
}
fn try_xor_gate(ctx: &GateCtx<'_>) -> Option<Gate> {
if !ctx.is_ternary_shaped() {
return None;
}
let all_indices = ctx.all_indices();
let inputs = collect_ternary_other_vars(ctx, &all_indices, 2)?;
let mut patterns: Vec<(bool, bool, bool)> = all_indices
.iter()
.map(|&ci| ternary_signs(&ctx.clauses[ci], ctx.var, inputs[0], inputs[1]))
.collect();
patterns.sort();
patterns.dedup();
if patterns.len() != 4 {
return None; }
let parity_of = |(a, b, c): (bool, bool, bool)| (a as u32 + b as u32 + c as u32) % 2;
let parity = parity_of(patterns[0]);
if !patterns.iter().all(|&p| parity_of(p) == parity) {
return None;
}
let gate_type = if parity == 0 {
GateType::Xor
} else {
GateType::Xnor
};
Some(Gate {
gate_type,
clause_indices: all_indices,
})
}
fn ternary_signs(clause: &Clause, y: VarId, a: VarId, b: VarId) -> (bool, bool, bool) {
let mut sy = false;
let mut sa = false;
let mut sb = false;
for lit in &clause.literals {
if lit.var == y {
sy = lit.positive;
} else if lit.var == a {
sa = lit.positive;
} else if lit.var == b {
sb = lit.positive;
}
}
(sy, sa, sb)
}
fn collect_ternary_other_vars(
ctx: &GateCtx<'_>,
clause_indices: &[usize],
expected_size: usize,
) -> Option<Vec<VarId>> {
let mut others: FxHashSet<VarId> = FxHashSet::default();
for &ci in clause_indices {
let clause = &ctx.clauses[ci];
if clause.literals.len() != 3 {
return None;
}
for lit in &clause.literals {
if lit.var != ctx.var {
others.insert(lit.var);
}
}
}
if others.len() != expected_size {
return None;
}
if others.iter().any(|v| ctx.eliminated.contains(v)) {
return None;
}
Some(others.into_iter().collect())
}
fn try_ite_gate(ctx: &GateCtx<'_>) -> Option<Gate> {
if !ctx.is_ternary_shaped() {
return None;
}
let all_indices = ctx.all_indices();
let inputs = collect_ternary_other_vars(ctx, &all_indices, 3)?;
for (si, &selector) in inputs.iter().enumerate() {
let non_selectors: [VarId; 2] = {
let mut it = inputs
.iter()
.enumerate()
.filter(|(i, _)| *i != si)
.map(|(_, &v)| v);
[it.next().unwrap(), it.next().unwrap()]
};
for &(a, b) in &[
(non_selectors[0], non_selectors[1]),
(non_selectors[1], non_selectors[0]),
] {
if check_ite_pattern(ctx, selector, a, b, &all_indices) {
return Some(Gate {
gate_type: GateType::Ite,
clause_indices: all_indices,
});
}
}
}
None
}
fn check_ite_pattern(
ctx: &GateCtx<'_>,
s: VarId,
a: VarId,
b: VarId,
clause_indices: &[usize],
) -> bool {
let sign_of = |clause: &Clause, v: VarId| -> Option<bool> {
clause
.literals
.iter()
.find(|l| l.var == v)
.map(|l| l.positive)
};
let mut found = [false; 4];
for &ci in clause_indices {
let clause = &ctx.clauses[ci];
let sy = match sign_of(clause, ctx.var) {
Some(v) => v,
None => return false,
};
let ss = match sign_of(clause, s) {
Some(v) => v,
None => return false,
};
let sa = sign_of(clause, a);
let sb = sign_of(clause, b);
let idx = match (ss, sa, sb) {
(false, Some(false), None) if sy => 0,
(false, Some(true), None) if !sy => 1,
(true, None, Some(false)) if sy => 2,
(true, None, Some(true)) if !sy => 3,
_ => return false,
};
if found[idx] {
return false; }
found[idx] = true;
}
found.iter().all(|&f| f)
}