use crate::traits::NumInputs;
use super::bitflip_iter::{bitflips, BitFlippable};
use super::permutation_gray_code::{permutation_swaps, permutation_swaps_cyclic};
use super::permutation_iter::{permutations, Permutable};
use super::small_lut::{SmallStaticTruthTable, SmallTT, SmallTruthTable};
use super::{gray_code_flips, npn_transform::*};
#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord)]
pub struct NPNTransformTracker {
tt: SmallTruthTable,
tf: NPNTransform,
}
impl NPNTransformTracker {
pub fn new(tt: SmallTruthTable) -> Self {
Self {
tt,
tf: NPNTransform::identity(tt.num_inputs()),
}
}
pub fn truth_table(&self) -> SmallTruthTable {
self.tt
}
pub fn transform(&self) -> NPNTransform {
self.tf
}
}
impl InvertOutput for NPNTransformTracker {
fn inverted_output(mut self) -> Self {
self.invert_output();
self
}
fn invert_output(&mut self) {
self.tt.invert_output();
self.tf.invert_output();
}
}
impl BitFlippable for NPNTransformTracker {
fn num_bits(&self) -> usize {
self.tt.num_bits()
}
fn flip_bit(&mut self, bit_idx: usize) {
self.tt.flip_bit(bit_idx);
self.tf.flip_bit(bit_idx);
}
}
impl Permutable for NPNTransformTracker {
fn len(&self) -> usize {
self.tt.len()
}
fn swap(&mut self, i: usize, j: usize) {
self.tt.swap(i, j);
self.tf.swap(i, j);
}
}
pub fn exact_npn_canonization<T>(tt: T) -> T
where
T: Clone + BitFlippable + Permutable + InvertOutput + Ord,
{
let mut state = tt.clone();
let mut min = tt.clone();
let n = tt.num_bits();
for swap in permutation_swaps_cyclic(n) {
for flip_idx in gray_code_flips::gray_code_flips(n) {
for _ in 0..2 {
if state < min {
state.clone_into(&mut min);
}
state.invert_output();
}
state.flip_bit(flip_idx);
}
state.swap(swap.0, swap.1);
}
debug_assert!(state == tt, "state should be reverted to the initial state");
min
}
pub fn exact_nn_canonization<T>(tt: T) -> T
where
T: Clone + BitFlippable + InvertOutput + Ord,
{
let mut state = tt.clone();
let mut min = tt.clone();
let n = tt.num_bits();
for flip_idx in gray_code_flips::gray_code_flips(n) {
for _ in 0..2 {
if state < min {
state.clone_into(&mut min);
}
state.invert_output();
}
state.flip_bit(flip_idx);
}
debug_assert!(state == tt, "state should be reverted to the initial state");
min
}
pub fn all_nn_equivalent_functions<T>(tt: T) -> impl Iterator<Item = T>
where
T: Clone + BitFlippable + InvertOutput + Ord,
{
[tt.clone(), tt.inverted_output()]
.into_iter()
.flat_map(bitflips)
}
pub fn all_npn_equivalent_functions<T>(tt: T) -> impl Iterator<Item = T>
where
T: Clone + BitFlippable + InvertOutput + Permutable + Ord,
{
all_nn_equivalent_functions(tt).flat_map(all_p_equivalent_functions)
}
#[test]
fn test_nn_canonization_3_inputs() {
let tt1 = SmallTruthTable::new(|[a, b, c]| a & b | c);
let tt2 = SmallTruthTable::new(|[a, b, c]| !(a & b | !c));
assert_eq!(exact_nn_canonization(tt1), exact_nn_canonization(tt2));
}
#[test]
fn test_npn_canonization_2_inputs() {
let tt1 = SmallTruthTable::new(|[a, b]| a & b);
let tt2 = SmallTruthTable::new(|[a, b]| !(!b & a));
assert_eq!(exact_npn_canonization(tt1), exact_npn_canonization(tt2));
}
#[test]
fn test_npn_canonization_3_inputs() {
let tt1 = SmallTruthTable::new(|[a, b, c]| a & b ^ c);
let tt2 = SmallTruthTable::new(|[a, b, c]| !(c & !b ^ a));
assert_eq!(exact_npn_canonization(tt1), exact_npn_canonization(tt2));
}
pub fn exact_p_canonization<T>(tt: T) -> T
where
T: Permutable + Ord + Clone,
{
let mut state = tt.clone();
let mut min = tt.clone();
let n = tt.len();
for swap in permutation_swaps(n) {
state.swap(swap.0, swap.1);
if state < min {
state.clone_into(&mut min);
}
}
min
}
pub fn all_p_equivalent_functions<T>(tt: T) -> impl Iterator<Item = T>
where
T: Clone + Permutable,
{
permutations(tt)
}
impl Permutable for SmallTruthTable {
fn len(&self) -> usize {
self.num_inputs()
}
fn swap(&mut self, i: usize, j: usize) {
*self = self.swap_inputs(i, j);
}
}
impl BitFlippable for SmallTruthTable {
fn num_bits(&self) -> usize {
self.num_inputs()
}
fn flip_bit(&mut self, bit_idx: usize) {
*self = self.invert_input(bit_idx)
}
}
impl InvertOutput for SmallTruthTable {
fn inverted_output(mut self) -> Self {
self.invert_output();
self
}
fn invert_output(&mut self) {
*self = <Self as SmallTT>::invert(*self);
}
}
impl<const N: usize> Permutable for SmallStaticTruthTable<N> {
fn len(&self) -> usize {
self.num_inputs()
}
fn swap(&mut self, i: usize, j: usize) {
*self = self.swap_inputs(i, j);
}
}
impl<const N: usize> BitFlippable for SmallStaticTruthTable<N> {
fn num_bits(&self) -> usize {
self.num_inputs()
}
fn flip_bit(&mut self, bit_idx: usize) {
*self = self.invert_input(bit_idx)
}
}
impl<const N: usize> InvertOutput for SmallStaticTruthTable<N> {
fn inverted_output(mut self) -> Self {
self.invert_output();
self
}
fn invert_output(&mut self) {
*self = <Self as SmallTT>::invert(*self);
}
}
#[test]
fn test_p_canonization_2_inputs() {
let tt1 = SmallTruthTable::new(|[a, b]| !a & b);
let tt2 = SmallTruthTable::new(|[a, b]| a & !b);
assert_eq!(exact_p_canonization(tt1), exact_p_canonization(tt2));
}
#[test]
fn test_p_canonization_6_inputs() {
let tt1 = SmallTruthTable::new(|[a, b, c, d, e, f]| a & b & !c | d ^ e ^ f);
let tt2 = SmallTruthTable::new(|[f, b, c, e, d, a]| a & b & !c | d ^ e ^ f);
assert_eq!(exact_p_canonization(tt1), exact_p_canonization(tt2));
}
#[test]
fn test_npn_canonization_get_transform() {
let tt = SmallTruthTable::new(|[a, b, c]| a & b ^ c);
let tracker = NPNTransformTracker::new(tt);
exact_npn_canonization(tt);
let canonical_form = exact_npn_canonization(tracker);
let canonical_truth_table = canonical_form.truth_table();
let transform = canonical_form.transform();
let inverse_transform = transform.inverse();
assert_eq!(transform.apply(tt), canonical_truth_table);
assert_eq!(tt, inverse_transform.apply(canonical_truth_table));
}
#[test]
fn test_generate_3_input_npn_classes() {
use std::collections::HashSet;
let all_functions = (0..(1 << 8)).map(|i| SmallTruthTable::from_table(i, 3));
let canonized = all_functions.map(exact_npn_canonization);
let npn_classes: HashSet<_> = canonized.collect();
assert_eq!(
npn_classes.len(),
14,
"there must be 14 different NPN classes of 3-input Boolean functions"
);
}
#[test]
fn test_generate_4_input_npn_classes() {
use std::collections::HashSet;
let all_functions = (0..(1 << 16)).map(|i| SmallTruthTable::from_table(i, 4));
let canonized = all_functions.map(exact_npn_canonization);
let npn_classes: HashSet<_> = canonized.collect();
assert_eq!(
npn_classes.len(),
222,
"there must be 222 different NPN classes of 4-input Boolean functions"
);
}