| |
| |
| |
| |
| |
|
|
| use crate::primitive; |
|
|
| |
| #[derive(Debug, Clone, PartialEq, Eq)] |
| pub enum BoolExpr { |
| |
| Var(usize), |
| |
| Const(bool), |
| |
| Nand(Box<BoolExpr>, Box<BoolExpr>), |
| } |
|
|
| impl BoolExpr { |
| |
| pub fn var(idx: usize) -> Self { |
| BoolExpr::Var(idx) |
| } |
|
|
| |
| pub fn constant(val: bool) -> Self { |
| BoolExpr::Const(val) |
| } |
|
|
| |
| pub fn nand(a: BoolExpr, b: BoolExpr) -> Self { |
| BoolExpr::Nand(Box::new(a), Box::new(b)) |
| } |
|
|
| |
| pub fn from_not(a: BoolExpr) -> Self { |
| BoolExpr::Nand(Box::new(a.clone()), Box::new(a)) |
| } |
|
|
| |
| pub fn from_and(a: BoolExpr, b: BoolExpr) -> Self { |
| let nand_ab = BoolExpr::Nand(Box::new(a.clone()), Box::new(b.clone())); |
| BoolExpr::Nand(Box::new(nand_ab.clone()), Box::new(nand_ab)) |
| } |
|
|
| |
| pub fn from_or(a: BoolExpr, b: BoolExpr) -> Self { |
| let not_a = BoolExpr::Nand(Box::new(a.clone()), Box::new(a)); |
| let not_b = BoolExpr::Nand(Box::new(b.clone()), Box::new(b)); |
| BoolExpr::Nand(Box::new(not_a), Box::new(not_b)) |
| } |
|
|
| |
| pub fn from_xor(a: BoolExpr, b: BoolExpr) -> Self { |
| let not_a = BoolExpr::Nand(Box::new(a.clone()), Box::new(a.clone())); |
| let not_b = BoolExpr::Nand(Box::new(b.clone()), Box::new(b.clone())); |
| let left = BoolExpr::Nand(Box::new(not_a), Box::new(b)); |
| let right = BoolExpr::Nand(Box::new(a), Box::new(not_b)); |
| BoolExpr::Nand(Box::new(left), Box::new(right)) |
| } |
|
|
| |
| pub fn from_implies(a: BoolExpr, b: BoolExpr) -> Self { |
| let not_b = BoolExpr::Nand(Box::new(b.clone()), Box::new(b)); |
| BoolExpr::Nand(Box::new(a), Box::new(not_b)) |
| } |
|
|
| |
| |
| |
| pub fn is_pure_nand(&self) -> bool { |
| match self { |
| BoolExpr::Var(_) | BoolExpr::Const(_) => true, |
| BoolExpr::Nand(a, b) => a.is_pure_nand() && b.is_pure_nand(), |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| pub fn eval(expr: &BoolExpr, bindings: &[bool]) -> bool { |
| match expr { |
| BoolExpr::Var(idx) => bindings[*idx], |
| BoolExpr::Const(val) => *val, |
| BoolExpr::Nand(a, b) => { |
| let va = eval(a, bindings); |
| let vb = eval(b, bindings); |
| primitive::nand(va, vb) |
| } |
| } |
| } |
|
|
| |
| |
| |
| |
| pub fn truth_table(expr: &BoolExpr, n_vars: usize) -> Vec<(Vec<bool>, bool)> { |
| let n_rows = 1usize << n_vars; |
| let mut table = Vec::with_capacity(n_rows); |
|
|
| for row in 0..n_rows { |
| let bindings: Vec<bool> = (0..n_vars) |
| .map(|var_idx| (row >> (n_vars - 1 - var_idx)) & 1 == 1) |
| .collect(); |
| let result = eval(expr, &bindings); |
| table.push((bindings, result)); |
| } |
|
|
| table |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| pub fn lower_to_nand(expr: &BoolExpr) -> BoolExpr { |
| match expr { |
| BoolExpr::Var(idx) => BoolExpr::Var(*idx), |
| BoolExpr::Const(val) => BoolExpr::Const(*val), |
| BoolExpr::Nand(a, b) => { |
| let la = lower_to_nand(a); |
| let lb = lower_to_nand(b); |
| BoolExpr::Nand(Box::new(la), Box::new(lb)) |
| } |
| } |
| } |
|
|
| |
| pub fn functionally_equivalent(a: &BoolExpr, b: &BoolExpr, n_vars: usize) -> bool { |
| let ta = truth_table(a, n_vars); |
| let tb = truth_table(b, n_vars); |
| ta.iter() |
| .zip(tb.iter()) |
| .all(|((_, ra), (_, rb))| ra == rb) |
| } |
|
|
| #[cfg(test)] |
| mod tests { |
| use super::*; |
|
|
| #[test] |
| fn test_eval_const() { |
| assert_eq!(eval(&BoolExpr::Const(true), &[]), true); |
| assert_eq!(eval(&BoolExpr::Const(false), &[]), false); |
| } |
|
|
| #[test] |
| fn test_eval_var() { |
| assert_eq!(eval(&BoolExpr::Var(0), &[true, false]), true); |
| assert_eq!(eval(&BoolExpr::Var(1), &[true, false]), false); |
| } |
|
|
| #[test] |
| fn test_eval_nand() { |
| let expr = BoolExpr::nand(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert_eq!(eval(&expr, &[true, true]), false); |
| assert_eq!(eval(&expr, &[true, false]), true); |
| assert_eq!(eval(&expr, &[false, true]), true); |
| assert_eq!(eval(&expr, &[false, false]), true); |
| } |
|
|
| #[test] |
| fn test_from_not() { |
| let expr = BoolExpr::from_not(BoolExpr::Var(0)); |
| assert_eq!(eval(&expr, &[true]), false); |
| assert_eq!(eval(&expr, &[false]), true); |
| } |
|
|
| #[test] |
| fn test_from_and() { |
| let expr = BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let table = truth_table(&expr, 2); |
| let expected = vec![ |
| (vec![false, false], false), |
| (vec![false, true], false), |
| (vec![true, false], false), |
| (vec![true, true], true), |
| ]; |
| assert_eq!(table, expected); |
| } |
|
|
| #[test] |
| fn test_from_or() { |
| let expr = BoolExpr::from_or(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let table = truth_table(&expr, 2); |
| let expected = vec![ |
| (vec![false, false], false), |
| (vec![false, true], true), |
| (vec![true, false], true), |
| (vec![true, true], true), |
| ]; |
| assert_eq!(table, expected); |
| } |
|
|
| #[test] |
| fn test_from_xor() { |
| let expr = BoolExpr::from_xor(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let table = truth_table(&expr, 2); |
| let expected = vec![ |
| (vec![false, false], false), |
| (vec![false, true], true), |
| (vec![true, false], true), |
| (vec![true, true], false), |
| ]; |
| assert_eq!(table, expected); |
| } |
|
|
| #[test] |
| fn test_from_implies() { |
| let expr = BoolExpr::from_implies(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let table = truth_table(&expr, 2); |
| let expected = vec![ |
| (vec![false, false], true), |
| (vec![false, true], true), |
| (vec![true, false], false), |
| (vec![true, true], true), |
| ]; |
| assert_eq!(table, expected); |
| } |
|
|
| #[test] |
| fn test_truth_table_single_var() { |
| let expr = BoolExpr::Var(0); |
| let table = truth_table(&expr, 1); |
| assert_eq!(table, vec![(vec![false], false), (vec![true], true)]); |
| } |
|
|
| #[test] |
| fn test_truth_table_three_vars() { |
| |
| let a_and_b = BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let expr = BoolExpr::from_or(a_and_b, BoolExpr::Var(2)); |
| let table = truth_table(&expr, 3); |
| assert_eq!(table.len(), 8); |
| |
| |
| assert_eq!(table[0], (vec![false, false, false], false)); |
| |
| assert_eq!(table[1], (vec![false, false, true], true)); |
| |
| assert_eq!(table[6], (vec![true, true, false], true)); |
| |
| assert_eq!(table[7], (vec![true, true, true], true)); |
| } |
|
|
| #[test] |
| fn test_lower_to_nand_preserves_semantics() { |
| |
| let expr = BoolExpr::from_or( |
| BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1)), |
| BoolExpr::from_not(BoolExpr::Var(2)), |
| ); |
| let lowered = lower_to_nand(&expr); |
| assert!(functionally_equivalent(&expr, &lowered, 3)); |
| } |
|
|
| #[test] |
| fn test_lower_to_nand_round_trip_all_ops() { |
| |
| let not_expr = BoolExpr::from_not(BoolExpr::Var(0)); |
| assert!(functionally_equivalent(¬_expr, &lower_to_nand(¬_expr), 1)); |
|
|
| |
| let and_expr = BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert!(functionally_equivalent(&and_expr, &lower_to_nand(&and_expr), 2)); |
|
|
| |
| let or_expr = BoolExpr::from_or(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert!(functionally_equivalent(&or_expr, &lower_to_nand(&or_expr), 2)); |
|
|
| |
| let xor_expr = BoolExpr::from_xor(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert!(functionally_equivalent(&xor_expr, &lower_to_nand(&xor_expr), 2)); |
|
|
| |
| let imp_expr = BoolExpr::from_implies(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert!(functionally_equivalent(&imp_expr, &lower_to_nand(&imp_expr), 2)); |
| } |
|
|
| #[test] |
| fn test_is_pure_nand() { |
| let expr = BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1)); |
| assert!(expr.is_pure_nand()); |
|
|
| let complex = BoolExpr::from_or( |
| BoolExpr::from_xor(BoolExpr::Var(0), BoolExpr::Var(1)), |
| BoolExpr::from_implies(BoolExpr::Var(2), BoolExpr::Const(true)), |
| ); |
| assert!(complex.is_pure_nand()); |
| } |
|
|
| #[test] |
| fn test_nested_expression_evaluation() { |
| |
| let imp = BoolExpr::from_implies(BoolExpr::Var(0), BoolExpr::Var(1)); |
| let not_c = BoolExpr::from_not(BoolExpr::Var(2)); |
| let expr = BoolExpr::from_and(imp, not_c); |
|
|
| |
| assert_eq!(eval(&expr, &[true, true, false]), true); |
| |
| assert_eq!(eval(&expr, &[true, false, false]), false); |
| |
| assert_eq!(eval(&expr, &[true, true, true]), false); |
| } |
|
|
| #[test] |
| fn test_functional_equivalence() { |
| |
| let lhs = BoolExpr::from_not(BoolExpr::from_and(BoolExpr::Var(0), BoolExpr::Var(1))); |
| let rhs = BoolExpr::from_or( |
| BoolExpr::from_not(BoolExpr::Var(0)), |
| BoolExpr::from_not(BoolExpr::Var(1)), |
| ); |
| assert!(functionally_equivalent(&lhs, &rhs, 2)); |
| } |
| } |
|
|