Skip to content

Commit

Permalink
support returning details of NPN transformation
Browse files Browse the repository at this point in the history
  • Loading branch information
snowkylin committed Jul 14, 2023
1 parent ebf576e commit b27c031
Show file tree
Hide file tree
Showing 7 changed files with 140 additions and 33 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ project(npn)
set(CMAKE_CXX_STANDARD 14)

#add_executable(npn_exe npn/npn.cpp)
add_library(npn MODULE npn/npn.cpp)
add_library(npn SHARED npn/npn.cpp)
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,15 @@ pip install npn
```python
import npn

# the truth table of f(x_1, x_2, x_3) as [f(0, 0, 0), f(1, 0, 0), f(0, 1, 0), f(1, 1, 0), ...]
# the truth table of f(x_2, x_1, x_0) as [f(0, 0, 0), f(0, 0, 1), f(0, 1, 0), f(0, 1, 1), ...]
tt = [True, True, True, False, True, True, True, True]
npn.npn_canonical_representative(tt) # 254 (11111101)
c = npn.npn_canonical_representative(tt) # c = 254 (11111101)
# return the NPN transformation information (phase, perm, output_inv)
# here phase = [False, False, True], perm = [0, 1, 2] and output_inv = False
# which means (x_0, x_1, x_2) should be mapped to (x_0, x_1, x_2), then the third variable (x_2) should be inverted
# and the final result should not be inverted
# note that permutation should be applied first before the invertion (see Section 2.1 of [1])
c, phase, perm, output_inv = npn.npn_canonical_representative(tt, return_details=True)
```

