use crate::aig::{Aig, Lit};
pub type CnfLit = i32;
pub type Clause = Vec<CnfLit>;
#[derive(Clone, Debug, Default)]
pub struct CnfFormula {
pub num_vars: u32,
pub clauses: Vec<Clause>,
}
impl CnfFormula {
pub fn eval(&self, assignment: &[bool]) -> bool {
self.clauses.iter().all(|clause| {
clause.iter().any(|&l| {
let v = assignment[(l.unsigned_abs() - 1) as usize];
if l > 0 { v } else { !v }
})
})
}
}
#[derive(Clone, Debug)]
pub struct TseitinMap;
impl TseitinMap {
pub fn cnf_lit(&self, lit: Lit) -> CnfLit {
let var = (lit.var() + 1) as CnfLit;
if lit.is_complement() { -var } else { var }
}
}
pub fn tseitin(aig: &Aig, outputs: &[Lit]) -> (CnfFormula, TseitinMap) {
let map = TseitinMap;
let mut clauses: Vec<Clause> = Vec::with_capacity(3 * aig.num_ands() as usize + 1);
clauses.push(vec![-1]);
for (var, a, b) in aig.and_gates() {
let o = (var + 1) as CnfLit;
let (la, lb) = (map.cnf_lit(a), map.cnf_lit(b));
clauses.push(vec![-o, la]);
clauses.push(vec![-o, lb]);
clauses.push(vec![o, -la, -lb]);
}
for &out in outputs {
clauses.push(vec![map.cnf_lit(out)]);
}
(
CnfFormula {
num_vars: aig.num_vars(),
clauses,
},
map,
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::aig::word_input;
fn brute_sat(f: &CnfFormula) -> bool {
let n = f.num_vars as usize;
assert!(n <= 24);
(0u32..1 << n).any(|bits| {
let assignment: Vec<bool> = (0..n).map(|i| bits >> i & 1 == 1).collect();
f.eval(&assignment)
})
}
#[test]
fn tseitin_is_equisatisfiable_on_random_circuits() {
let mut s: u64 = 0x9E3779B97F4A7C15;
let mut next = move || {
s ^= s << 13;
s ^= s >> 7;
s ^= s << 17;
s
};
for round in 0..30 {
let mut g = Aig::new();
let inputs = word_input(&mut g, 5);
let mut pool: Vec<Lit> = inputs.clone();
while g.num_vars() < 21 {
let a = pool[(next() % pool.len() as u64) as usize];
let b = pool[(next() % pool.len() as u64) as usize];
let lit = match next() % 3 {
0 => g.and(a, b),
1 => g.or(a, b),
_ => g.xor(a, b),
};
pool.push(if next() % 2 == 0 { lit } else { lit.not() });
}
let out = *pool.last().unwrap();
let sem_sat = (0u32..32).any(|bits| {
let iv: Vec<bool> = (0..5).map(|i| bits >> i & 1 == 1).collect();
let vals = g.simulate(&iv);
g.lit_value(&vals, out)
});
let (cnf, _) = tseitin(&g, &[out]);
assert_eq!(
brute_sat(&cnf),
sem_sat,
"round {round}: Tseitin must be equisatisfiable"
);
assert_eq!(cnf.clauses.len(), 2 + 3 * g.num_ands() as usize);
}
}
#[test]
fn constant_outputs() {
let g = Aig::new();
let (cnf_true, _) = tseitin(&g, &[Lit::TRUE]);
assert!(brute_sat(&cnf_true));
let (cnf_false, _) = tseitin(&g, &[Lit::FALSE]);
assert!(!brute_sat(&cnf_false));
}
#[test]
fn conflicting_outputs_are_unsat() {
let mut g = Aig::new();
let x = g.input();
let (cnf, _) = tseitin(&g, &[x, x.not()]);
assert!(!brute_sat(&cnf));
}
}