diff --git a/src/lib.rs b/src/lib.rs index 53c2135..f528663 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -8,7 +8,7 @@ //! fixed-size truth tables ([`Lut2` to `Lut12`](https://docs.rs/volute/latest/volute/struct.StaticLut.html)). //! They provide logical operators and utility functions for analysis, canonization and decomposition. //! Some support is available for other standard representation, such as Sum-of-Products -//! ([`Sop`](https://docs.rs/volute/latest/volute/struct.Sop.html)). +//! ([`Sop`](https://docs.rs/volute/latest/volute/sop/struct.Sop.html)). //! //! API and documentation try to follow the same terminology as the C++ library [Kitty](https://libkitty.readthedocs.io/en/latest). //! @@ -52,25 +52,14 @@ mod bdd; mod canonization; -mod cube; mod decomposition; -mod ecube; mod lut; mod operations; -mod soes; -mod sop; +pub mod sop; mod static_lut; -#[cfg(feature = "optim-mip")] -#[cfg_attr(docsrs, doc(cfg(feature = "optim-mip")))] -pub mod optim; - -pub use cube::Cube; pub use decomposition::DecompositionType; -pub use ecube::Ecube; pub use lut::Lut; -pub use soes::Soes; -pub use sop::Sop; pub use static_lut::StaticLut; pub use static_lut::{ diff --git a/src/sop.rs b/src/sop.rs index e6b6d35..93ffb7d 100644 --- a/src/sop.rs +++ b/src/sop.rs @@ -1,377 +1,18 @@ -use std::{ - fmt, - ops::{BitAnd, BitOr, Not}, -}; - -use crate::{cube::Cube, Lut}; - -/// Sum of Products representation (Or of And) -/// -/// This is the usual representation for 2-level logic optimization. Any boolean function can be -/// represented this way, and the optimization can be done quite efficiently (with -/// [Espresso](https://en.wikipedia.org/wiki/Espresso_heuristic_logic_minimizer), for example). -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] -pub struct Sop { - num_vars: usize, - cubes: Vec, -} - -impl Sop { - /// Query the number of variables - pub fn num_vars(&self) -> usize { - self.num_vars - } - - /// Return the constant zero Sop - pub fn zero(num_vars: usize) -> Sop { - Sop { - num_vars, - cubes: vec![], - } - } - - /// Return the constant one Sop - pub fn one(num_vars: usize) -> Sop { - Sop { - num_vars, - cubes: vec![Cube::one()], - } - } - - /// Number of cubes in the Sop - pub fn num_cubes(&self) -> usize { - self.cubes.len() - } - - /// Number of literals in the Sop - pub fn num_lits(&self) -> usize { - let mut ret = 0; - for c in &self.cubes { - ret += c.num_lits(); - } - ret - } - - /// Returns whether the Sop is trivially constant zero - pub fn is_zero(&self) -> bool { - self.cubes.is_empty() - } - - /// Returns whether the Sop is trivially constant one - pub fn is_one(&self) -> bool { - match self.cubes.first() { - Some(c) => c.is_one(), - None => false, - } - } - - /// Return the Sop representing the nth variable - pub fn nth_var(num_vars: usize, var: usize) -> Sop { - Sop { - num_vars, - cubes: vec![Cube::nth_var(var)], - } - } - - /// Return the cube representing the nth variable, inverted - pub fn nth_var_inv(num_vars: usize, var: usize) -> Sop { - Sop { - num_vars, - cubes: vec![Cube::nth_var_inv(var)], - } - } - - /// Build an Sop from cubes - pub fn from_cubes(num_vars: usize, cubes: Vec) -> Sop { - for c in &cubes { - for v in c.pos_vars() { - assert!(v < num_vars); - } - for v in c.neg_vars() { - assert!(v < num_vars); - } - } - Sop { num_vars, cubes } - } - - /// Returns the cubes in the Sop - pub fn cubes(&self) -> &[Cube] { - &self.cubes - } - - /// Get the value of the Sop for these inputs (input bits packed in the mask) - pub fn value(&self, mask: usize) -> bool { - let mut ret = false; - for c in &self.cubes { - ret |= c.value(mask); - } - ret - } - - /// Basic simplification of the Sop - /// - /// The following simplifications are performed: - /// * Zero cubes are removed - /// * Cubes that imply another are removed - /// - /// The following are not yet implemented: - /// * Cubes that differ by one literal are merged - fn simplify(&mut self) { - // No need for zeros - self.cubes.retain(|c| !c.is_zero()); - // Remove any duplicate cube the easy way - self.cubes.sort(); - self.cubes.dedup(); - // Remove any cube that implies another - let mut new_cubes = Vec::new(); - for c in &self.cubes { - if self.cubes.iter().all(|o| *c == *o || !c.implies(*o)) { - new_cubes.push(*c); - } - } - self.cubes = new_cubes; - } - - /// Compute the or of two Sops - fn or(a: &Sop, b: &Sop) -> Sop { - assert_eq!(a.num_vars, b.num_vars); - let mut cubes = a.cubes.clone(); - cubes.extend(&b.cubes); - let mut ret = Sop { - num_vars: a.num_vars, - cubes, - }; - ret.simplify(); - ret - } - - /// Compute the and of two Sops - fn and(a: &Sop, b: &Sop) -> Sop { - assert_eq!(a.num_vars, b.num_vars); - let mut cubes = Vec::new(); - for c1 in &a.cubes { - for c2 in &b.cubes { - let c = c1 & c2; - if c != Cube::zero() { - cubes.push(c); - } - } - } - let mut ret = Sop { - num_vars: a.num_vars, - cubes, - }; - ret.simplify(); - ret - } -} - -impl Not for Sop { - type Output = Sop; - fn not(self) -> Self::Output { - !&self - } -} - -impl Not for &Sop { - type Output = Sop; - fn not(self) -> Self::Output { - let mut ret = Sop::one(self.num_vars); - for c in &self.cubes { - let mut v = Vec::new(); - for l in c.pos_vars() { - v.push(Cube::nth_var_inv(l)); - } - for l in c.neg_vars() { - v.push(Cube::nth_var(l)); - } - let s = Sop { - num_vars: self.num_vars, - cubes: v, - }; - ret = ret & s; - } - ret - } -} - -impl BitAnd for Sop { - type Output = Sop; - fn bitand(self, rhs: Sop) -> Self::Output { - Sop::and(&self, &rhs) - } -} - -impl BitAnd for &Sop { - type Output = Sop; - fn bitand(self, rhs: Sop) -> Self::Output { - Sop::and(self, &rhs) - } -} - -impl BitAnd<&Sop> for &Sop { - type Output = Sop; - fn bitand(self, rhs: &Sop) -> Self::Output { - Sop::and(self, rhs) - } -} - -impl BitAnd<&Sop> for Sop { - type Output = Sop; - fn bitand(self, rhs: &Sop) -> Self::Output { - Sop::and(&self, rhs) - } -} - -impl BitOr for Sop { - type Output = Sop; - fn bitor(self, rhs: Sop) -> Self::Output { - Sop::or(&self, &rhs) - } -} - -impl BitOr for &Sop { - type Output = Sop; - fn bitor(self, rhs: Sop) -> Self::Output { - Sop::or(self, &rhs) - } -} - -impl BitOr<&Sop> for &Sop { - type Output = Sop; - fn bitor(self, rhs: &Sop) -> Self::Output { - Sop::or(self, rhs) - } -} - -impl BitOr<&Sop> for Sop { - type Output = Sop; - fn bitor(self, rhs: &Sop) -> Self::Output { - Sop::or(&self, rhs) - } -} - -impl fmt::Display for Sop { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - if self.is_zero() { - write!(f, "0")?; - return Ok(()); - } - let s = self - .cubes - .iter() - .map(|c| c.to_string()) - .collect::>() - .join(" | "); - write!(f, "{}", s) - } -} - -impl From<&Lut> for Sop { - fn from(value: &Lut) -> Self { - let mut ret = Sop::zero(value.num_vars()); - let mx = value.num_bits(); - for mask in 0..mx { - if value.value(mask) { - ret.cubes.push(Cube::minterm(value.num_vars(), mask)); - } - } - ret - } -} - -impl From for Sop { - fn from(value: Lut) -> Self { - Sop::from(&value) - } -} - -impl From<&Sop> for Lut { - fn from(value: &Sop) -> Self { - let mut ret = Lut::zero(value.num_vars()); - let mx = ret.num_bits(); - for mask in 0..mx { - if value.value(mask) { - ret.set_bit(mask); - } - } - ret - } -} - -impl From for Lut { - fn from(value: Sop) -> Self { - Lut::from(&value) - } -} - -#[cfg(test)] -mod tests { - use super::Sop; - - #[test] - fn test_zero_one() { - assert!(Sop::zero(32).is_zero()); - assert!(!Sop::one(32).is_zero()); - assert!(!Sop::zero(32).is_one()); - assert!(Sop::one(32).is_one()); - for i in 0..32 { - assert!(!Sop::nth_var(32, i).is_zero()); - assert!(!Sop::nth_var(32, i).is_one()); - assert!(!Sop::nth_var_inv(32, i).is_zero()); - assert!(!Sop::nth_var_inv(32, i).is_one()); - } - } - - #[test] - #[cfg(feature = "rand")] - fn test_not() { - use crate::Lut; - - for i in 0..8 { - for _ in 0..10 { - let l = Lut::random(i); - let ln = !&l; - let s: Sop = l.into(); - let sn = !&s; - assert_eq!(ln, sn.into()); - } - } - } - - #[test] - #[cfg(feature = "rand")] - fn test_or() { - use crate::Lut; - - for i in 0..8 { - for _ in 0..10 { - let l1 = Lut::random(i); - let l2 = Lut::random(i); - let lo = &l1 | &l2; - let s1: Sop = l1.into(); - let s2: Sop = l2.into(); - let so = s1 | s2; - assert_eq!(lo, so.into()); - } - } - } - - #[test] - #[cfg(feature = "rand")] - fn test_and() { - use crate::Lut; - - for i in 0..8 { - for _ in 0..10 { - let l1 = Lut::random(i); - let l2 = Lut::random(i); - let lo = &l1 & &l2; - let s1: Sop = l1.into(); - let s2: Sop = l2.into(); - let so = s1 & s2; - assert_eq!(lo, so.into()); - } - } - } -} +//! Sum-of-Products representations +//! +//! This module provides two-level representations such as [Sumof-Products](crate::sop::Sop) +//! and [Sum-of-Exclusive Sums](crate::sop::Soes). + +mod cube; +mod ecube; +mod soes; +mod sop; + +#[cfg(feature = "optim-mip")] +#[cfg_attr(docsrs, doc(cfg(feature = "optim-mip")))] +pub mod optim; + +pub use cube::Cube; +pub use ecube::Ecube; +pub use soes::Soes; +pub use sop::Sop; diff --git a/src/cube.rs b/src/sop/cube.rs similarity index 100% rename from src/cube.rs rename to src/sop/cube.rs diff --git a/src/ecube.rs b/src/sop/ecube.rs similarity index 99% rename from src/ecube.rs rename to src/sop/ecube.rs index 1e0de26..b3826ed 100644 --- a/src/ecube.rs +++ b/src/sop/ecube.rs @@ -8,7 +8,7 @@ use std::{ use crate::Lut; -/// Representation of the Xor of variables, similar to [`Cube`](crate::cube::Cube) for Xor +/// Representation of the Xor of variables, similar to [`Cube`](crate::sop::Cube) for Xor /// /// Each variable is represented by a bit, and the overall parity (Xor or Xnor) is represented /// on the side. diff --git a/src/optim.rs b/src/sop/optim.rs similarity index 98% rename from src/optim.rs rename to src/sop/optim.rs index fde5566..f29a576 100644 --- a/src/optim.rs +++ b/src/sop/optim.rs @@ -12,11 +12,11 @@ use good_lp::Solution; use good_lp::SolverModel; use good_lp::Variable; -use crate::Cube; -use crate::Ecube; +use crate::sop::Cube; +use crate::sop::Ecube; +use crate::sop::Soes; +use crate::sop::Sop; use crate::Lut; -use crate::Soes; -use crate::Sop; /// Enumerate cubes that can be used to implement the given function. That is, cubes that are imply the function fn enumerate_valid_cubes(function: &Lut) -> Vec { @@ -336,7 +336,7 @@ pub fn optimize_sopes_mip( #[cfg(test)] mod tests { - use crate::{optim::optimize_sop_mip, optim::optimize_sopes_mip, Lut}; + use crate::{sop::optim::optimize_sop_mip, sop::optim::optimize_sopes_mip, Lut}; #[test] #[cfg(feature = "rand")] diff --git a/src/soes.rs b/src/sop/soes.rs similarity index 99% rename from src/soes.rs rename to src/sop/soes.rs index 09bd1d4..2ea745b 100644 --- a/src/soes.rs +++ b/src/sop/soes.rs @@ -1,6 +1,7 @@ use std::{fmt, ops::BitOr}; -use crate::{ecube::Ecube, Lut}; +use crate::sop::Ecube; +use crate::Lut; /// Sum of Exclusive Sums representation (Or of Xor) /// diff --git a/src/sop/sop.rs b/src/sop/sop.rs new file mode 100644 index 0000000..e1b86b5 --- /dev/null +++ b/src/sop/sop.rs @@ -0,0 +1,378 @@ +use std::{ + fmt, + ops::{BitAnd, BitOr, Not}, +}; + +use crate::sop::Cube; +use crate::Lut; + +/// Sum of Products representation (Or of And) +/// +/// This is the usual representation for 2-level logic optimization. Any boolean function can be +/// represented this way, and the optimization can be done quite efficiently (with +/// [Espresso](https://en.wikipedia.org/wiki/Espresso_heuristic_logic_minimizer), for example). +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +pub struct Sop { + num_vars: usize, + cubes: Vec, +} + +impl Sop { + /// Query the number of variables + pub fn num_vars(&self) -> usize { + self.num_vars + } + + /// Return the constant zero Sop + pub fn zero(num_vars: usize) -> Sop { + Sop { + num_vars, + cubes: vec![], + } + } + + /// Return the constant one Sop + pub fn one(num_vars: usize) -> Sop { + Sop { + num_vars, + cubes: vec![Cube::one()], + } + } + + /// Number of cubes in the Sop + pub fn num_cubes(&self) -> usize { + self.cubes.len() + } + + /// Number of literals in the Sop + pub fn num_lits(&self) -> usize { + let mut ret = 0; + for c in &self.cubes { + ret += c.num_lits(); + } + ret + } + + /// Returns whether the Sop is trivially constant zero + pub fn is_zero(&self) -> bool { + self.cubes.is_empty() + } + + /// Returns whether the Sop is trivially constant one + pub fn is_one(&self) -> bool { + match self.cubes.first() { + Some(c) => c.is_one(), + None => false, + } + } + + /// Return the Sop representing the nth variable + pub fn nth_var(num_vars: usize, var: usize) -> Sop { + Sop { + num_vars, + cubes: vec![Cube::nth_var(var)], + } + } + + /// Return the cube representing the nth variable, inverted + pub fn nth_var_inv(num_vars: usize, var: usize) -> Sop { + Sop { + num_vars, + cubes: vec![Cube::nth_var_inv(var)], + } + } + + /// Build an Sop from cubes + pub fn from_cubes(num_vars: usize, cubes: Vec) -> Sop { + for c in &cubes { + for v in c.pos_vars() { + assert!(v < num_vars); + } + for v in c.neg_vars() { + assert!(v < num_vars); + } + } + Sop { num_vars, cubes } + } + + /// Returns the cubes in the Sop + pub fn cubes(&self) -> &[Cube] { + &self.cubes + } + + /// Get the value of the Sop for these inputs (input bits packed in the mask) + pub fn value(&self, mask: usize) -> bool { + let mut ret = false; + for c in &self.cubes { + ret |= c.value(mask); + } + ret + } + + /// Basic simplification of the Sop + /// + /// The following simplifications are performed: + /// * Zero cubes are removed + /// * Cubes that imply another are removed + /// + /// The following are not yet implemented: + /// * Cubes that differ by one literal are merged + fn simplify(&mut self) { + // No need for zeros + self.cubes.retain(|c| !c.is_zero()); + // Remove any duplicate cube the easy way + self.cubes.sort(); + self.cubes.dedup(); + // Remove any cube that implies another + let mut new_cubes = Vec::new(); + for c in &self.cubes { + if self.cubes.iter().all(|o| *c == *o || !c.implies(*o)) { + new_cubes.push(*c); + } + } + self.cubes = new_cubes; + } + + /// Compute the or of two Sops + fn or(a: &Sop, b: &Sop) -> Sop { + assert_eq!(a.num_vars, b.num_vars); + let mut cubes = a.cubes.clone(); + cubes.extend(&b.cubes); + let mut ret = Sop { + num_vars: a.num_vars, + cubes, + }; + ret.simplify(); + ret + } + + /// Compute the and of two Sops + fn and(a: &Sop, b: &Sop) -> Sop { + assert_eq!(a.num_vars, b.num_vars); + let mut cubes = Vec::new(); + for c1 in &a.cubes { + for c2 in &b.cubes { + let c = c1 & c2; + if c != Cube::zero() { + cubes.push(c); + } + } + } + let mut ret = Sop { + num_vars: a.num_vars, + cubes, + }; + ret.simplify(); + ret + } +} + +impl Not for Sop { + type Output = Sop; + fn not(self) -> Self::Output { + !&self + } +} + +impl Not for &Sop { + type Output = Sop; + fn not(self) -> Self::Output { + let mut ret = Sop::one(self.num_vars); + for c in &self.cubes { + let mut v = Vec::new(); + for l in c.pos_vars() { + v.push(Cube::nth_var_inv(l)); + } + for l in c.neg_vars() { + v.push(Cube::nth_var(l)); + } + let s = Sop { + num_vars: self.num_vars, + cubes: v, + }; + ret = ret & s; + } + ret + } +} + +impl BitAnd for Sop { + type Output = Sop; + fn bitand(self, rhs: Sop) -> Self::Output { + Sop::and(&self, &rhs) + } +} + +impl BitAnd for &Sop { + type Output = Sop; + fn bitand(self, rhs: Sop) -> Self::Output { + Sop::and(self, &rhs) + } +} + +impl BitAnd<&Sop> for &Sop { + type Output = Sop; + fn bitand(self, rhs: &Sop) -> Self::Output { + Sop::and(self, rhs) + } +} + +impl BitAnd<&Sop> for Sop { + type Output = Sop; + fn bitand(self, rhs: &Sop) -> Self::Output { + Sop::and(&self, rhs) + } +} + +impl BitOr for Sop { + type Output = Sop; + fn bitor(self, rhs: Sop) -> Self::Output { + Sop::or(&self, &rhs) + } +} + +impl BitOr for &Sop { + type Output = Sop; + fn bitor(self, rhs: Sop) -> Self::Output { + Sop::or(self, &rhs) + } +} + +impl BitOr<&Sop> for &Sop { + type Output = Sop; + fn bitor(self, rhs: &Sop) -> Self::Output { + Sop::or(self, rhs) + } +} + +impl BitOr<&Sop> for Sop { + type Output = Sop; + fn bitor(self, rhs: &Sop) -> Self::Output { + Sop::or(&self, rhs) + } +} + +impl fmt::Display for Sop { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if self.is_zero() { + write!(f, "0")?; + return Ok(()); + } + let s = self + .cubes + .iter() + .map(|c| c.to_string()) + .collect::>() + .join(" | "); + write!(f, "{}", s) + } +} + +impl From<&Lut> for Sop { + fn from(value: &Lut) -> Self { + let mut ret = Sop::zero(value.num_vars()); + let mx = value.num_bits(); + for mask in 0..mx { + if value.value(mask) { + ret.cubes.push(Cube::minterm(value.num_vars(), mask)); + } + } + ret + } +} + +impl From for Sop { + fn from(value: Lut) -> Self { + Sop::from(&value) + } +} + +impl From<&Sop> for Lut { + fn from(value: &Sop) -> Self { + let mut ret = Lut::zero(value.num_vars()); + let mx = ret.num_bits(); + for mask in 0..mx { + if value.value(mask) { + ret.set_bit(mask); + } + } + ret + } +} + +impl From for Lut { + fn from(value: Sop) -> Self { + Lut::from(&value) + } +} + +#[cfg(test)] +mod tests { + use super::Sop; + + #[test] + fn test_zero_one() { + assert!(Sop::zero(32).is_zero()); + assert!(!Sop::one(32).is_zero()); + assert!(!Sop::zero(32).is_one()); + assert!(Sop::one(32).is_one()); + for i in 0..32 { + assert!(!Sop::nth_var(32, i).is_zero()); + assert!(!Sop::nth_var(32, i).is_one()); + assert!(!Sop::nth_var_inv(32, i).is_zero()); + assert!(!Sop::nth_var_inv(32, i).is_one()); + } + } + + #[test] + #[cfg(feature = "rand")] + fn test_not() { + use crate::Lut; + + for i in 0..8 { + for _ in 0..10 { + let l = Lut::random(i); + let ln = !&l; + let s: Sop = l.into(); + let sn = !&s; + assert_eq!(ln, sn.into()); + } + } + } + + #[test] + #[cfg(feature = "rand")] + fn test_or() { + use crate::Lut; + + for i in 0..8 { + for _ in 0..10 { + let l1 = Lut::random(i); + let l2 = Lut::random(i); + let lo = &l1 | &l2; + let s1: Sop = l1.into(); + let s2: Sop = l2.into(); + let so = s1 | s2; + assert_eq!(lo, so.into()); + } + } + } + + #[test] + #[cfg(feature = "rand")] + fn test_and() { + use crate::Lut; + + for i in 0..8 { + for _ in 0..10 { + let l1 = Lut::random(i); + let l2 = Lut::random(i); + let lo = &l1 & &l2; + let s1: Sop = l1.into(); + let s2: Sop = l2.into(); + let so = s1 & s2; + assert_eq!(lo, so.into()); + } + } + } +}