use std::collections::{HashMap, HashSet};
use biodivine_lib_bdd::{Bdd, BddVariable};
use trit_core::algebra::Algebra;
use trit_core::ast::Expr;
use trit_core::limits::{Limits, check_expr};
use trit_core::value::Ternary;
use trit_eval::Assignment;
use crate::encode::Encoder;
use crate::order::{VarOrder, merge_order, order_variables};
use crate::{BddError, BddLimits, BddRequest};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Quantifier {
Exists,
Forall,
}
impl Quantifier {
pub fn parse(name: &str) -> Option<Self> {
match name.to_ascii_lowercase().as_str() {
"exists" | "exist" | "some" => Some(Self::Exists),
"forall" | "all" => Some(Self::Forall),
_ => None,
}
}
pub const fn as_str(self) -> &'static str {
match self {
Self::Exists => "exists",
Self::Forall => "forall",
}
}
}
#[derive(Debug)]
pub struct Quantified {
bdd: Bdd,
valid_free: Bdd,
free: Vec<String>,
varids: HashMap<String, (BddVariable, Option<BddVariable>)>,
limits: BddLimits,
}
impl Quantified {
pub fn is_satisfiable(&self) -> bool {
!self.bdd.is_false()
}
pub fn is_valid(&self) -> bool {
self.bdd.not().and(&self.valid_free).is_false()
}
pub fn try_is_valid(&self) -> Result<bool, BddError> {
let diff = self.bdd.not().and(&self.valid_free);
self.check_budget(&diff)?;
Ok(diff.is_false())
}
fn check_budget(&self, bdd: &Bdd) -> Result<(), BddError> {
if bdd.size() > self.limits.max_nodes {
Err(BddError::BudgetExceeded {
limit: self.limits.max_nodes,
})
} else {
Ok(())
}
}
pub fn free_variables(&self) -> &[String] {
&self.free
}
pub fn nodes(&self) -> usize {
self.bdd.size()
}
pub fn witness(&self) -> Option<Assignment> {
let valuation = self.bdd.sat_valuations().next()?;
Some(self.decode(valuation.as_vector()))
}
pub fn counterexample(&self) -> Option<Assignment> {
let negated = self.bdd.not().and(&self.valid_free);
let valuation = negated.sat_valuations().next()?;
Some(self.decode(valuation.as_vector()))
}
pub fn try_counterexample(&self) -> Result<Option<Assignment>, BddError> {
let negated = self.bdd.not().and(&self.valid_free);
self.check_budget(&negated)?;
Ok(negated
.sat_valuations()
.next()
.map(|valuation| self.decode(valuation.as_vector())))
}
fn decode(&self, bits: &[bool]) -> Assignment {
let mut assignment = Assignment::new();
for name in &self.free {
let (t_var, x_var) = self.varids[name];
let t = bits[t_var.to_index()];
let x = x_var.is_some_and(|v| bits[v.to_index()]);
let value = if t {
Ternary::True
} else if x {
Ternary::Unknown
} else {
Ternary::False
};
assignment.set(name.clone(), value);
}
assignment
}
}
pub fn quantify_bdd(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
) -> Result<Quantified, BddError> {
check_expr(expr, Limits::default())?;
let vars = expr.variables();
quantify_with_vars(
expr,
quantified,
kind,
alg,
binary_vars,
&vars,
BddLimits::default(),
)
}
pub fn quantify_bdd_with_order(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
order: &[String],
) -> Result<Quantified, BddError> {
check_expr(expr, Limits::default())?;
let all = expr.variables();
let mut vars: Vec<String> = Vec::with_capacity(all.len());
for name in order {
if all.contains(name) && !vars.contains(name) {
vars.push(name.clone());
}
}
for name in all {
if !vars.contains(&name) {
vars.push(name);
}
}
quantify_with_vars(
expr,
quantified,
kind,
alg,
binary_vars,
&vars,
BddLimits::default(),
)
}
pub fn quantify_bdd_with(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
request: &BddRequest<'_>,
) -> Result<Quantified, BddError> {
check_expr(expr, Limits::default())?;
let all = expr.variables();
let vars = match request.order {
Some(order) => merge_order(&all, order),
None => all,
};
quantify_with_vars(
expr,
quantified,
kind,
alg,
binary_vars,
&vars,
request.limits,
)
}
pub fn quantify_bdd_best(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
) -> Result<(Quantified, VarOrder), BddError> {
quantify_bdd_best_with_limits(
expr,
quantified,
kind,
alg,
binary_vars,
BddLimits::default(),
)
}
pub fn quantify_bdd_best_with_limits(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
limits: BddLimits,
) -> Result<(Quantified, VarOrder), BddError> {
check_expr(expr, Limits::default())?;
let mut best: Option<(Quantified, VarOrder)> = None;
for order in [
VarOrder::Appearance,
VarOrder::Reverse,
VarOrder::FrequencyDesc,
VarOrder::FrequencyAsc,
] {
let vars = order_variables(expr, expr, order);
let quantified =
quantify_with_vars(expr, quantified, kind, alg, binary_vars, &vars, limits)?;
if best
.as_ref()
.is_none_or(|(current, _)| quantified.nodes() < current.nodes())
{
best = Some((quantified, order));
}
}
Ok(best.expect("至少尝试一种排序"))
}
fn quantify_with_vars(
expr: &Expr,
quantified: &[String],
kind: Quantifier,
alg: Algebra,
binary_vars: &HashSet<String>,
vars: &[String],
limits: BddLimits,
) -> Result<Quantified, BddError> {
check_expr(expr, Limits::default())?;
for name in quantified {
if !vars.iter().any(|v| v == name) {
return Err(BddError::UnknownVariable(name.clone()));
}
}
let free: Vec<String> = vars
.iter()
.filter(|v| !quantified.iter().any(|q| q == *v))
.cloned()
.collect();
let enc = Encoder::new(alg, vars, binary_vars, limits)?;
let node = enc.encode(expr)?;
let pred = node.t.and(&node.x.not());
let mut q_rails = Vec::new();
let mut valid_q = enc.true_bdd.clone();
for name in quantified {
let (t_var, x_var) = enc.varids[name];
q_rails.push(t_var);
if let Some(x_var) = x_var {
q_rails.push(x_var);
let n = &enc.vars[name];
valid_q = enc.guard(valid_q.and(&n.t.not().or(&n.x.not())))?;
}
}
let mut valid_free = enc.true_bdd.clone();
for name in &free {
let n = &enc.vars[name];
if !n.binary {
valid_free = enc.guard(valid_free.and(&n.t.not().or(&n.x.not())))?;
}
}
let quantified_bdd = match kind {
Quantifier::Exists => valid_q.and(&pred).exists(&q_rails),
Quantifier::Forall => valid_q.and(&pred.not()).exists(&q_rails).not(),
};
Ok(Quantified {
bdd: enc.guard(quantified_bdd.and(&valid_free))?,
valid_free,
free,
varids: enc.varids,
limits,
})
}