use super::blast::{Blaster, Gates};
use super::ir::{ConstraintEvalError, Expr, IrError, StateId, Ty, VarId};
use biodivine_lib_bdd::{Bdd, BddValuation, BddVariable, BddVariableSet, op_function};
use core::fmt;
use num_bigint::BigUint;
use num_traits::Zero;
pub const NODE_BUDGET: usize = 200_000;
pub const MAX_BDD_VARS: usize = u16::MAX as usize - 2;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum BddBuildError {
TooLarge {
budget: usize,
during: &'static str,
},
CnfTooLarge {
limit: usize,
during: &'static str,
},
TooManyBits {
bits: usize,
},
Ir(IrError),
StateUnknown(StateId),
UnboundState(StateId),
UnboundVar(VarId),
Unexpanded,
VarWidthMismatch {
var: VarId,
declared: u32,
used: u32,
},
}
impl fmt::Display for BddBuildError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
BddBuildError::TooLarge { budget, during } => write!(
f,
"constraint BDD exceeded the {budget}-node budget while building `{during}`"
),
BddBuildError::CnfTooLarge { limit, during } => write!(
f,
"constraint CNF exceeded the {limit}-clause limit while building `{during}`"
),
BddBuildError::TooManyBits { bits } => write!(
f,
"{bits} random-variable bits exceed the BDD backend's {MAX_BDD_VARS}-variable limit"
),
BddBuildError::Ir(e) => fmt::Display::fmt(e, f),
BddBuildError::StateUnknown(s) => {
fmt::Display::fmt(&ConstraintEvalError::StateUnknown(*s), f)
}
BddBuildError::UnboundState(s) => {
fmt::Display::fmt(&ConstraintEvalError::UnboundState(*s), f)
}
BddBuildError::UnboundVar(v) => {
fmt::Display::fmt(&ConstraintEvalError::UnboundVar(*v), f)
}
BddBuildError::Unexpanded => fmt::Display::fmt(&ConstraintEvalError::Unexpanded, f),
BddBuildError::VarWidthMismatch {
var,
declared,
used,
} => write!(
f,
"random variable #{var} is declared {declared} bits wide but read as {used}"
),
}
}
}
impl std::error::Error for BddBuildError {}
impl From<IrError> for BddBuildError {
fn from(e: IrError) -> Self {
BddBuildError::Ir(e)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Layout {
pub tys: Vec<Ty>,
pub bits: Vec<Vec<BddVariable>>,
}
impl Layout {
pub fn interleaved(tys: &[Ty]) -> Result<Layout, BddBuildError> {
let total: usize = tys.iter().map(|t| t.width as usize).sum();
if total > MAX_BDD_VARS {
return Err(BddBuildError::TooManyBits { bits: total });
}
let max_w = tys.iter().map(|t| t.width).max().unwrap_or(0);
let mut bits: Vec<Vec<BddVariable>> = tys
.iter()
.map(|t| Vec::with_capacity(t.width as usize))
.collect();
let mut next = 0usize;
for pos in 0..max_w {
for (v, t) in tys.iter().enumerate() {
if t.width > pos {
bits[v].push(BddVariable::from_index(next));
next += 1;
}
}
}
Ok(Layout {
tys: tys.to_vec(),
bits,
})
}
pub fn num_bits(&self) -> usize {
self.tys.iter().map(|t| t.width as usize).sum()
}
pub fn encode(&self, values: &[BigUint]) -> BddValuation {
assert_eq!(values.len(), self.tys.len(), "one value per variable");
let n = u16::try_from(self.num_bits()).expect("checked by interleaved()");
let mut val = BddValuation::all_false(n);
for (v, vars) in self.bits.iter().enumerate() {
for (i, bv) in vars.iter().enumerate() {
val.set_value(*bv, values[v].bit(i as u64));
}
}
val
}
pub fn decode(&self, val: &BddValuation) -> Vec<BigUint> {
self.bits
.iter()
.map(|vars| {
let mut x = BigUint::zero();
for (i, bv) in vars.iter().enumerate() {
if val.value(*bv) {
x.set_bit(i as u64, true);
}
}
x
})
.collect()
}
}
#[derive(Clone, Debug)]
pub struct Compiled {
pub bdd: Bdd,
pub layout: Layout,
}
impl Compiled {
pub fn accepts(&self, values: &[BigUint]) -> bool {
self.bdd.eval_in(&self.layout.encode(values))
}
}
pub fn compile(
var_tys: &[Ty],
states: &[Option<BigUint>],
conjuncts: &[Expr],
budget: usize,
) -> Result<Compiled, BddBuildError> {
let layout = Layout::interleaved(var_tys)?;
let n = u16::try_from(layout.num_bits()).expect("checked by interleaved()");
let gates = BddGates {
set: BddVariableSet::new_anonymous(n),
layout: &layout,
budget,
};
let mut acc = gates.set.mk_true();
{
let widths: Vec<usize> = layout.bits.iter().map(Vec::len).collect();
let b = Blaster {
g: &gates,
widths: &widths,
states,
};
for c in conjuncts {
c.validate()?;
let t = b.conjunct(c)?;
acc = gates.and("constraint conjunction", &acc, &t)?;
}
}
Ok(Compiled { bdd: acc, layout })
}
type OpFn = fn(Option<bool>, Option<bool>) -> Option<bool>;
struct BddGates<'a> {
set: BddVariableSet,
layout: &'a Layout,
budget: usize,
}
impl BddGates<'_> {
fn lim(&self, during: &'static str, f: OpFn, a: &Bdd, b: &Bdd) -> Result<Bdd, BddBuildError> {
Bdd::binary_op_with_limit(self.budget, a, b, f).ok_or(BddBuildError::TooLarge {
budget: self.budget,
during,
})
}
}
impl Gates for BddGates<'_> {
type Bit = Bdd;
fn constant(&self, v: bool) -> Bdd {
if v {
self.set.mk_true()
} else {
self.set.mk_false()
}
}
fn as_const(&self, a: &Bdd) -> Option<bool> {
if a.is_true() {
Some(true)
} else if a.is_false() {
Some(false)
} else {
None
}
}
fn input(&self, var: VarId, i: usize) -> Bdd {
self.set.mk_var(self.layout.bits[var as usize][i])
}
fn not(&self, a: &Bdd) -> Bdd {
a.not()
}
fn and(&self, d: &'static str, a: &Bdd, b: &Bdd) -> Result<Bdd, BddBuildError> {
if a.is_false() || b.is_false() {
return Ok(self.set.mk_false());
}
if a.is_true() {
return Ok(b.clone());
}
if b.is_true() || a == b {
return Ok(a.clone());
}
self.lim(d, op_function::and, a, b)
}
fn or(&self, d: &'static str, a: &Bdd, b: &Bdd) -> Result<Bdd, BddBuildError> {
if a.is_true() || b.is_true() {
return Ok(self.set.mk_true());
}
if a.is_false() {
return Ok(b.clone());
}
if b.is_false() || a == b {
return Ok(a.clone());
}
self.lim(d, op_function::or, a, b)
}
fn xor(&self, d: &'static str, a: &Bdd, b: &Bdd) -> Result<Bdd, BddBuildError> {
if a.is_false() {
return Ok(b.clone());
}
if b.is_false() {
return Ok(a.clone());
}
if a.is_true() {
return Ok(b.not());
}
if b.is_true() {
return Ok(a.not());
}
if a == b {
return Ok(self.set.mk_false());
}
self.lim(d, op_function::xor, a, b)
}
fn mux(&self, d: &'static str, s: &Bdd, a: &Bdd, b: &Bdd) -> Result<Bdd, BddBuildError> {
if s.is_true() {
return Ok(a.clone());
}
if s.is_false() {
return Ok(b.clone());
}
if a == b {
return Ok(a.clone());
}
let t = self.and(d, s, a)?;
let e = self.and(d, &s.not(), b)?;
self.or(d, &t, &e)
}
}