## Build
Expand All @@ -30,6 +36,12 @@ Compile the shared library `npn.dll` and `libnpn.so` via CMake on Windows and Li
python setup.py sdist
```

To upload to PyPI, run

```bash
twine upload .\dist\npn-X.X.tar.gz
```

## Test

```bash
Expand All @@ -38,6 +50,6 @@ python -m pytest
```

## References
- Chai, D., and A. Kuehlmann. “Building a Better Boolean Matcher and Symmetry Detector.” In Proceedings of the Design Automation & Test in Europe Conference, 1:1–6, 2006. https://doi.org/10.1109/DATE.2006.243959.
- Debnath, D., and T. Sasao. “Efficient Computation of Canonical Form for Boolean Matching in Large Libraries.” In ASP-DAC 2004: Asia and South Pacific Design Automation Conference 2004 (IEEE Cat. No.04EX753), 591–96, 2004. https://doi.org/10.1109/ASPDAC.2004.1337660.
- Hinsberger, U., and R. Kolla. “Boolean Matching for Large Libraries.” In Proceedings 1998 Design and Automation Conference. 35th DAC. (Cat. No.98CH36175), 206–11, 1998. https://doi.org/10.1145/277044.277100.
1. Chai, D., and A. Kuehlmann. “Building a Better Boolean Matcher and Symmetry Detector.” In Proceedings of the Design Automation & Test in Europe Conference, 1:1–6, 2006. https://doi.org/10.1109/DATE.2006.243959.
2. Debnath, D., and T. Sasao. “Efficient Computation of Canonical Form for Boolean Matching in Large Libraries.” In ASP-DAC 2004: Asia and South Pacific Design Automation Conference 2004 (IEEE Cat. No.04EX753), 591–96, 2004. https://doi.org/10.1109/ASPDAC.2004.1337660.
3. Hinsberger, U., and R. Kolla. “Boolean Matching for Large Libraries.” In Proceedings 1998 Design and Automation Conference. 35th DAC. (Cat. No.98CH36175), 206–11, 1998. https://doi.org/10.1145/277044.277100.
2 changes: 1 addition & 1 deletion npn/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +1 @@
from npn.api import generate_permutation_table, npn_canonical_representative, generate_permutation_table_cpp, npn_canonical_representative_cpp, c_bool_p
from npn.api import generate_permutation_table, npn_canonical_representative, generate_permutation_table_cpp, npn_canonical_representative_cpp, c_bool_p, transform_tt, tt_to_int
87 changes: 78 additions & 9 deletions npn/api.py
Original file line number Diff line number Diff line change
@@ -1,49 +1,118 @@
import ctypes
import math
import os
import itertools
import platform

this_dir = os.path.abspath(os.path.dirname(__file__))
if (platform.system() == 'Windows'):
if platform.system() == 'Windows':
mod = ctypes.cdll.LoadLibrary(this_dir + "/npn.dll")
else:
mod = ctypes.cdll.LoadLibrary(this_dir + "/libnpn.so")
# mod = ctypes.cdll.LoadLibrary("cmake-build-release/npn.dll")
# mod = ctypes.cdll.LoadLibrary("cmake-build-release/libnpn.so")

max_num_inputs = 6

c_bool_p = ctypes.POINTER(ctypes.c_bool)

generate_permutation_table_cpp = mod.GeneratePermutationTable
generate_permutation_table_cpp.argtypes = (ctypes.c_uint8,)
generate_permutation_table_cpp.restype = ctypes.c_void_p
generate_permutation_table_cpp.restype = ctypes.POINTER(ctypes.c_uint8)

npn_canonical_representative_cpp = mod.NpnCanonicalRepresentative
npn_canonical_representative_cpp.argtypes = (c_bool_p, ctypes.c_uint8)
npn_canonical_representative_cpp.argtypes = (c_bool_p, ctypes.c_uint8,
ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.c_uint), c_bool_p)
npn_canonical_representative_cpp.restype = ctypes.c_ulonglong

current_num_inputs = -1
perm_table = None


def generate_permutation_table(num_inputs):
global current_num_inputs
if num_inputs > 6:
raise ValueError("This package only support boolean functions with number of inputs <= 6")
if num_inputs > max_num_inputs:
raise ValueError("This package only support boolean functions with number of inputs <= %d" % max_num_inputs)
if num_inputs != current_num_inputs:
generate_permutation_table_cpp(num_inputs)
global perm_table
perm_table = generate_permutation_table_cpp(num_inputs)
current_num_inputs = num_inputs
else:
print("Warning: the permutation table with num_inputs = %d is already generated" % num_inputs)


def npn_canonical_representative(tt, num_inputs=None):
def npn_canonical_representative(tt, num_inputs=None, return_details=False):
if num_inputs is None:
num_inputs = int(math.log2(len(tt)))
num_inputs = base_2_log(len(tt))
if num_inputs != current_num_inputs:
generate_permutation_table(num_inputs)
print("Permutation table generated for %d-input functions" % num_inputs)
if isinstance(tt, list) or isinstance(tt, tuple):
tt = (ctypes.c_bool * len(tt))(*tt)
return npn_canonical_representative_cpp(tt, num_inputs)
phase = ctypes.c_uint8()
id = ctypes.c_uint()
output_inv = ctypes.c_bool()
c = npn_canonical_representative_cpp(tt, num_inputs, ctypes.pointer(phase), ctypes.pointer(id), ctypes.pointer(output_inv))
if return_details:
phase = [bool(phase.value & (1 << i)) for i in range(num_inputs)]
p = [perm_table[i] for i in range(max_num_inputs * id.value, max_num_inputs * id.value + num_inputs)]
return c, phase, p, output_inv.value
else:
return c


# static inline int Abc_Base2Log( unsigned n )
# { int r; if ( n < 2 ) return (int)n; for ( r = 0, n--; n; n >>= 1, r++ ) {}; return r; }
def base_2_log(n: int): # see src/misc/util/abc_global.h in abc
if n < 2:
return n
else:
r = 0
n -= 1
while n > 0:
r += 1
n >>= 1
return r


def transform_tt(tt, phase, perm, output_inv):
res = [False] * len(tt)
num_inputs = base_2_log(len(tt))
for i, value in enumerate(itertools.product([False, True], repeat=num_inputs)): # value = (x_2, x_1, x_0)
value = list(reversed(value)) # reverse `value` to (x_0, x_1, x_2) so that x_i can be accessed by index i
value_new = [0] * num_inputs
for j in range(num_inputs):
value_new[j] = value[perm[j]] # permutate first, then change the phase of the input
if phase[j]: # x_i -> not(x_i), note that `phase` is already reversed
value_new[j] = not value_new[j]
id = 0
for j in range(num_inputs):
if value_new[j]:
id += 2 ** j
res[i] = not(tt[id]) if output_inv else tt[id]
return res


def tt_to_int(tt): # use reverse order, see line 1482 of src/misc/util/utilTruth.h in abc
tt_size = len(tt)
res = 0
for i, b in enumerate(tt):
if b:
res += 2 ** (tt_size - 1 - i)
return res


# num_inputs = 2
# generate_permutation_table(num_inputs)
# # p = [0] * num_inputs
# # p_ctypes = (ctypes.c_uint8 * len(p))(*p)
# tt = [False, True, False, True]
# # tt = [False, False, False, True, False, False, False, False]
# # tt = [True, True, True, False, True, True, True, True] # f(x_2, x_1, x_0) = x_2 + not(x_1) + not(x_0)
# c, phase, perm, output_inv = npn_canonical_representative(tt, return_details=True)
# npn_tt = transform_tt(tt, phase, perm, output_inv)
# c_npn_tt = tt_to_int(npn_tt)
# pass



51 changes: 36 additions & 15 deletions npn/npn.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,22 +20,24 @@ const uint MAX_SIZE = (1 << MAX_NUM_INPUTS) * MAX_PERM_SIZE;
const uint MAX_POS_SIZE = 1 << MAX_NUM_INPUTS;

uint8 pos[MAX_PERM_SIZE][MAX_POS_SIZE];
bool perm[MAX_PERM_SIZE][MAX_NUM_INPUTS];
uint8 perm[MAX_PERM_SIZE][MAX_NUM_INPUTS];
uint8 phase_[MAX_SIZE], phase_next_[MAX_SIZE];
uint ids_[MAX_SIZE], ids_next_[MAX_SIZE];

uint8 c_num_inputs;

extern "C" {
LIBRARY_API void GeneratePermutationTable(uint8 num_inputs);
LIBRARY_API ulonglong NpnCanonicalRepresentative(bool* tt, uint8 num_inputs);
LIBRARY_API ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs);
LIBRARY_API uint8* GeneratePermutationTable(uint8 num_inputs);
LIBRARY_API ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs, uint8* phase_p, uint* id_p);
LIBRARY_API ulonglong NpnCanonicalRepresentative(bool* tt, uint8 num_inputs, uint8* phase_p, uint* id_p, bool* not_p);
}

void GeneratePermutationTable(uint8 num_inputs) {
uint8* GeneratePermutationTable(uint8 num_inputs) {
uint n = Factorial(num_inputs);
uint num_prev_perm = 1;
for (uint i = 0; i < n; i++) {
pos[i][0] = 0;
for (uint8 j = 0; j < num_inputs; j++) perm[i][j] = true;
for (uint8 j = 0; j < num_inputs; j++) perm[i][j] = num_inputs;
}

for (uint8 k = 0; k < num_inputs; k++) {
Expand All @@ -44,9 +46,9 @@ void GeneratePermutationTable(uint8 num_inputs) {
uint num_duplicate = n / num_prev_perm / num_perm;
uint id = 0;
for (uint i = 0; i < num_prev_perm; i++) {
for (uint8 j = 0; j < num_inputs; j++) if (perm[id][j]) {
for (uint8 j = 0; j < num_inputs; j++) if (perm[id][j] == num_inputs) {
for (uint t = 0; t < num_duplicate; t++) {
perm[id][j] = false;
perm[id][j] = k;
for (uint8 l = 0; l < num_levels; l++) {
pos[id][num_levels + l] = pos[id][l] + (1 << j);
}
Expand All @@ -56,9 +58,11 @@ void GeneratePermutationTable(uint8 num_inputs) {
}
num_prev_perm *= num_perm;
}
c_num_inputs = num_inputs;
return (uint8*) perm;
}

ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs) {
ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs, uint8* phase_p, uint* id_p) {
uint n = Factorial(num_inputs);
uint tt_size = 1 << num_inputs;
bool all_false = true;
Expand Down Expand Up @@ -98,7 +102,7 @@ ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs) {
q_next_size = 0;
bool all_zeros = true;
for (uint i = 0; i < q_size; i++) {
uint8 pos_1_phase_l = phase[i] ^ pos[ids[i]][l + num_levels];
uint8 pos_1_phase_l = phase[i] ^ pos[ids[i]][l + num_levels]; // xor, true when two operands are different
bool seq_part_l = tt[pos_1_phase_l];
if (all_zeros && seq_part_l) {
all_zeros = false;
Expand All @@ -117,18 +121,35 @@ ulonglong NpCanonicalRepresentative(bool* tt, uint8 num_inputs) {
num_prev_perm *= num_perm;
}
ulonglong c = 0;
uint8 c_phase = phase[0];
uint c_id = ids[0];
for (uint8 i = 0; i < tt_size; i++) {
if (tt[phase[0] ^ pos[ids[0]][i]]) c+= (ulonglong)(1) << (tt_size - 1 - i);
if (tt[c_phase ^ pos[c_id][i]]) c+= (ulonglong)(1) << (tt_size - 1 - i);
}
*phase_p = c_phase;
*id_p = c_id;
return c;
}

ulonglong NpnCanonicalRepresentative(bool* tt, uint8 num_inputs) {
ulonglong c_1 = NpCanonicalRepresentative(tt, num_inputs);
ulonglong NpnCanonicalRepresentative(bool* tt, uint8 num_inputs, uint8* phase_p, uint* id_p, bool* not_p) {
c_num_inputs = num_inputs;
uint8 c_phase_0, c_phase_1;
uint c_id_0, c_id_1;
ulonglong c_1 = NpCanonicalRepresentative(tt, num_inputs, &c_phase_0, &c_id_0);
for (uint8 i = 0; i < 1 << num_inputs; i++) tt[i] = !tt[i];
ulonglong c_2 = NpCanonicalRepresentative(tt, num_inputs);
ulonglong c_2 = NpCanonicalRepresentative(tt, num_inputs, &c_phase_1, &c_id_1);
for (uint8 i = 0; i < 1 << num_inputs; i++) tt[i] = !tt[i];
return (c_1 > c_2)? c_1 : c_2;
if (c_1 > c_2) {
*phase_p = c_phase_0;
*id_p = c_id_0;
*not_p = false;
return c_1;
} else {
*phase_p = c_phase_1;
*id_p = c_id_1;
*not_p = true;
return c_2;
}
}

/*int main() {
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

setup(
name='npn',
version='1.0',
version='1.1',
author='Xihan Li',
author_email='[email protected]',
description='A boolean matcher that computes the canonical representative, which is unique for each NPN equivalence class, for a given boolean function represented by truth table.',
Expand Down
7 changes: 6 additions & 1 deletion tests/test_npn.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ def test_npn():
for num_inputs in num_npn_classes.keys():
npn_classes = set()
for i, tt in enumerate(itertools.product([False, True], repeat=2 ** num_inputs)):
c = npn.npn_canonical_representative(tt)
c, phase, perm, output_inv = npn.npn_canonical_representative(tt, return_details=True)
# print(tt, c, phase, perm, output_inv, end=' ')
npn_tt = npn.transform_tt(tt, phase, perm, output_inv)
c_npn_tt = npn.tt_to_int(npn_tt)
# print(npn_tt, c_npn_tt)
assert c_npn_tt == c
npn_classes.add(c)
print("number of npn classes for %d-input boolean functions: %d" % (num_inputs, len(npn_classes)))
assert len(npn_classes) == num_npn_classes[num_inputs]

0 comments on commit b27c031

Please sign in to comment.