use std::collections::{HashMap, VecDeque};
use std::sync::Mutex;
use super::expr::{MOp, MbaExpr};
use crate::ops::{BinOp, UnOp};
use crate::{BitVec, Width};
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub struct MbaBudget {
pub steps: u64,
pub evidence: bool,
}
impl Default for MbaBudget {
fn default() -> Self {
MbaBudget {
steps: 1 << 20,
evidence: true,
}
}
}
setters!(MbaBudget {
with_steps: steps: u64,
with_evidence: evidence: bool,
});
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Claim {
Unverified,
Sampled,
Proved,
Certified,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum MbaAnswer {
Simplified {
expr: MbaExpr,
claim: Claim,
},
NoSimpler,
Unsupported(String),
Exhausted,
}
pub trait MbaSolver: Send + Sync {
fn id(&self) -> &str;
fn solve(&self, p: &MbaExpr, budget: &MbaBudget) -> MbaAnswer;
fn polynomial_fragments(&self) -> bool {
false
}
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum Verdict {
Proved,
Refuted,
Unknown,
}
pub trait EquivalenceProver: Send + Sync {
fn id(&self) -> &str;
fn prove_equal(&self, a: &MbaExpr, b: &MbaExpr, budget: &MbaBudget) -> Verdict;
}
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub struct CacheKey(pub [u64; 2]);
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CacheEntry {
Simplified(MbaExpr),
NoSimpler,
}
pub trait MbaCacheStore: Send + Sync {
fn get(&self, k: &CacheKey) -> Option<CacheEntry>;
fn put(&self, k: &CacheKey, e: &CacheEntry);
}
#[derive(Copy, Clone, Debug, Default)]
pub struct NoCache;
impl MbaCacheStore for NoCache {
fn get(&self, _: &CacheKey) -> Option<CacheEntry> {
None
}
fn put(&self, _: &CacheKey, _: &CacheEntry) {}
}
#[derive(Debug)]
pub struct MemoryCache {
capacity: usize,
inner: Mutex<(HashMap<CacheKey, CacheEntry>, VecDeque<CacheKey>)>,
}
impl MemoryCache {
pub fn new(capacity: usize) -> MemoryCache {
MemoryCache {
capacity,
inner: Mutex::new((HashMap::new(), VecDeque::new())),
}
}
pub fn len(&self) -> usize {
self.inner.lock().map_or(0, |g| g.0.len())
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
}
impl MbaCacheStore for MemoryCache {
fn get(&self, k: &CacheKey) -> Option<CacheEntry> {
self.inner.lock().ok()?.0.get(k).cloned()
}
fn put(&self, k: &CacheKey, e: &CacheEntry) {
let Ok(mut g) = self.inner.lock() else {
return;
};
if self.capacity == 0 {
return;
}
if g.0.insert(*k, e.clone()).is_none() {
g.1.push_back(*k);
}
while g.0.len() > self.capacity {
match g.1.pop_front() {
Some(old) => {
g.0.remove(&old);
}
None => break,
}
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct SignatureSolver;
const SIGNATURE_MAX_VARS: usize = 10;
impl MbaSolver for SignatureSolver {
fn id(&self) -> &str {
"bitwright.signature.v1"
}
fn solve(&self, p: &MbaExpr, _: &MbaBudget) -> MbaAnswer {
if !p.is_linear() {
return MbaAnswer::Unsupported("not a linear MBA".into());
}
let t = p.vars().len();
if t > SIGNATURE_MAX_VARS || p.vars().iter().any(|&w| Some(w) != p.width()) {
return MbaAnswer::Unsupported("too many variables, or mixed widths".into());
}
let Some(sig) = p.corners() else {
return MbaAnswer::Unsupported("no signature".into());
};
let Some(w) = p.width() else {
return MbaAnswer::Unsupported("empty".into());
};
let d = mobius(&sig);
let m = conjunction_form(p.vars(), w, &d);
match m {
Some(m) if m.nodes().len() < p.nodes().len() => MbaAnswer::Simplified {
expr: m,
claim: Claim::Proved,
},
_ => MbaAnswer::NoSimpler,
}
}
}
pub(crate) fn mobius(sig: &[BitVec]) -> Vec<BitVec> {
let mut d: Vec<BitVec> = sig
.iter()
.map(|v| BitVec::un_unchecked(UnOp::Neg, v))
.collect();
let n = d.len();
let mut bit = 1;
while bit < n {
for p in 0..n {
if p & bit != 0 {
d[p] = BitVec::bin_unchecked(BinOp::Sub, &d[p], &d[p ^ bit]);
}
}
bit <<= 1;
}
d
}
fn conjunction_form(vars: &[Width], w: Width, d: &[BitVec]) -> Option<MbaExpr> {
let mut m = MbaExpr::new(vars.to_vec());
let var_nodes: Vec<u32> = (0..vars.len() as u32)
.map(|i| m.push(MOp::Var(i), &[]))
.collect::<Result<_, _>>()
.ok()?;
let konst = BitVec::un_unchecked(UnOp::Neg, &d[0]);
let mut acc = m.push(MOp::Const(konst), &[]).ok()?;
let one = BitVec::one(w);
for (s, ds) in d.iter().enumerate().skip(1) {
if ds.is_zero() {
continue;
}
let mut conj: Option<u32> = None;
for (j, &v) in var_nodes.iter().enumerate() {
if s >> j & 1 == 1 {
conj = Some(match conj {
None => v,
Some(c) => m.push(MOp::And, &[c, v]).ok()?,
});
}
}
let conj = conj?;
let term = if *ds == one {
conj
} else {
let k = m.push(MOp::Const(*ds), &[]).ok()?;
m.push(MOp::Mul, &[conj, k]).ok()?
};
acc = m.push(MOp::Add, &[acc, term]).ok()?;
}
Some(m)
}