use super::{aig::Aig, cuts::Probe};
use crate::{BitVec, Context, Error, Expr, WidthError, hash::IdMap};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Limits {
pub visits: usize,
pub nodes: usize,
pub construction_work: usize,
pub enumeration_work: usize,
pub inputs: usize,
pub tuples: usize,
pub output_bits: usize,
}
impl Default for Limits {
fn default() -> Self {
Self {
visits: 256,
nodes: 4096,
construction_work: 16_384,
enumeration_work: 1_048_576,
inputs: 12,
tuples: 256,
output_bits: 4096,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Stop {
TooManyRoots,
OutputBits,
UnavailableOutput(usize),
Nodes(usize),
Inputs(usize),
EnumerationWork,
Tuples,
}
#[derive(Clone, Debug)]
pub enum Analysis {
Superset(TupleDomain),
Unknown(Stop),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct Stats {
pub nodes: usize,
pub inputs: usize,
pub assignments: usize,
}
#[derive(Clone, Debug)]
pub struct TupleDomain {
outputs: Vec<Expr>,
premises: Vec<Expr>,
tuples: Vec<Vec<BitVec>>,
stats: Stats,
}
impl TupleDomain {
pub fn outputs(&self) -> &[Expr] {
&self.outputs
}
pub fn premises(&self) -> &[Expr] {
&self.premises
}
pub fn tuples(&self) -> &[Vec<BitVec>] {
&self.tuples
}
pub fn stats(&self) -> Stats {
self.stats
}
pub fn coverage(&self, cx: &mut Context) -> Result<Expr, Error> {
for &root in self.outputs.iter().chain(&self.premises) {
cx.id(root)?;
}
let mut covered = cx.zero(crate::Width::W1)?;
for tuple in &self.tuples {
let mut equal = cx.one(crate::Width::W1)?;
for (&output, value) in self.outputs.iter().zip(tuple) {
let value = cx.constant(value)?;
let field = cx.eq(output, value)?;
equal = cx.and(equal, field)?;
}
covered = cx.or(covered, equal)?;
}
for &premise in &self.premises {
let absent = cx.not(premise)?;
covered = cx.or(absent, covered)?;
}
Ok(covered)
}
}
pub fn analyze(
cx: &mut Context,
outputs: &[Expr],
premises: &[Expr],
limits: Limits,
) -> Result<Analysis, Error> {
let unknown = |why| Ok(Analysis::Unknown(why));
if outputs.len() > 64 || premises.len() > 64 {
return unknown(Stop::TooManyRoots);
}
let mut ids = Vec::with_capacity(outputs.len());
let mut widths = Vec::with_capacity(outputs.len());
let mut output_bits = 0usize;
for &output in outputs {
let id = cx.id(output)?;
let width = cx.width_of(id);
output_bits += usize::from(width.bits());
ids.push(id);
widths.push(width);
}
let mut constraints = Vec::with_capacity(premises.len());
for &premise in premises {
let id = cx.id(premise)?;
if cx.wid(id) != 1 {
return Err(WidthError::ConditionWidth { bits: cx.wid(id) }.into());
}
constraints.push(id);
}
if output_bits > limits.output_bits.min(4096) {
return unknown(Stop::OutputBits);
}
let mut probe = Probe::new(
cx,
limits.visits,
limits.nodes,
limits.construction_work.min(16_384),
);
let mut words = Vec::with_capacity(ids.len());
for (index, &id) in ids.iter().enumerate() {
let bits = probe.word(id);
if probe.blaster.g.len() > limits.nodes {
return unknown(Stop::Nodes(probe.blaster.g.len()));
}
let Some(bits) = bits else {
return unknown(Stop::UnavailableOutput(index));
};
words.push(bits);
}
let mut conditions = Vec::with_capacity(constraints.len());
for &id in &constraints {
conditions.push(probe.word(id).expect("Boolean cut")[0]);
if probe.blaster.g.len() > limits.nodes {
return unknown(Stop::Nodes(probe.blaster.g.len()));
}
}
let graph = &probe.blaster.g;
if graph.len() > limits.nodes {
return unknown(Stop::Nodes(graph.len()));
}
let live_roots = words
.iter()
.flatten()
.copied()
.chain(conditions.iter().copied())
.collect::<Vec<_>>();
let (support, inputs) = graph.input_support(&live_roots);
if inputs > limits.inputs.min(20) {
return unknown(Stop::Inputs(inputs));
}
let assignments = 1usize << inputs;
let batches = assignments.div_ceil(64);
let per_tuple = output_bits + 8 * outputs.len();
let Some(work) = (graph.len() + conditions.len())
.checked_mul(batches)
.and_then(|n| {
assignments
.checked_mul(per_tuple)
.and_then(|m| n.checked_add(m))
})
else {
return unknown(Stop::EnumerationWork);
};
if work > limits.enumeration_work {
return unknown(Stop::EnumerationWork);
}
let mut seen = IdMap::<Vec<BitVec>, ()>::default();
let mut tuples = Vec::new();
for base in (0..assignments).step_by(64) {
let lanes = (assignments - base).min(64);
let values = graph.eval_assignment_batch(&support, base);
let mut active = if lanes == 64 {
u64::MAX
} else {
(1u64 << lanes) - 1
};
for &condition in &conditions {
active &= Aig::word(&values, condition);
}
while active != 0 {
let lane = active.trailing_zeros();
active &= active - 1;
let tuple = words
.iter()
.zip(&widths)
.map(|(bits, &width)| {
let mut limbs = [0u64; 8];
for (bit, &literal) in bits.iter().enumerate() {
limbs[bit / 64] |=
((Aig::word(&values, literal) >> lane) & 1) << (bit % 64);
}
BitVec::wrapping_from_limbs(width, &limbs)
})
.collect::<Vec<_>>();
if seen.contains_key(&tuple) {
continue;
}
if tuples.len() == limits.tuples {
return unknown(Stop::Tuples);
}
seen.insert(tuple.clone(), ());
tuples.push(tuple);
}
}
Ok(Analysis::Superset(TupleDomain {
outputs: outputs.to_vec(),
premises: premises.to_vec(),
tuples,
stats: Stats {
nodes: graph.len(),
inputs,
assignments,
},
}))
}
#[cfg(test)]
mod tests;