use std::collections::{HashMap, HashSet};
use trit_core::algebra::Algebra;
use trit_core::ast::Expr;
use trit_core::bits::{Evaluator, TriBit, TruthTable};
use trit_core::limits::{LimitError, Limits, check_expr};
use trit_core::value::Ternary;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Assignment {
entries: Vec<(String, Ternary)>,
}
impl Assignment {
pub fn new() -> Self {
Self::default()
}
pub fn set(&mut self, name: impl Into<String>, value: Ternary) -> &mut Self {
let name = name.into();
match self.entries.iter_mut().find(|(n, _)| *n == name) {
Some(slot) => slot.1 = value,
None => self.entries.push((name, value)),
}
self
}
pub fn get(&self, name: &str) -> Option<Ternary> {
self.entries
.iter()
.find(|(n, _)| n == name)
.map(|(_, v)| *v)
}
pub fn variables(&self) -> Vec<String> {
self.entries.iter().map(|(n, _)| n.clone()).collect()
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
}
pub fn eval(expr: &Expr, assignment: &Assignment, alg: Algebra) -> Ternary {
match expr {
Expr::Const(v) => *v,
Expr::Var(name) => assignment.get(name).unwrap_or(Ternary::Unknown),
Expr::Not(a) => alg.not(eval(a, assignment, alg)),
Expr::And(a, b) => alg.and(eval(a, assignment, alg), eval(b, assignment, alg)),
Expr::Or(a, b) => alg.or(eval(a, assignment, alg), eval(b, assignment, alg)),
Expr::Xor(a, b) => alg.strong_add(eval(a, assignment, alg), eval(b, assignment, alg)),
Expr::Implies(a, b) => alg.implies(eval(a, assignment, alg), eval(b, assignment, alg)),
Expr::Equiv(a, b) => alg.equiv(eval(a, assignment, alg), eval(b, assignment, alg)),
}
}
pub type VarMap = HashMap<String, usize>;
pub fn eval_bitwise(expr: &Expr, planes: &[TriBit], vars: &VarMap, alg: Algebra) -> TriBit {
match expr {
Expr::Const(v) => TriBit::constant(*v),
Expr::Var(name) => match vars.get(name) {
Some(idx) => planes.get(*idx).copied().unwrap_or(TriBit::UNKNOWN),
None => TriBit::UNKNOWN,
},
Expr::Not(a) => eval_bitwise(a, planes, vars, alg).not(),
Expr::And(a, b) => {
let (x, y) = (
eval_bitwise(a, planes, vars, alg),
eval_bitwise(b, planes, vars, alg),
);
x.and(y, alg)
}
Expr::Or(a, b) => {
let (x, y) = (
eval_bitwise(a, planes, vars, alg),
eval_bitwise(b, planes, vars, alg),
);
x.or(y, alg)
}
Expr::Xor(a, b) => {
let (x, y) = (
eval_bitwise(a, planes, vars, alg),
eval_bitwise(b, planes, vars, alg),
);
x.strong_add(y, alg)
}
Expr::Implies(a, b) => {
let (x, y) = (
eval_bitwise(a, planes, vars, alg),
eval_bitwise(b, planes, vars, alg),
);
x.implies(y, alg)
}
Expr::Equiv(a, b) => {
let (x, y) = (
eval_bitwise(a, planes, vars, alg),
eval_bitwise(b, planes, vars, alg),
);
x.equiv(y, alg)
}
}
}
fn evaluator_for(vars: &[String], alg: Algebra) -> (Evaluator, VarMap) {
let mut ev = Evaluator::new(alg);
let mut map = VarMap::new();
for name in vars {
let idx = ev.declare(name.clone());
map.insert(name.clone(), idx);
}
(ev, map)
}
pub fn truth_table(expr: &Expr, alg: Algebra) -> Result<TruthTable, LimitError> {
truth_table_within(expr, alg, Limits::default())
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct EquivalenceResult {
pub equivalent: bool,
pub counterexample: Option<u64>,
pub counterexample_assignment: Option<Assignment>,
pub variable_count: usize,
}
pub fn equivalent(a: &Expr, b: &Expr, alg: Algebra) -> Result<EquivalenceResult, LimitError> {
let limits = Limits::default();
check_expr(a, limits)?;
check_expr(b, limits)?;
let vars = union_vars(a, b);
equivalent_with_vars(a, b, alg, &vars, limits)
}
fn union_vars(a: &Expr, b: &Expr) -> Vec<String> {
let mut vars = a.variables();
for v in b.variables() {
if !vars.contains(&v) {
vars.push(v);
}
}
vars
}
fn equivalent_with_vars(
a: &Expr,
b: &Expr,
alg: Algebra,
vars: &[String],
limits: Limits,
) -> Result<EquivalenceResult, LimitError> {
if vars.len() > limits.max_vars {
return Err(LimitError::Vars {
limit: limits.max_vars,
});
}
let (ev, map) = evaluator_for(vars, alg);
let total = ev.assignment_count()?;
if total > limits.max_rows as u64 {
return Err(LimitError::Rows {
limit: limits.max_rows,
});
}
let mut base = 0u64;
while base < total {
let planes = ev.chunk_planes(base)?;
let va = eval_bitwise(a, &planes, &map, alg);
let vb = eval_bitwise(b, &planes, &map, alg);
let take = (total - base).min(64) as u32;
for bit in 0..take {
if va.get(bit) != vb.get(bit) {
let index = base + bit as u64;
return Ok(EquivalenceResult {
equivalent: false,
counterexample: Some(index),
counterexample_assignment: Some(decode_assignment(index, vars)),
variable_count: vars.len(),
});
}
}
base += 64;
}
Ok(EquivalenceResult {
equivalent: true,
counterexample: None,
counterexample_assignment: None,
variable_count: vars.len(),
})
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum AssignmentCodecError {
#[error("变量数 {vars} 的赋值编号超出 u64 可表示范围")]
Overflow {
vars: usize,
},
#[error("赋值编号 {index} 超出 {vars} 个变量的有效域 3^{vars}")]
OutOfDomain {
index: u64,
vars: usize,
},
#[error("变量名重复:{0}")]
DuplicateVariable(String),
}
pub fn try_assignment_index(
assignment: &Assignment,
vars: &[String],
) -> Result<u64, AssignmentCodecError> {
let mut seen = HashSet::new();
let mut index = 0u64;
let mut power = 1u64;
let mut power_overflow = false;
for (i, name) in vars.iter().enumerate() {
if !seen.insert(name.as_str()) {
return Err(AssignmentCodecError::DuplicateVariable(name.clone()));
}
if i > 0 {
match power.checked_mul(3) {
Some(next) => power = next,
None => power_overflow = true,
}
}
let digit = match assignment.get(name).unwrap_or(Ternary::Unknown) {
Ternary::False => 0u64,
Ternary::True => 1u64,
Ternary::Unknown => 2u64,
};
if digit == 0 {
continue;
}
if power_overflow {
return Err(AssignmentCodecError::Overflow { vars: vars.len() });
}
let term = digit
.checked_mul(power)
.ok_or(AssignmentCodecError::Overflow { vars: vars.len() })?;
index = index
.checked_add(term)
.ok_or(AssignmentCodecError::Overflow { vars: vars.len() })?;
}
Ok(index)
}
pub fn assignment_index(assignment: &Assignment, vars: &[String]) -> Option<u64> {
try_assignment_index(assignment, vars).ok()
}
pub fn try_decode_assignment(
index: u64,
vars: &[String],
) -> Result<Assignment, AssignmentCodecError> {
let mut seen = HashSet::new();
for name in vars {
if !seen.insert(name.as_str()) {
return Err(AssignmentCodecError::DuplicateVariable(name.clone()));
}
}
let mut asg = Assignment::new();
let mut t = index;
for name in vars {
let digit = t % 3;
t /= 3;
let v = match digit {
0 => Ternary::False,
1 => Ternary::True,
_ => Ternary::Unknown,
};
asg.set(name.clone(), v);
}
if t != 0 {
return Err(AssignmentCodecError::OutOfDomain {
index,
vars: vars.len(),
});
}
Ok(asg)
}
pub fn decode_assignment(index: u64, vars: &[String]) -> Assignment {
let mut asg = Assignment::new();
let mut t = index;
for name in vars {
let digit = t % 3;
t /= 3;
let v = match digit {
0 => Ternary::False,
1 => Ternary::True,
_ => Ternary::Unknown,
};
asg.set(name.clone(), v);
}
asg
}
pub fn eval_within(
expr: &Expr,
assignment: &Assignment,
alg: Algebra,
limits: Limits,
) -> Result<Ternary, LimitError> {
check_expr(expr, limits)?;
Ok(eval(expr, assignment, alg))
}
pub fn truth_table_within(
expr: &Expr,
alg: Algebra,
limits: Limits,
) -> Result<TruthTable, LimitError> {
check_expr(expr, limits)?;
let vars = expr.variables();
if vars.len() > limits.max_vars {
return Err(LimitError::Vars {
limit: limits.max_vars,
});
}
let (ev, map) = evaluator_for(&vars, alg);
ev.evaluate_within(|planes| eval_bitwise(expr, planes, &map, alg), limits)
}
pub fn equivalent_within(
a: &Expr,
b: &Expr,
alg: Algebra,
limits: Limits,
) -> Result<EquivalenceResult, LimitError> {
check_expr(a, limits)?;
check_expr(b, limits)?;
let vars = union_vars(a, b);
equivalent_with_vars(a, b, alg, &vars, limits)
}
#[cfg(test)]
mod tests {
use super::*;
use trit_core::parser::Parser;
#[test]
fn eval_single_assignment() {
let e = Parser::parse("a & b").unwrap();
let mut asg = Assignment::new();
asg.set("a", Ternary::True).set("b", Ternary::False);
assert_eq!(eval(&e, &asg, Algebra::Kleene), Ternary::False);
}
#[test]
fn unbound_variable_is_unknown() {
let e = Parser::parse("a").unwrap();
assert_eq!(
eval(&e, &Assignment::new(), Algebra::Kleene),
Ternary::Unknown
);
}
#[test]
fn truth_table_covers_all_rows() {
let e = Parser::parse("a | b").unwrap();
let tt = truth_table(&e, Algebra::Kleene).unwrap();
assert_eq!(tt.variables.len(), 2);
assert_eq!(tt.values.len(), 9);
}
#[test]
fn truth_table_matches_scalar_evaluation() {
let e = Parser::parse("(a & b) | (!a & c)").unwrap();
let vars = e.variables();
let tt = truth_table(&e, Algebra::Kleene).unwrap();
for (i, expected) in tt.values.iter().enumerate() {
let asg = decode_assignment(i as u64, &vars);
assert_eq!(eval(&e, &asg, Algebra::Kleene), *expected, "row {i}");
}
}
#[test]
fn truth_table_refuses_unbudgeted_variable_count() {
let e = Parser::parse(
&(0..13)
.map(|i| format!("v{i}"))
.collect::<Vec<_>>()
.join(" & "),
)
.unwrap();
assert_eq!(
truth_table(&e, Algebra::Kleene).unwrap_err(),
LimitError::Vars { limit: 12 }
);
}
#[test]
fn equivalent_detects_equal_expressions() {
let a = Parser::parse("a & b").unwrap();
let b = Parser::parse("b & a").unwrap();
let r = equivalent(&a, &b, Algebra::Kleene).unwrap();
assert!(r.equivalent);
assert!(r.counterexample.is_none());
}
#[test]
fn equivalent_finds_counterexample() {
let a = Parser::parse("a & b").unwrap();
let b = Parser::parse("a | b").unwrap();
let r = equivalent(&a, &b, Algebra::Kleene).unwrap();
assert!(!r.equivalent);
assert!(r.counterexample.is_some());
}
#[test]
fn decode_assignment_round_trips() {
let vars = vec!["a".to_string(), "b".to_string()];
let asg = decode_assignment(0, &vars);
assert_eq!(asg.get("a"), Some(Ternary::False));
assert_eq!(asg.get("b"), Some(Ternary::False));
let asg = decode_assignment(5, &vars);
assert_eq!(asg.get("a"), Some(Ternary::Unknown));
assert_eq!(asg.get("b"), Some(Ternary::True));
}
#[test]
fn checked_codec_rejects_out_of_domain_and_duplicates() {
let one = vec!["a".to_string()];
assert_eq!(
try_decode_assignment(3, &one).unwrap_err(),
AssignmentCodecError::OutOfDomain { index: 3, vars: 1 }
);
assert!(try_decode_assignment(2, &one).is_ok());
let dup = vec!["a".to_string(), "a".to_string()];
assert!(matches!(
try_decode_assignment(0, &dup).unwrap_err(),
AssignmentCodecError::DuplicateVariable(_)
));
let mut asg = Assignment::new();
asg.set("a", Ternary::False);
assert!(matches!(
try_assignment_index(&asg, &dup).unwrap_err(),
AssignmentCodecError::DuplicateVariable(_)
));
}
#[test]
fn assignment_index_does_not_over_reject_high_zero_digits() {
let vars: Vec<String> = (0..42).map(|i| format!("v{i}")).collect();
let mut all_false = Assignment::new();
for name in &vars {
all_false.set(name.clone(), Ternary::False);
}
assert_eq!(assignment_index(&all_false, &vars), Some(0));
let mut high = all_false.clone();
high.set("v40", Ternary::True);
assert_eq!(assignment_index(&high, &vars), Some(3u64.pow(40)));
let mut over = all_false;
over.set("v41", Ternary::True);
assert!(matches!(
try_assignment_index(&over, &vars).unwrap_err(),
AssignmentCodecError::Overflow { .. }
));
}
}