├── .python-version ├── libs ├── .dcignore └── CMakeLists.txt ├── docs ├── support.md ├── contributing.md ├── example.pla ├── examples │ └── i10.aig ├── _static │ ├── aigverse_repo_banner.png │ ├── aigverse_logo_dark_mode.png │ ├── aigverse_logo_light_mode.png │ └── custom.css ├── index.md ├── installation.md ├── truth_tables.md ├── machine_learning.md ├── conf.py └── algorithms.md ├── test ├── resources │ ├── or.aag │ ├── test.pla │ ├── and_with_names.aag │ ├── seq.aag │ ├── mux21.aig │ └── test.v ├── inout │ ├── test_read_pla.py │ ├── test_write_verilog.py │ ├── test_write_aiger.py │ ├── test_read_verilog.py │ ├── test_write_dot.py │ └── test_read_aiger.py ├── networks │ ├── test_fanout_aig.py │ ├── test_depth_aig.py │ └── test_named_aig.py ├── algorithms │ ├── test_equivalence_checking.py │ ├── test_rewriting.py │ ├── test_refactoring.py │ ├── test_resubstitution.py │ └── test_simulation.py └── adapters │ ├── test_numpy.py │ └── test_index_list.py ├── src └── aigverse │ ├── py.typed │ ├── _version.pyi │ ├── adapters │ ├── __init__.py │ ├── edge_list.cpp │ └── index_list.cpp │ ├── io │ ├── write_dot.cpp │ ├── write_aiger.cpp │ ├── write_verilog.cpp │ ├── read_pla.cpp │ ├── read_verilog.cpp │ └── read_aiger.cpp │ ├── pch.hpp │ ├── CMakeLists.txt │ ├── __init__.py │ ├── algorithms │ ├── equivalence_checking.cpp │ ├── resubstitution.cpp │ ├── refactoring.cpp │ ├── rewriting.cpp │ ├── balancing.cpp │ └── simulation.cpp │ ├── aigverse.cpp │ └── truth_tables │ └── operations.cpp ├── .gitmodules ├── cmake ├── InterproceduralOptimization.cmake ├── CheckSubmodules.cmake ├── PreventInSourceBuilds.cmake ├── Cache.cmake ├── StandardProjectSettings.cmake ├── ExternalDependencies.cmake ├── Sanitizers.cmake ├── ProjectOptions.cmake └── CompilerWarnings.cmake ├── include ├── aigverse │ ├── truth_tables │ │ ├── truth_table.hpp │ │ └── operations.hpp │ ├── algorithms │ │ ├── balancing.hpp │ │ ├── rewriting.hpp │ │ ├── refactoring.hpp │ │ ├── simulation.hpp │ │ ├── resubstitution.hpp │ │ └── equivalence_checking.hpp │ ├── io │ │ ├── write_dot.hpp │ │ ├── write_aiger.hpp │ │ ├── write_verilog.hpp │ │ ├── read_pla.hpp │ │ ├── read_verilog.hpp │ │ └── read_aiger.hpp │ ├── networks │ │ └── logic_networks.hpp │ ├── types.hpp │ └── adapters │ │ ├── index_list.hpp │ │ └── edge_list.hpp └── CMakeLists.txt ├── .github ├── SECURITY.md ├── pull_request_template.md ├── ISSUE_TEMPLATE │ ├── feature-request.yml │ └── bug-report.yml ├── support.md ├── renovate.json5 ├── workflows │ ├── aigverse-python-tests.yml │ ├── aigverse-codeql.yml │ └── aigverse-pypi-deployment.yml └── contributing.md ├── LICENSE ├── .readthedocs.yaml ├── CMakeLists.txt ├── .clang-format ├── .clang-tidy ├── .pre-commit-config.yaml ├── .gitignore └── noxfile.py /.python-version: -------------------------------------------------------------------------------- 1 | 3.12 2 | -------------------------------------------------------------------------------- /libs/.dcignore: -------------------------------------------------------------------------------- 1 | * 2 | ** 3 | -------------------------------------------------------------------------------- /docs/support.md: -------------------------------------------------------------------------------- 1 | ```{include} ../.github/support.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /test/resources/or.aag: -------------------------------------------------------------------------------- 1 | aag 3 2 0 1 1 2 | 2 3 | 4 4 | 7 5 | 6 3 5 6 | -------------------------------------------------------------------------------- /docs/contributing.md: -------------------------------------------------------------------------------- 1 | ```{include} ../.github/contributing.md 2 | 3 | ``` 4 | -------------------------------------------------------------------------------- /docs/example.pla: -------------------------------------------------------------------------------- 1 | .i 3 2 | .o 2 3 | 1-1 11 4 | 00- 10 5 | -11 01 6 | .e 7 | -------------------------------------------------------------------------------- /test/resources/test.pla: -------------------------------------------------------------------------------- 1 | .i 3 2 | .o 2 3 | 1-1 11 4 | 00- 10 5 | -11 01 6 | .e 7 | -------------------------------------------------------------------------------- /docs/examples/i10.aig: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelwa/aigverse/HEAD/docs/examples/i10.aig -------------------------------------------------------------------------------- /src/aigverse/py.typed: -------------------------------------------------------------------------------- 1 | # Instruct type checkers to look for inline type annotations in this package. 2 | # See PEP 561. 3 | -------------------------------------------------------------------------------- /docs/_static/aigverse_repo_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelwa/aigverse/HEAD/docs/_static/aigverse_repo_banner.png -------------------------------------------------------------------------------- /docs/_static/aigverse_logo_dark_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelwa/aigverse/HEAD/docs/_static/aigverse_logo_dark_mode.png -------------------------------------------------------------------------------- /docs/_static/aigverse_logo_light_mode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcelwa/aigverse/HEAD/docs/_static/aigverse_logo_light_mode.png -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "libs/mockturtle"] 2 | path = libs/mockturtle 3 | url = https://github.com/marcelwa/mockturtle.git 4 | branch = mnt 5 | -------------------------------------------------------------------------------- /test/resources/and_with_names.aag: -------------------------------------------------------------------------------- 1 | aag 3 2 0 1 1 2 | 2 3 | 4 4 | 6 5 | 6 2 4 6 | i0 input_a 7 | i1 input_b 8 | o0 output_and 9 | c 10 | Simple AND gate with names for testing 11 | -------------------------------------------------------------------------------- /test/resources/seq.aag: -------------------------------------------------------------------------------- 1 | aag 7 2 1 2 4 2 | 2 3 | 4 4 | 6 8 5 | 6 6 | 7 7 | 8 2 6 8 | 10 3 7 9 | 12 9 11 10 | 14 4 12 11 | i0 foo 12 | i1 bar 13 | l0 barfoo 14 | o0 foobar 15 | o1 barbar 16 | -------------------------------------------------------------------------------- /test/resources/mux21.aig: -------------------------------------------------------------------------------- 1 | aig 6 3 0 1 3 2 | 13 3 | c 4 | top 5 | This file was written by ABC on Wed Sep 4 21:10:42 2024 6 | For information about AIGER format, refer to http://fmv.jku.at/aiger 7 | -------------------------------------------------------------------------------- /src/aigverse/_version.pyi: -------------------------------------------------------------------------------- 1 | __version__: str 2 | version: str 3 | __version_tuple__: tuple[int, int, int, str, str] | tuple[int, int, int] 4 | version_tuple: tuple[int, int, int, str, str] | tuple[int, int, int] 5 | -------------------------------------------------------------------------------- /test/resources/test.v: -------------------------------------------------------------------------------- 1 | module top( y1, y2, a, b, c ) ; 2 | input a , b , c ; 3 | output y1; 4 | wire g0, g1 , g2 , g3 , g4 ; 5 | assign g0 = a ; 6 | assign g1 = ~c ; 7 | assign g3 = b ; 8 | assign g2 = g0 & g1 ; 9 | assign g4 = g3 | g2 ; 10 | assign y1 = g4 ; 11 | endmodule 12 | -------------------------------------------------------------------------------- /cmake/InterproceduralOptimization.cmake: -------------------------------------------------------------------------------- 1 | macro(aigverse_enable_ipo) 2 | include(CheckIPOSupported) 3 | check_ipo_supported(RESULT result OUTPUT output) 4 | if(result) 5 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION ON) 6 | else() 7 | message(SEND_ERROR "IPO is not supported: ${output}") 8 | endif() 9 | endmacro() 10 | -------------------------------------------------------------------------------- /include/aigverse/truth_tables/truth_table.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 06.11.24. 3 | // 4 | 5 | #pragma once 6 | 7 | namespace pybind11 8 | { 9 | class module_; 10 | } 11 | 12 | namespace aigverse 13 | { 14 | 15 | // Registers truth table bindings 16 | void bind_truth_table(pybind11::module_& m); 17 | 18 | } // namespace aigverse 19 | -------------------------------------------------------------------------------- /include/aigverse/truth_tables/operations.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 12.12.24. 3 | // 4 | 5 | #pragma once 6 | 7 | namespace pybind11 8 | { 9 | class module_; 10 | } 11 | 12 | namespace aigverse 13 | { 14 | 15 | // Registers truth tables' free function bindings 16 | void bind_truth_table_operations(pybind11::module_& m); 17 | 18 | } // namespace aigverse 19 | -------------------------------------------------------------------------------- /libs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(${PROJECT_SOURCE_DIR}/cmake/CheckSubmodules.cmake) 2 | 3 | # Include mockturtle 4 | set(MOCKTURTLE_EXAMPLES OFF CACHE BOOL "" FORCE) 5 | set(MOCKTURTLE_EXPERIMENTS OFF CACHE BOOL "" FORCE) 6 | set(MOCKTURTLE_TEST OFF CACHE BOOL "" FORCE) 7 | check_if_present(mockturtle) 8 | add_subdirectory(mockturtle) 9 | add_library(aigverse::mockturtle ALIAS mockturtle) 10 | -------------------------------------------------------------------------------- /cmake/CheckSubmodules.cmake: -------------------------------------------------------------------------------- 1 | # check whether the submodule ``modulename`` is correctly cloned in the ``/libs`` directory. 2 | macro(check_if_present modulename) 3 | if(NOT EXISTS "${PROJECT_SOURCE_DIR}/libs/${modulename}/CMakeLists.txt") 4 | message( 5 | FATAL_ERROR 6 | "Submodule `${PROJECT_SOURCE_DIR}/libs/${modulename}` not cloned properly. Please run `git submodule update --init --recursive` from the main project directory to fix this issue." 7 | ) 8 | endif() 9 | endmacro() 10 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/balancing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "aigverse/types.hpp" 4 | 5 | namespace pybind11 6 | { 7 | class module_; 8 | } 9 | 10 | namespace aigverse 11 | { 12 | 13 | namespace detail 14 | { 15 | 16 | template 17 | void balancing(pybind11::module_& m); 18 | 19 | extern template void balancing(pybind11::module_& m); 20 | 21 | } // namespace detail 22 | 23 | void bind_balancing(pybind11::module_& m); 24 | 25 | } // namespace aigverse 26 | -------------------------------------------------------------------------------- /include/aigverse/io/write_dot.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 22.04.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void write_dot(pybind11::module_& m); 22 | 23 | extern template void write_dot(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_write_dot(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/rewriting.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 15.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void rewriting(pybind11::module_& m); 22 | 23 | extern template void rewriting(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_rewriting(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/io/write_aiger.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 05.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void write_aiger(pybind11::module_& m); 22 | 23 | extern template void write_aiger(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_write_aiger(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/refactoring.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 15.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void refactoring(pybind11::module_& m); 22 | 23 | extern template void refactoring(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_refactoring(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/simulation.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 06.11.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void simulation(pybind11::module_& m); 22 | 23 | extern template void simulation(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_simulation(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/io/write_verilog.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jingren on 20.04.25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void write_verilog(pybind11::module_& m); 22 | 23 | extern template void write_verilog(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_write_verilog(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/resubstitution.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 09.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void resubstitution(pybind11::module_& m); 22 | 23 | extern template void resubstitution(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_resubstitution(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/algorithms/equivalence_checking.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 09.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | namespace pybind11 10 | { 11 | class module_; 12 | } 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void equivalence_checking(pybind11::module_& m); 22 | 23 | extern template void equivalence_checking(pybind11::module_& m); 24 | 25 | } // namespace detail 26 | 27 | void bind_equivalence_checking(pybind11::module_& m); 28 | 29 | } // namespace aigverse 30 | -------------------------------------------------------------------------------- /include/aigverse/io/read_pla.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jingren on 06.05.25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include 10 | 11 | namespace pybind11 12 | { 13 | class module_; 14 | } 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template 23 | void read_pla(pybind11::module_& m, const std::string& network_name); 24 | 25 | extern template void read_pla(pybind11::module_& m, const std::string& network_name); 26 | 27 | } // namespace detail 28 | 29 | void bind_read_pla(pybind11::module_& m); 30 | 31 | } // namespace aigverse 32 | -------------------------------------------------------------------------------- /include/aigverse/io/read_verilog.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by Jingren on 21.04.25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include 10 | 11 | namespace pybind11 12 | { 13 | class module_; 14 | } 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template 23 | void read_verilog(pybind11::module_& m, const std::string& network_name); 24 | 25 | extern template void read_verilog(pybind11::module_& m, const std::string& network_name); 26 | 27 | } // namespace detail 28 | 29 | void bind_read_verilog(pybind11::module_& m); 30 | 31 | } // namespace aigverse 32 | -------------------------------------------------------------------------------- /test/inout/test_read_pla.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from pathlib import Path 5 | 6 | from aigverse import TruthTable, read_pla_into_aig, simulate 7 | 8 | dir_path = Path(os.path.realpath(__file__)).parent 9 | 10 | 11 | def test_read_pla_into_aig(): 12 | aig = read_pla_into_aig(str(dir_path / "../resources/test.pla")) 13 | assert aig.num_pis() == 3 14 | assert aig.num_pos() == 2 15 | assert aig.num_gates() == 5 16 | sim = simulate(aig) 17 | tt_0 = TruthTable(3) 18 | tt_0.create_from_binary_string("10110001") 19 | tt_1 = TruthTable(3) 20 | tt_1.create_from_binary_string("11100000") 21 | assert len(sim) == 2 22 | assert sim[0] == tt_0 23 | assert sim[1] == tt_1 24 | -------------------------------------------------------------------------------- /.github/SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Security updates are applied only to the most recent releases. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | To report vulnerabilities, you can privately report a potential security issue 10 | via the GitHub security vulnerabilities feature. This can be done here: 11 | 12 | https://github.com/marcelwa/aigverse/security/advisories 13 | 14 | Please do **not** open a public issue about a potential security vulnerability. 15 | 16 | You can find more details on the security vulnerability feature in the GitHub 17 | documentation here: 18 | 19 | https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing/privately-reporting-a-security-vulnerability 20 | -------------------------------------------------------------------------------- /include/aigverse/io/read_aiger.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 04.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include 10 | 11 | namespace pybind11 12 | { 13 | class module_; 14 | } 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template 23 | void read_aiger(pybind11::module_& m, const std::string& network_name); 24 | 25 | extern template void read_aiger(pybind11::module_& m, const std::string& network_name); 26 | extern template void read_aiger(pybind11::module_& m, const std::string& network_name); 27 | 28 | } // namespace detail 29 | 30 | void bind_read_aiger(pybind11::module_& m); 31 | 32 | } // namespace aigverse 33 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | 3 | Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. 4 | 5 | Fixes #(issue) 6 | 7 | ## Checklist: 8 | 9 | 12 | 13 | - [ ] The pull request only contains commits that are related to it. 14 | - [ ] I have added appropriate tests and documentation. 15 | - [ ] I have made sure that all CI jobs on GitHub pass. 16 | - [ ] The pull request introduces no new warnings and follows the project's style guidelines. 17 | -------------------------------------------------------------------------------- /src/aigverse/adapters/__init__.py: -------------------------------------------------------------------------------- 1 | """aigverse adapters for ML tasks.""" 2 | 3 | from __future__ import annotations 4 | 5 | try: 6 | import networkx as nx # noqa: F401 7 | import numpy as np # noqa: F401 8 | 9 | except ImportError: 10 | import warnings 11 | 12 | warnings.warn( 13 | "Key libraries could not be imported. The `AIG.to_networkx()` adapter will not be available. " 14 | "To enable this functionality, install aigverse's 'adapters' extra:\n\n" 15 | " uv pip install aigverse[adapters]\n", 16 | category=ImportWarning, 17 | stacklevel=2, 18 | ) 19 | 20 | else: 21 | from .. import Aig 22 | from .networkx import to_networkx 23 | 24 | Aig.to_networkx = to_networkx # type: ignore[method-assign] 25 | 26 | del to_networkx 27 | -------------------------------------------------------------------------------- /test/inout/test_write_verilog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import tempfile 4 | from pathlib import Path 5 | 6 | from aigverse import Aig, read_verilog_into_aig, write_verilog 7 | 8 | # Get the temporary directory as a Path object 9 | temp_dir = Path(tempfile.gettempdir()) 10 | 11 | 12 | def test_write_verilog() -> None: 13 | aig = Aig() 14 | x1 = aig.create_pi() 15 | x2 = aig.create_pi() 16 | 17 | a1 = aig.create_or(x1, x2) 18 | aig.create_po(a1) 19 | 20 | write_verilog(aig, str(temp_dir / "test.v")) 21 | 22 | aig2 = read_verilog_into_aig(str(temp_dir / "test.v")) 23 | 24 | assert aig2.size() == 4 25 | assert aig2.nodes() == list(range(4)) 26 | assert aig2.num_gates() == 1 27 | assert aig2.gates() == [3] 28 | assert aig2.pis() == [1, 2] 29 | -------------------------------------------------------------------------------- /cmake/PreventInSourceBuilds.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # This function will prevent in-source builds 3 | # 4 | function(aigverse_assure_out_of_source_builds) 5 | # make sure the user doesn't play dirty with symlinks 6 | get_filename_component(srcdir "${CMAKE_SOURCE_DIR}" REALPATH) 7 | get_filename_component(bindir "${CMAKE_BINARY_DIR}" REALPATH) 8 | 9 | # disallow in-source builds 10 | if("${srcdir}" STREQUAL "${bindir}") 11 | message("######################################################") 12 | message("Warning: in-source builds are disabled") 13 | message("Please create a separate build directory and run cmake from there") 14 | message("######################################################") 15 | message(FATAL_ERROR "Quitting configuration") 16 | endif() 17 | endfunction() 18 | 19 | aigverse_assure_out_of_source_builds() 20 | -------------------------------------------------------------------------------- /include/aigverse/networks/logic_networks.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 04.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include 10 | 11 | namespace pybind11 12 | { 13 | class module_; 14 | } 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template 23 | void bind_network(pybind11::module_& m, const std::string& network_name); 24 | 25 | extern template void bind_network(pybind11::module_& m, const std::string& network_name); 26 | // extern template void network(pybind11::module_& m, const std::string& network_name); 27 | // extern template void network(pybind11::module_& m, const std::string& network_name); 28 | 29 | } // namespace detail 30 | 31 | // Registers logic network bindings 32 | void bind_logic_networks(pybind11::module_& m); 33 | 34 | } // namespace aigverse 35 | -------------------------------------------------------------------------------- /test/inout/test_write_aiger.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import tempfile 4 | from pathlib import Path 5 | 6 | from aigverse import Aig, read_aiger_into_aig, write_aiger 7 | 8 | # Get the temporary directory as a Path object 9 | temp_dir = Path(tempfile.gettempdir()) 10 | 11 | 12 | def test_write_aiger() -> None: 13 | aig = Aig() 14 | x1 = aig.create_pi() 15 | x2 = aig.create_pi() 16 | x3 = aig.create_pi() 17 | 18 | a1 = aig.create_and(x1, x2) 19 | a2 = aig.create_and(x1, x3) 20 | a3 = aig.create_and(a1, a2) 21 | 22 | aig.create_po(a3) 23 | 24 | write_aiger(aig, str(temp_dir / "test.aig")) 25 | 26 | aig2 = read_aiger_into_aig(str(temp_dir / "test.aig")) 27 | 28 | assert aig2.size() == 7 29 | assert aig2.nodes() == list(range(7)) 30 | assert aig2.num_gates() == 3 31 | assert aig2.gates() == [4, 5, 6] 32 | assert aig2.pis() == [1, 2, 3] 33 | -------------------------------------------------------------------------------- /src/aigverse/io/write_dot.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/write_dot.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace aigverse 14 | { 15 | 16 | namespace detail 17 | { 18 | 19 | template 20 | void write_dot(pybind11::module_& m) 21 | { 22 | using namespace pybind11::literals; 23 | 24 | m.def( 25 | "write_dot", [](const Ntk& ntk, const std::filesystem::path& filename) 26 | { mockturtle::write_dot(ntk, filename.string()); }, "network"_a, "filename"_a); 27 | } 28 | 29 | // Explicit instantiation for AIG 30 | template void write_dot(pybind11::module_& m); 31 | 32 | } // namespace detail 33 | 34 | void bind_write_dot(pybind11::module_& m) 35 | { 36 | detail::write_dot(m); 37 | } 38 | 39 | } // namespace aigverse 40 | -------------------------------------------------------------------------------- /test/networks/test_fanout_aig.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import FanoutAig 4 | 5 | 6 | def test_fanout_aig() -> None: 7 | aig = FanoutAig() 8 | assert hasattr(aig, "fanouts") 9 | 10 | # Create primary inputs 11 | x1 = aig.create_pi() 12 | x2 = aig.create_pi() 13 | x3 = aig.create_pi() 14 | 15 | # Create AND gates 16 | n4 = aig.create_and(x1, x2) 17 | n5 = aig.create_and(n4, x3) 18 | n6 = aig.create_and(n4, n5) 19 | 20 | # Create primary outputs 21 | aig.create_po(n6) 22 | 23 | # Check the fanout of n4 24 | fanout_list = aig.fanouts(aig.get_node(n4)) 25 | assert len(fanout_list) == 2 26 | assert aig.get_node(n6) in fanout_list 27 | assert aig.get_node(n5) in fanout_list 28 | 29 | # fanouts() only collect internal fanouts (no POs) while fanout_size() will collect all 30 | assert len(aig.fanouts(aig.get_node(n6))) == 0 31 | assert aig.fanout_size(aig.get_node(n6)) == 1 32 | -------------------------------------------------------------------------------- /src/aigverse/io/write_aiger.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/write_aiger.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void write_aiger(pybind11::module_& m) 22 | { 23 | using namespace pybind11::literals; 24 | 25 | m.def( 26 | "write_aiger", [](const Ntk& ntk, const std::filesystem::path& filename) 27 | { mockturtle::write_aiger(ntk, filename.string()); }, "network"_a, "filename"_a); 28 | } 29 | 30 | // Explicit instantiation for AIG 31 | template void write_aiger(pybind11::module_& m); 32 | 33 | } // namespace detail 34 | 35 | void bind_write_aiger(pybind11::module_& m) 36 | { 37 | detail::write_aiger(m); 38 | } 39 | 40 | } // namespace aigverse 41 | -------------------------------------------------------------------------------- /src/aigverse/io/write_verilog.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/write_verilog.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | namespace aigverse 15 | { 16 | 17 | namespace detail 18 | { 19 | 20 | template 21 | void write_verilog(pybind11::module_& m) 22 | { 23 | using namespace pybind11::literals; 24 | 25 | m.def( 26 | "write_verilog", [](const Ntk& ntk, const std::filesystem::path& filename) 27 | { mockturtle::write_verilog(ntk, filename.string()); }, "network"_a, "filename"_a); 28 | } 29 | 30 | // Explicit instantiation for AIG 31 | template void write_verilog(pybind11::module_& m); 32 | 33 | } // namespace detail 34 | 35 | void bind_write_verilog(pybind11::module_& m) 36 | { 37 | detail::write_verilog(m); 38 | } 39 | 40 | } // namespace aigverse 41 | -------------------------------------------------------------------------------- /src/aigverse/pch.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Precompiled header for the pyaigverse module. 3 | * Aggregates commonly used heavy system and library headers to reduce 4 | * compile times for each translation unit. 5 | */ 6 | 7 | #pragma once 8 | 9 | // pybind11 core 10 | #include 11 | #include 12 | 13 | // fmt formatting 14 | #include 15 | 16 | // mockturtle frequently used minimal set 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | 23 | // kitty truth tables 24 | #include 25 | 26 | // STL basics 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | #include 39 | #include 40 | #include 41 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | # Enable cache if available 2 | function(aigverse_enable_cache) 3 | set(CACHE_OPTION 4 | "ccache" 5 | CACHE STRING "Compiler cache to be used") 6 | set(CACHE_OPTION_VALUES "ccache" "sccache") 7 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 8 | list(FIND CACHE_OPTION_VALUES ${CACHE_OPTION} CACHE_OPTION_INDEX) 9 | 10 | if(${CACHE_OPTION_INDEX} EQUAL -1) 11 | message( 12 | STATUS 13 | "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}" 14 | ) 15 | endif() 16 | 17 | find_program(CACHE_BINARY NAMES ${CACHE_OPTION_VALUES}) 18 | if(CACHE_BINARY) 19 | message(STATUS "${CACHE_BINARY} found and enabled") 20 | set(CMAKE_CXX_COMPILER_LAUNCHER 21 | ${CACHE_BINARY} 22 | CACHE FILEPATH "CXX compiler cache used") 23 | set(CMAKE_C_COMPILER_LAUNCHER 24 | ${CACHE_BINARY} 25 | CACHE FILEPATH "C compiler cache used") 26 | else() 27 | message( 28 | WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 29 | endif() 30 | endfunction() 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Marcel Walter 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/aigverse/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Automatically collect all .cpp sources in this directory (recursive) 2 | file(GLOB_RECURSE AIGVERSE_PY_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/*.cpp") 3 | 4 | pybind11_add_module(pyaigverse 5 | THIN_LTO 6 | ${AIGVERSE_PY_SOURCES}) 7 | 8 | # Provide namespaced alias for consistent consumer usage 9 | add_library(aigverse::pyaigverse ALIAS pyaigverse) 10 | 11 | # Link against the public header-only target and dependencies (namespaced) 12 | target_link_libraries(pyaigverse PRIVATE aigverse::aigverse aigverse::mockturtle) 13 | 14 | # Precompiled header to speed up heavy pybind11/fmt/mockturtle includes 15 | target_precompile_headers(pyaigverse PRIVATE "$") 16 | 17 | set_property(TARGET pyaigverse PROPERTY POSITION_INDEPENDENT_CODE ON) 18 | 19 | if (MSVC) 20 | target_compile_options(pyaigverse PRIVATE /utf-8) 21 | target_compile_definitions(pyaigverse PRIVATE UNICODE _UNICODE) 22 | endif () 23 | 24 | # Install directive for scikit-build-core 25 | install(TARGETS pyaigverse 26 | DESTINATION . 27 | COMPONENT aigverse_Python) 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yml: -------------------------------------------------------------------------------- 1 | name: ✨ Feature request 2 | description: Suggest an idea 3 | title: "✨ " 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: > 8 | **Thank you for wanting to suggest a feature for this project!** 9 | 10 | ⚠ 11 | Verify first that your idea is not [already requested on GitHub](https://github.com/marcelwa/aigverse/search?q=is%3Aissue&type=issues). 12 | 13 | - type: textarea 14 | attributes: 15 | label: What's the problem this feature will solve? 16 | description: >- 17 | What are you trying to do, that you are unable to achieve as it currently stands? 18 | placeholder: >- 19 | I'm trying to do X and I'm missing feature Y for this to be 20 | easily achievable. 21 | validations: 22 | required: true 23 | 24 | - type: textarea 25 | attributes: 26 | label: Describe the solution you'd like 27 | description: > 28 | Clear and concise description of what you want to happen. 29 | placeholder: >- 30 | When I do X, I want to achieve Y in a situation when Z. 31 | validations: 32 | required: true 33 | -------------------------------------------------------------------------------- /include/aigverse/types.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 06.11.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include <kitty/dynamic_truth_table.hpp> 8 | #include <mockturtle/networks/aig.hpp> 9 | #include <mockturtle/networks/sequential.hpp> 10 | #include <mockturtle/utils/index_list.hpp> 11 | #include <mockturtle/views/depth_view.hpp> 12 | #include <mockturtle/views/fanout_view.hpp> 13 | #include <mockturtle/views/names_view.hpp> 14 | 15 | namespace aigverse 16 | { 17 | 18 | /** 19 | * Alias for the AIG. 20 | */ 21 | using aig = mockturtle::aig_network; 22 | /** 23 | * Alias for the named AIG. 24 | */ 25 | using named_aig = mockturtle::names_view<aig>; 26 | /** 27 | * Alias for the depth AIG. 28 | */ 29 | using depth_aig = mockturtle::depth_view<aig>; 30 | /** 31 | * Alias for the fanout AIG. 32 | */ 33 | using fanouts_aig = mockturtle::fanout_view<aig>; 34 | /** 35 | * Alias for the sequential AIG. 36 | */ 37 | using sequential_aig = mockturtle::sequential<aig>; 38 | /** 39 | * Alias for the AIG index list. 40 | */ 41 | using aig_index_list = mockturtle::xag_index_list<true>; 42 | /** 43 | * Alias for the truth table. 44 | */ 45 | using truth_table = kitty::dynamic_truth_table; 46 | 47 | } // namespace aigverse 48 | -------------------------------------------------------------------------------- /test/inout/test_read_verilog.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from pathlib import Path 5 | 6 | from aigverse import AigSignal, read_verilog_into_aig 7 | 8 | dir_path = Path(os.path.realpath(__file__)).parent 9 | 10 | 11 | def test_read_verilog() -> None: 12 | aig = read_verilog_into_aig(str(dir_path / "../resources/test.v")) 13 | assert aig.size() == 6 14 | assert aig.nodes() == list(range(6)) 15 | assert aig.num_gates() == 2 16 | assert aig.gates() == [4, 5] 17 | assert aig.pis() == [1, 2, 3] 18 | 19 | 20 | def test_read_verilog_with_names() -> None: 21 | """Test that Verilog files preserve signal names in NamedAig.""" 22 | aig = read_verilog_into_aig(str(dir_path / "../resources/test.v")) 23 | 24 | # Test network name (module name) 25 | assert aig.get_network_name() == "top" 26 | 27 | # Test PI names (inputs: a, b, c) 28 | pi_nodes = aig.pis() 29 | assert len(pi_nodes) == 3 30 | 31 | # The inputs should be named a, b, c 32 | pi_names = [aig.get_name(AigSignal(pi, False)) for pi in pi_nodes if aig.has_name(AigSignal(pi, False))] 33 | assert set(pi_names) == {"a", "b", "c"} 34 | 35 | # Test PO names (output: y1) 36 | assert aig.num_pos() == 1 37 | assert aig.has_output_name(0) 38 | assert aig.get_output_name(0) == "y1" 39 | -------------------------------------------------------------------------------- /docs/_static/custom.css: -------------------------------------------------------------------------------- 1 | .acknowledgements { 2 | margin-top: 1rem; 3 | padding-bottom: 1rem; 4 | padding-top: 1rem; 5 | border-top: 1px solid var(--color-background-border); 6 | font-size: var(--font-size--small); 7 | color: var(--color-foreground-secondary); 8 | } 9 | 10 | .acknowledgements-logos { 11 | display: grid; 12 | grid-template-columns: repeat(auto-fit, minmax(100px, 1fr)); 13 | grid-gap: 1em; 14 | align-items: center; 15 | margin-top: 0.5rem; 16 | } 17 | .acknowledgement { 18 | display: flex; 19 | flex-direction: column; 20 | align-items: center; 21 | justify-content: center; 22 | } 23 | 24 | /* override the default background color for literal strings */ 25 | body:not([data-theme="light"]) .highlight .sa, 26 | .highlight .sb, 27 | .highlight .sc, 28 | .highlight .dl, 29 | .highlight .sd, 30 | .highlight .s2, 31 | .highlight .se, 32 | .highlight .sh, 33 | .highlight .si, 34 | .highlight .sx, 35 | .highlight .sr, 36 | .highlight .s1, 37 | .highlight .ss, 38 | .highlight .s1, 39 | .highlight .s { 40 | background-color: #00000001; 41 | } 42 | 43 | /* provide dark mode overrides for mystnb variables */ 44 | body:not([data-theme="light"]) { 45 | --mystnb-source-bg-color: #131416; 46 | --mystnb-stdout-bg-color: #1a1c1e; 47 | --mystnb-stderr-bg-color: #442222; 48 | --mystnb-traceback-bg-color: #202020; 49 | } 50 | 51 | body:not([data-theme="light"]) .highlight .gp { 52 | color: #c65d09; 53 | } 54 | -------------------------------------------------------------------------------- /test/algorithms/test_equivalence_checking.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from aigverse import Aig, equivalence_checking 6 | 7 | 8 | def test_empty_aigs() -> None: 9 | aig1 = Aig() 10 | aig2 = Aig() 11 | 12 | assert equivalence_checking(aig1, aig2) 13 | 14 | 15 | def test_simple_aigs() -> None: 16 | aig1 = Aig() 17 | aig2 = Aig() 18 | 19 | a1 = aig1.create_pi() 20 | b1 = aig1.create_pi() 21 | 22 | and1 = aig1.create_and(a1, b1) 23 | 24 | aig1.create_po(and1) 25 | aig1.create_po(b1) 26 | 27 | a2 = aig2.create_pi() 28 | b2 = aig2.create_pi() 29 | 30 | and2 = aig2.create_and(a2, b2) 31 | 32 | aig2.create_po(and2) 33 | aig2.create_po(b2) 34 | 35 | assert equivalence_checking(aig1, aig2) 36 | assert equivalence_checking(aig1, aig1.clone()) 37 | 38 | aig2.create_po(a1) 39 | 40 | with pytest.raises(RuntimeError): 41 | equivalence_checking(aig1, aig2) 42 | 43 | 44 | def test_aig_and_its_negated_copy() -> None: 45 | aig1 = Aig() 46 | 47 | a1 = aig1.create_pi() 48 | b1 = aig1.create_pi() 49 | c1 = aig1.create_pi() 50 | 51 | and1 = aig1.create_and(a1, b1) 52 | and2 = aig1.create_and(~a1, c1) 53 | and3 = aig1.create_and(and1, and2) 54 | 55 | aig2 = aig1.clone() 56 | 57 | aig1.create_po(and3) 58 | 59 | aig2.create_po(~and3) 60 | 61 | assert not equivalence_checking(aig1, aig2) 62 | -------------------------------------------------------------------------------- /test/inout/test_write_dot.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import tempfile 4 | from pathlib import Path 5 | 6 | from aigverse import Aig, write_dot 7 | 8 | # Get the temporary directory as a Path object 9 | temp_dir = Path(tempfile.gettempdir()) 10 | 11 | 12 | def test_write_dot_creates_file() -> None: 13 | aig = Aig() 14 | x1 = aig.create_pi() 15 | x2 = aig.create_pi() 16 | f = aig.create_and(x1, x2) 17 | aig.create_po(f) 18 | 19 | dot_path = temp_dir / "aig.dot" 20 | if dot_path.exists(): 21 | dot_path.unlink() 22 | 23 | write_dot(aig, str(dot_path)) 24 | assert dot_path.exists() 25 | 26 | content = dot_path.read_text() 27 | 28 | expected = """ 29 | digraph { 30 | rankdir=BT; 31 | 32 | 0 [label="0",shape=box,style=filled,fillcolor=snow2] 33 | 1 [label="1",shape=triangle,style=filled,fillcolor=snow2] 34 | 2 [label="2",shape=triangle,style=filled,fillcolor=snow2] 35 | 3 [label="3",shape=ellipse,style=filled,fillcolor=white] 36 | 37 | po0 [shape=invtriangle,style=filled,fillcolor=snow2] 38 | 39 | 1 -> 3 [style=solid] 40 | 2 -> 3 [style=solid] 41 | 3 -> po0 [style=solid] 42 | 43 | {rank = same; 0; 1; 2; } 44 | {rank = same; 3; } 45 | {rank = same; po0; } 46 | } 47 | """ 48 | 49 | def normalize(s: str) -> str: 50 | return "".join(s.split()) 51 | 52 | assert normalize(content) == normalize(expected) 53 | -------------------------------------------------------------------------------- /.github/support.md: -------------------------------------------------------------------------------- 1 | # Support & Consulting 2 | 3 | If you are stuck with a problem using `aigverse` or have questions, please get in touch at our [Issues](https://github.com/marcelwa/aigverse/issues) or 4 | [Discussions](https://github.com/marcelwa/aigverse/discussions). We'd love to help. 5 | 6 | You can save time by following this procedure when reporting a problem: 7 | 8 | - Try to solve the problem on your own first. 9 | - Search through past [Issues](https://github.com/marcelwa/aigverse/issues) and [Discussions](https://github.com/marcelwa/aigverse/discussions) to see if someone else already had the same problem. 10 | - Before filing a bug report, try to create a minimal working example (MWE) that reproduces the problem. It is much easier to identify the cause for the problem if a handful of lines suffice to show that something is not working. 11 | 12 | --- 13 | 14 | `aigverse` is a free, open-source library, and it will continue to be developed and maintained as such. 15 | The project relies on community contributions and the support of its users. 16 | 17 | If you or your organization require specific new features, dedicated support, training, or integration of `aigverse` 18 | into your projects, professional consulting services are available. This is a great way to get the features you need 19 | while also supporting the ongoing maintenance and development of the library. 20 | 21 | For inquiries about consulting services, please reach out to [@marcelwa](https://github.com/marcelwa/) directly. 22 | -------------------------------------------------------------------------------- /test/networks/test_depth_aig.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import DepthAig 4 | 5 | 6 | def test_depth_aig() -> None: 7 | aig = DepthAig() 8 | 9 | assert hasattr(aig, "num_levels") 10 | assert hasattr(aig, "level") 11 | assert hasattr(aig, "is_on_critical_path") 12 | assert hasattr(aig, "update_levels") 13 | assert hasattr(aig, "create_po") 14 | 15 | # Create primary inputs 16 | x1 = aig.create_pi() 17 | x2 = aig.create_pi() 18 | x3 = aig.create_pi() 19 | 20 | assert aig.num_levels() == 0 21 | 22 | # Create AND gates 23 | n4 = aig.create_and(~x1, x2) 24 | n5 = aig.create_and(x1, n4) 25 | n6 = aig.create_and(x3, n5) 26 | n7 = aig.create_and(n4, x2) 27 | n8 = aig.create_and(~n5, ~n7) 28 | n9 = aig.create_and(~n8, n4) 29 | 30 | # Create primary outputs 31 | aig.create_po(n6) 32 | aig.create_po(n9) 33 | 34 | # Check the depth of the AIG 35 | assert aig.num_levels() == 4 36 | 37 | assert aig.level(aig.get_node(x1)) == 0 38 | assert aig.level(aig.get_node(x2)) == 0 39 | assert aig.level(aig.get_node(x3)) == 0 40 | assert aig.level(aig.get_node(n4)) == 1 41 | assert aig.level(aig.get_node(n5)) == 2 42 | assert aig.level(aig.get_node(n6)) == 3 43 | assert aig.level(aig.get_node(n7)) == 2 44 | assert aig.level(aig.get_node(n8)) == 3 45 | assert aig.level(aig.get_node(n9)) == 4 46 | 47 | # Copy constructor 48 | aig2 = DepthAig(aig) 49 | 50 | assert aig2.num_levels() == 4 51 | -------------------------------------------------------------------------------- /src/aigverse/io/read_pla.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/read_pla.hpp" 6 | 7 | #include <fmt/format.h> 8 | #include <lorina/diagnostics.hpp> 9 | #include <mockturtle/io/pla_reader.hpp> 10 | #include <pybind11/pybind11.h> 11 | #include <pybind11/stl/filesystem.h> 12 | 13 | #include <filesystem> 14 | #include <stdexcept> 15 | #include <string> 16 | 17 | namespace aigverse 18 | { 19 | 20 | namespace detail 21 | { 22 | 23 | template <typename Ntk> 24 | void read_pla(pybind11::module_& m, const std::string& network_name) 25 | { 26 | using namespace pybind11::literals; 27 | 28 | m.def( 29 | fmt::format("read_pla_into_{}", network_name).c_str(), 30 | [](const std::filesystem::path& filename) 31 | { 32 | Ntk ntk{}; 33 | 34 | lorina::text_diagnostics consumer{}; 35 | lorina::diagnostic_engine diag{&consumer}; 36 | 37 | const auto read_pla_result = lorina::read_pla(filename.string(), mockturtle::pla_reader<Ntk>(ntk), &diag); 38 | 39 | if (read_pla_result != lorina::return_code::success) 40 | { 41 | throw std::runtime_error("Error reading PLA file"); 42 | } 43 | 44 | return ntk; 45 | }, 46 | "filename"_a); 47 | } 48 | 49 | // Explicit instantiation for AIG 50 | template void read_pla<aigverse::aig>(pybind11::module_& m, const std::string& network_name); 51 | 52 | } // namespace detail 53 | 54 | void bind_read_pla(pybind11::module_& m) 55 | { 56 | detail::read_pla<aigverse::aig>(m, "aig"); 57 | } 58 | 59 | } // namespace aigverse 60 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.yml: -------------------------------------------------------------------------------- 1 | name: 🐛 Bug report 2 | description: Something is not working correctly. 3 | title: "🐛 <title>" 4 | body: 5 | - type: markdown 6 | attributes: 7 | value: >- 8 | **Thank you for wanting to report a bug for this project!** 9 | 10 | ⚠ 11 | Verify first that your issue is not [already reported on GitHub](https://github.com/marcelwa/aigverse/search?q=is%3Aissue&type=issues). 12 | 13 | If you have general questions, please consider [starting a discussion](https://github.com/marcelwa/aigverse/discussions). 14 | - type: textarea 15 | attributes: 16 | label: Environment information 17 | description: >- 18 | Please provide information about your environment. For example, OS, C++ compiler, aigverse version etc. 19 | placeholder: | 20 | - OS: 21 | - C++ compiler: 22 | - aigverse version: 23 | - Additional environment information: 24 | validations: 25 | required: true 26 | - type: textarea 27 | attributes: 28 | label: Description 29 | description: A clear and concise description of what the bug is. 30 | validations: 31 | required: true 32 | - type: textarea 33 | attributes: 34 | label: Expected behavior 35 | description: A clear and concise description of what you expected to happen. 36 | - type: textarea 37 | attributes: 38 | label: How to Reproduce 39 | description: Please provide steps to reproduce this bug. 40 | placeholder: | 41 | 1. Get package from '...' 42 | 2. Then run '...' 43 | 3. An error occurs. 44 | validations: 45 | required: true 46 | -------------------------------------------------------------------------------- /src/aigverse/io/read_verilog.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/read_verilog.hpp" 6 | 7 | #include <fmt/format.h> 8 | #include <lorina/aiger.hpp> 9 | #include <lorina/diagnostics.hpp> 10 | #include <lorina/verilog.hpp> 11 | #include <mockturtle/io/verilog_reader.hpp> 12 | #include <pybind11/pybind11.h> 13 | #include <pybind11/stl/filesystem.h> 14 | 15 | #include <filesystem> 16 | #include <stdexcept> 17 | #include <string> 18 | 19 | namespace aigverse 20 | { 21 | 22 | namespace detail 23 | { 24 | 25 | template <typename Ntk> 26 | void read_verilog(pybind11::module_& m, const std::string& network_name) 27 | { 28 | using namespace pybind11::literals; 29 | 30 | m.def( 31 | fmt::format("read_verilog_into_{}", network_name).c_str(), 32 | [](const std::filesystem::path& filename) 33 | { 34 | Ntk ntk{}; 35 | 36 | lorina::text_diagnostics consumer{}; 37 | lorina::diagnostic_engine diag{&consumer}; 38 | 39 | const auto read_verilog_result = 40 | lorina::read_verilog(filename.string(), mockturtle::verilog_reader<Ntk>(ntk), &diag); 41 | 42 | if (read_verilog_result != lorina::return_code::success) 43 | { 44 | throw std::runtime_error("Error reading Verilog file"); 45 | } 46 | 47 | return ntk; 48 | }, 49 | "filename"_a); 50 | } 51 | 52 | // Explicit instantiation for named AIG 53 | template void read_verilog<aigverse::named_aig>(pybind11::module_& m, const std::string& network_name); 54 | 55 | } // namespace detail 56 | 57 | void bind_read_verilog(pybind11::module_& m) 58 | { 59 | detail::read_verilog<aigverse::named_aig>(m, "aig"); 60 | } 61 | 62 | } // namespace aigverse 63 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message(STATUS "Setting build type to 'Release' as none was specified.") 4 | set(CMAKE_BUILD_TYPE 5 | Release 6 | CACHE STRING "Choose the type of build." FORCE) 7 | # Set the possible values of build type for cmake-gui, ccmake 8 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 9 | "MinSizeRel" "RelWithDebInfo") 10 | endif() 11 | 12 | # Generate compile_commands.json to make it easier to work with clang based 13 | # tools 14 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 15 | 16 | # Enhance error reporting and compiler messages 17 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 18 | if(WIN32) 19 | # On Windows cuda nvcc uses cl and not clang 20 | add_compile_options($<$<COMPILE_LANGUAGE:C>:-fcolor-diagnostics> 21 | $<$<COMPILE_LANGUAGE:CXX>:-fcolor-diagnostics>) 22 | else() 23 | add_compile_options(-fcolor-diagnostics) 24 | endif() 25 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 26 | if(WIN32) 27 | # On Windows cuda nvcc uses cl and not gcc 28 | add_compile_options($<$<COMPILE_LANGUAGE:C>:-fdiagnostics-color=always> 29 | $<$<COMPILE_LANGUAGE:CXX>:-fdiagnostics-color=always>) 30 | else() 31 | add_compile_options(-fdiagnostics-color=always) 32 | endif() 33 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND MSVC_VERSION GREATER 1900) 34 | add_compile_options(/diagnostics:column) 35 | else() 36 | message( 37 | STATUS 38 | "No colored compiler diagnostic set for '${CMAKE_CXX_COMPILER_ID}' compiler." 39 | ) 40 | endif() 41 | 42 | # Use bigobj for MSVC due to many inline and template functions 43 | if(MSVC) 44 | add_compile_options(/bigobj) 45 | endif() 46 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: "https://docs.renovatebot.com/renovate-schema.json", 3 | extends: [ 4 | "config:recommended", 5 | ":gitSignOff" 6 | ], 7 | prHourlyLimit: 10, 8 | enabledManagers: [ 9 | "git-submodules", 10 | "github-actions", 11 | "pre-commit", 12 | "pep621", 13 | ], 14 | "git-submodules": { 15 | "enabled": true 16 | }, 17 | "pre-commit": { 18 | enabled: true 19 | }, 20 | lockFileMaintenance: { 21 | "enabled": true 22 | // "automerge": true, disabled due to endless update loops caused by setuptools_scm 23 | }, 24 | configMigration: true, 25 | schedule: [ 26 | "every weekend" 27 | ], 28 | packageRules: [ 29 | { 30 | matchManagers: [ 31 | "git-submodules" 32 | ], 33 | addLabels: [ 34 | "dependencies", 35 | "submodules" 36 | ], 37 | commitMessagePrefix: "⬆\uFE0F\uD83D\uDC68\u200D\uD83D\uDCBB", 38 | "groupName": "Submodules", 39 | }, 40 | { 41 | matchManagers: [ 42 | "github-actions" 43 | ], 44 | addLabels: [ 45 | "github_actions" 46 | ], 47 | commitMessagePrefix: "⬆\uFE0F\uD83D\uDC68\u200D\uD83D\uDCBB", 48 | "groupName": "GitHub Actions", 49 | }, 50 | { 51 | matchManagers: [ 52 | "pre-commit" 53 | ], 54 | addLabels: [ 55 | "pre-commit" 56 | ], 57 | commitMessagePrefix: "⬆\uFE0F\uD83E\uDE9D", 58 | "groupName": "Pre-Commit Hooks", 59 | }, 60 | { 61 | matchManagers: [ 62 | "pep621" 63 | ], 64 | addLabels: [ 65 | "dependencies", 66 | "python" 67 | ], 68 | commitMessagePrefix: "⬆\uFE0F\uD83D\uDC0D", 69 | "groupName": "Python Dependencies", 70 | }, 71 | { 72 | "description": "Automerge patch updates", 73 | "matchUpdateTypes": [ 74 | "patch" 75 | ], 76 | "automerge": true 77 | } 78 | ] 79 | } 80 | -------------------------------------------------------------------------------- /src/aigverse/__init__.py: -------------------------------------------------------------------------------- 1 | """aigverse: A Python Library for Logic Networks, Synthesis, and Optimization.""" 2 | 3 | from __future__ import annotations 4 | 5 | from ._version import version as __version__ 6 | from .pyaigverse import ( 7 | Aig, 8 | AigEdge, 9 | AigEdgeList, 10 | AigIndexList, 11 | AigNode, 12 | AigRegister, 13 | AigSignal, 14 | DepthAig, 15 | FanoutAig, 16 | NamedAig, 17 | SequentialAig, 18 | TruthTable, 19 | aig_cut_rewriting, 20 | aig_resubstitution, 21 | balancing, 22 | cofactor0, 23 | cofactor1, 24 | equivalence_checking, 25 | read_aiger_into_aig, 26 | read_aiger_into_sequential_aig, 27 | read_ascii_aiger_into_aig, 28 | read_ascii_aiger_into_sequential_aig, 29 | read_pla_into_aig, 30 | read_verilog_into_aig, 31 | simulate, 32 | simulate_nodes, 33 | sop_refactoring, 34 | ternary_majority, 35 | to_aig, 36 | to_edge_list, 37 | to_index_list, 38 | write_aiger, 39 | write_dot, 40 | write_verilog, 41 | ) 42 | 43 | __all__ = [ 44 | "Aig", 45 | "AigEdge", 46 | "AigEdgeList", 47 | "AigIndexList", 48 | "AigNode", 49 | "AigRegister", 50 | "AigSignal", 51 | "DepthAig", 52 | "FanoutAig", 53 | "NamedAig", 54 | "SequentialAig", 55 | "TruthTable", 56 | "__version__", 57 | "aig_cut_rewriting", 58 | "aig_resubstitution", 59 | "balancing", 60 | "cofactor0", 61 | "cofactor1", 62 | "equivalence_checking", 63 | "read_aiger_into_aig", 64 | "read_aiger_into_sequential_aig", 65 | "read_ascii_aiger_into_aig", 66 | "read_ascii_aiger_into_sequential_aig", 67 | "read_pla_into_aig", 68 | "read_verilog_into_aig", 69 | "simulate", 70 | "simulate_nodes", 71 | "sop_refactoring", 72 | "ternary_majority", 73 | "to_aig", 74 | "to_edge_list", 75 | "to_index_list", 76 | "write_aiger", 77 | "write_dot", 78 | "write_verilog", 79 | ] 80 | -------------------------------------------------------------------------------- /.readthedocs.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | 3 | formats: 4 | - pdf 5 | - htmlzip 6 | 7 | build: 8 | os: ubuntu-24.04 9 | tools: 10 | python: "3.12" 11 | apt_packages: 12 | - graphviz 13 | - inkscape 14 | jobs: 15 | post_checkout: 16 | # Skip docs build if the commit message contains "skip ci" 17 | - (git --no-pager log --pretty="tformat:%s -- %b" -1 | grep -viq "skip ci") || exit 183 18 | # Skip docs build if there are no changes related to docs 19 | - | 20 | if [ "$READTHEDOCS_VERSION_TYPE" = "external" ] && git diff --quiet origin/main -- docs/ .readthedocs.yaml pyproject.toml src/aigverse/ .github/contributing* .github/support*; 21 | then 22 | exit 183; 23 | fi 24 | # Unshallow the git clone and fetch tags to get proper version information 25 | - git fetch --unshallow --tags 26 | # Ensure submodules are checked out 27 | - git submodule update --init --recursive 28 | pre_build: 29 | # Set up uv 30 | - asdf plugin add uv 31 | - asdf install uv latest 32 | - asdf global uv latest 33 | build: 34 | html: 35 | - uv run --frozen --no-dev --group docs -m sphinx -T -b html -d docs/_build/doctrees -D language=en docs $READTHEDOCS_OUTPUT/html 36 | htmlzip: 37 | - uv run --frozen --no-dev --group docs -m sphinx -T -b dirhtml -d docs/_build/doctrees -D language=en docs docs/_build/dirhtml 38 | - mkdir -p $READTHEDOCS_OUTPUT/htmlzip 39 | - zip -r $READTHEDOCS_OUTPUT/htmlzip/html.zip docs/_build/dirhtml/* 40 | pdf: 41 | - uv run --frozen --no-dev --group docs -m sphinx -T -b latex -d docs/_build/doctrees -D language=en docs docs/_build/latex 42 | - cd docs/_build/latex && latexmk -pdf -f -dvi- -ps- -interaction=nonstopmode -jobname=$READTHEDOCS_PROJECT 43 | - mkdir -p $READTHEDOCS_OUTPUT/pdf 44 | - cp docs/_build/latex/$READTHEDOCS_PROJECT.pdf $READTHEDOCS_OUTPUT/pdf/$READTHEDOCS_PROJECT.pdf 45 | -------------------------------------------------------------------------------- /include/aigverse/adapters/index_list.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 02.05.25. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <fmt/ranges.h> 11 | #include <mockturtle/utils/index_list.hpp> 12 | 13 | #include <cstddef> 14 | #include <cstdint> 15 | #include <string> 16 | #include <tuple> 17 | #include <vector> 18 | 19 | namespace pybind11 20 | { 21 | class module_; 22 | } 23 | 24 | namespace aigverse 25 | { 26 | 27 | namespace detail 28 | { 29 | 30 | template <typename Ntk> 31 | void ntk_index_list(pybind11::module_& m, const std::string& network_name); 32 | 33 | extern template void ntk_index_list<aigverse::aig>(pybind11::module_& m, const std::string& network_name); 34 | 35 | } // namespace detail 36 | 37 | // Wrapper declaration (implemented in .cpp) 38 | void bind_to_index_list(pybind11::module_& m); 39 | 40 | } // namespace aigverse 41 | 42 | namespace fmt 43 | { 44 | 45 | // make index_list compatible with fmt::format 46 | template <> 47 | struct formatter<aigverse::aig_index_list> 48 | { 49 | template <typename ParseContext> 50 | constexpr auto parse(ParseContext& ctx) 51 | { 52 | return ctx.begin(); 53 | } 54 | 55 | template <typename FormatContext> 56 | auto format(const aigverse::aig_index_list& il, FormatContext& ctx) const 57 | { 58 | std::vector<std::tuple<uint32_t, uint32_t>> gates{}; 59 | gates.reserve(il.num_gates()); 60 | 61 | il.foreach_gate([&gates](const auto& lit0, const auto& lit1) { gates.emplace_back(lit0, lit1); }); 62 | 63 | std::vector<uint32_t> outputs{}; 64 | outputs.reserve(il.num_pos()); 65 | 66 | il.foreach_po([&outputs](const auto& lit) { outputs.push_back(lit); }); 67 | 68 | return format_to(ctx.out(), "#PIs: {}, #POs: {}, #Gates: {}, Gates: {}, POs: {}", il.num_pis(), il.num_pos(), 69 | il.num_gates(), gates, outputs); 70 | } 71 | }; 72 | 73 | } // namespace fmt 74 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Large parts of this build system are based on Jason Turner's C++ Starter 2 | # Project (https://github.com/lefticus/cpp_starter_project) and CMake Template 3 | # (https://github.com/cpp-best-practices/cmake_template) 4 | 5 | cmake_minimum_required(VERSION 3.23...4.1.0) 6 | 7 | # Only set the CMAKE_CXX_STANDARD if it is not set by someone else 8 | if(NOT DEFINED CMAKE_CXX_STANDARD) 9 | # Set C++ standard; at least C++17 is required 10 | set(CMAKE_CXX_STANDARD 17) 11 | endif() 12 | 13 | # strongly encouraged to enable this globally to avoid conflicts between 14 | # -Wpedantic being enabled and -std=c++20 and -std=gnu++20 for example when 15 | # compiling with PCH enabled 16 | set(CMAKE_CXX_EXTENSIONS OFF) 17 | 18 | # Set the project name and description 19 | project( 20 | aigverse 21 | DESCRIPTION 22 | "A Python library for working with logic networks, synthesis, and optimization." 23 | HOMEPAGE_URL "https://github.com/marcelwa/aigverse" 24 | LANGUAGES CXX C) 25 | 26 | include(cmake/PreventInSourceBuilds.cmake) 27 | include(cmake/ProjectOptions.cmake) 28 | include(cmake/ExternalDependencies.cmake) 29 | 30 | aigverse_setup_options() 31 | aigverse_global_options() 32 | aigverse_local_options() 33 | 34 | # don't know if this should be set globally from here or not... 35 | set(CMAKE_CXX_VISIBILITY_PRESET hidden) 36 | 37 | set(GIT_SHA 38 | "Unknown" 39 | CACHE STRING "SHA this build was generated from") 40 | string(SUBSTRING "${GIT_SHA}" 0 8 GIT_SHORT_SHA) 41 | 42 | target_compile_features(aigverse_options INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) 43 | 44 | # Alias for the options target 45 | add_library(aigverse::aigverse_options ALIAS aigverse_options) 46 | # Alias for the warnings target 47 | add_library(aigverse::aigverse_warnings ALIAS aigverse_warnings) 48 | 49 | # Include libraries 50 | add_subdirectory(libs) 51 | 52 | # Include headers 53 | add_subdirectory(include) 54 | 55 | # Include source files 56 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/aigverse) 57 | 58 | if(CMAKE_SKIP_INSTALL_RULES) 59 | return() 60 | endif() 61 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/equivalence_checking.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/equivalence_checking.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <mockturtle/algorithms/equivalence_checking.hpp> 10 | #include <mockturtle/algorithms/miter.hpp> 11 | #include <pybind11/pybind11.h> 12 | #include <pybind11/stl.h> 13 | 14 | #include <cstdint> 15 | #include <optional> 16 | #include <stdexcept> 17 | 18 | namespace aigverse 19 | { 20 | 21 | namespace detail 22 | { 23 | 24 | template <typename Spec, typename Impl> 25 | void equivalence_checking(pybind11::module_& m) 26 | { 27 | using namespace pybind11::literals; 28 | 29 | m.def( 30 | "equivalence_checking", 31 | [](const Spec& spec, const Impl& impl, const uint32_t conflict_limit = 0, 32 | const bool functional_reduction = true, const bool verbose = false) -> std::optional<bool> 33 | { 34 | const auto miter = mockturtle::miter<mockturtle::aig_network, Spec, Impl>(spec, impl); 35 | 36 | if (!miter.has_value()) 37 | { 38 | throw std::runtime_error("miter construction failed due to differing numbers of PIs or POs"); 39 | } 40 | 41 | mockturtle::equivalence_checking_params params{}; 42 | params.conflict_limit = conflict_limit; 43 | params.functional_reduction = functional_reduction; 44 | params.verbose = verbose; 45 | 46 | return mockturtle::equivalence_checking(miter.value(), params); 47 | }, 48 | "spec"_a, "impl"_a, "conflict_limit"_a = 0, "functional_reduction"_a = true, "verbose"_a = false, 49 | pybind11::call_guard<pybind11::gil_scoped_release>()); 50 | } 51 | 52 | // Explicit instantiation for AIG 53 | template void equivalence_checking<aigverse::aig, aigverse::aig>(pybind11::module_& m); 54 | 55 | } // namespace detail 56 | 57 | void bind_equivalence_checking(pybind11::module_& m) 58 | { 59 | detail::equivalence_checking<aigverse::aig, aigverse::aig>(m); 60 | } 61 | 62 | } // namespace aigverse 63 | -------------------------------------------------------------------------------- /src/aigverse/aigverse.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 04.09.24. 3 | // 4 | 5 | #include "aigverse/adapters/edge_list.hpp" 6 | #include "aigverse/adapters/index_list.hpp" 7 | #include "aigverse/algorithms/balancing.hpp" 8 | #include "aigverse/algorithms/equivalence_checking.hpp" 9 | #include "aigverse/algorithms/refactoring.hpp" 10 | #include "aigverse/algorithms/resubstitution.hpp" 11 | #include "aigverse/algorithms/rewriting.hpp" 12 | #include "aigverse/algorithms/simulation.hpp" 13 | #include "aigverse/io/read_aiger.hpp" 14 | #include "aigverse/io/read_pla.hpp" 15 | #include "aigverse/io/read_verilog.hpp" 16 | #include "aigverse/io/write_aiger.hpp" 17 | #include "aigverse/io/write_dot.hpp" 18 | #include "aigverse/io/write_verilog.hpp" 19 | #include "aigverse/networks/logic_networks.hpp" 20 | #include "aigverse/truth_tables/operations.hpp" 21 | #include "aigverse/truth_tables/truth_table.hpp" 22 | 23 | #include <pybind11/pybind11.h> 24 | 25 | #define PYBIND11_DETAILED_ERROR_MESSAGES 26 | 27 | PYBIND11_MODULE(pyaigverse, m, pybind11::mod_gil_not_used()) 28 | { 29 | // docstring 30 | m.doc() = "A Python library for working with logic networks, synthesis, and optimization."; 31 | 32 | /** 33 | * Networks 34 | */ 35 | aigverse::bind_logic_networks(m); 36 | 37 | /** 38 | * Truth tables. 39 | */ 40 | aigverse::bind_truth_table(m); 41 | aigverse::bind_truth_table_operations(m); 42 | 43 | /** 44 | * Algorithms 45 | */ 46 | aigverse::bind_equivalence_checking(m); 47 | aigverse::bind_refactoring(m); 48 | aigverse::bind_resubstitution(m); 49 | aigverse::bind_rewriting(m); 50 | aigverse::bind_balancing(m); 51 | aigverse::bind_simulation(m); 52 | 53 | /** 54 | * I/O 55 | */ 56 | aigverse::bind_read_aiger(m); 57 | aigverse::bind_write_aiger(m); 58 | aigverse::bind_read_pla(m); 59 | aigverse::bind_read_verilog(m); 60 | aigverse::bind_write_verilog(m); 61 | aigverse::bind_write_dot(m); 62 | 63 | /** 64 | * Adapters 65 | */ 66 | aigverse::bind_to_edge_list(m); 67 | aigverse::bind_to_index_list(m); 68 | } 69 | -------------------------------------------------------------------------------- /src/aigverse/truth_tables/operations.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/truth_tables/operations.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <kitty/operations.hpp> 10 | #include <pybind11/pybind11.h> 11 | 12 | #include <cstdint> 13 | #include <stdexcept> 14 | 15 | namespace aigverse 16 | { 17 | 18 | namespace detail 19 | { 20 | 21 | static void bind_truth_table_operations(pybind11::module_& m) 22 | { 23 | using namespace pybind11::literals; 24 | 25 | m.def( 26 | "ternary_majority", 27 | [](const aigverse::truth_table& a, const aigverse::truth_table& b, const aigverse::truth_table& c) 28 | { return kitty::ternary_majority(a, b, c); }, "a"_a, "b"_a, "c"_a, 29 | "Compute the ternary majority of three truth tables.", pybind11::call_guard<pybind11::gil_scoped_release>()); 30 | 31 | m.def( 32 | "cofactor0", 33 | [](const aigverse::truth_table& tt, const uint8_t var_index) 34 | { 35 | if (var_index >= tt.num_vars()) 36 | { 37 | throw std::invalid_argument("var_index out of range"); 38 | } 39 | return kitty::cofactor0(tt, var_index); 40 | }, 41 | "tt"_a, "var_index"_a, 42 | "Returns the cofactor with respect to 0 of the variable at index `var_index` in the given truth table.", 43 | pybind11::call_guard<pybind11::gil_scoped_release>()); 44 | 45 | m.def( 46 | "cofactor1", 47 | [](const aigverse::truth_table& tt, const uint8_t var_index) 48 | { 49 | if (var_index >= tt.num_vars()) 50 | { 51 | throw std::invalid_argument("var_index out of range"); 52 | } 53 | return kitty::cofactor1(tt, var_index); 54 | }, 55 | "tt"_a, "var_index"_a, 56 | "Returns the cofactor with respect to 1 of the variable at index `var_index` in the given truth table.", 57 | pybind11::call_guard<pybind11::gil_scoped_release>()); 58 | } 59 | 60 | } // namespace detail 61 | 62 | void bind_truth_table_operations(pybind11::module_& m) 63 | { 64 | detail::bind_truth_table_operations(m); 65 | } 66 | 67 | } // namespace aigverse 68 | -------------------------------------------------------------------------------- /include/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(aigverse INTERFACE) 2 | add_library(aigverse::aigverse ALIAS aigverse) 3 | 4 | # Collect all project headers 5 | file(GLOB_RECURSE AIGVERSE_PUBLIC_HEADERS CONFIGURE_DEPENDS 6 | "${PROJECT_SOURCE_DIR}/include/aigverse/*.hpp") 7 | 8 | # Expose include directories (build + install interfaces) 9 | # Avoid plain absolute paths to support relocatable packages 10 | # Public headers live in source include dir 11 | # install tree flattens to include/ 12 | target_include_directories(aigverse 13 | INTERFACE 14 | $<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include> 15 | $<BUILD_INTERFACE:${AIGVERSE_GENERATED_DIR}> 16 | $<INSTALL_INTERFACE:include> 17 | ) 18 | 19 | # Register header file set for IDE integration and installation metadata 20 | # Multiple BASE_DIRS so CMake knows how to layout the installed tree 21 | # 22 | # Using a FILE_SET ensures proper exposure in CMake package exports later 23 | # if export() logic is added. 24 | # 25 | # (GLOB with CONFIGURE_DEPENDS keeps IDE view in sync when adding headers.) 26 | target_sources(aigverse 27 | INTERFACE 28 | FILE_SET HEADERS 29 | BASE_DIRS 30 | ${PROJECT_SOURCE_DIR}/include 31 | FILES 32 | ${AIGVERSE_PUBLIC_HEADERS} 33 | ) 34 | 35 | # Enforce project-wide C++ standard feature requirement (redundant but explicit) 36 | target_compile_features(aigverse INTERFACE cxx_std_${CMAKE_CXX_STANDARD}) 37 | 38 | # Propagate options and warnings 39 | target_link_libraries(aigverse INTERFACE 40 | $<BUILD_INTERFACE:aigverse_options> 41 | $<BUILD_INTERFACE:aigverse_warnings> 42 | ) 43 | 44 | # Visibility / conformity flags for consumers. 45 | # * GCC/Clang: hide symbols by default 46 | # * MSVC: tighten language conformance & proper __cplusplus, modern preprocessor 47 | # Symbol export on MSVC still requires dllexport/dllimport if building shared libs. 48 | target_compile_options(aigverse 49 | INTERFACE 50 | $<BUILD_INTERFACE:$<$<COMPILE_LANG_AND_ID:CXX,Clang,AppleClang,GNU>:-fvisibility=hidden -fvisibility-inlines-hidden>> 51 | $<BUILD_INTERFACE:$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/permissive- /Zc:__cplusplus /Zc:inline /Zc:preprocessor /Zc:throwingNew /EHsc>> 52 | ) 53 | 54 | # Install headers via file set (skipped if global install disabled) 55 | if(NOT CMAKE_SKIP_INSTALL_RULES) 56 | install(TARGETS aigverse FILE_SET HEADERS) 57 | endif() 58 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/resubstitution.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/resubstitution.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <mockturtle/algorithms/aig_resub.hpp> 10 | #include <mockturtle/algorithms/cleanup.hpp> 11 | #include <mockturtle/algorithms/resubstitution.hpp> 12 | #include <pybind11/pybind11.h> 13 | 14 | #include <cstdint> 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template <typename Ntk> 23 | void resubstitution(pybind11::module_& m) 24 | { 25 | using namespace pybind11::literals; 26 | 27 | m.def( 28 | "aig_resubstitution", 29 | [](Ntk& ntk, const uint32_t max_pis = 8, const uint32_t max_divisors = 150, const uint32_t max_inserts = 2, 30 | const uint32_t skip_fanout_limit_for_roots = 1000, const uint32_t skip_fanout_limit_for_divisors = 100, 31 | const bool verbose = false, const bool use_dont_cares = false, const uint32_t window_size = 12, 32 | const bool preserve_depth = false) -> void 33 | { 34 | mockturtle::resubstitution_params params{}; 35 | params.max_pis = max_pis; 36 | params.max_divisors = max_divisors; 37 | params.max_inserts = max_inserts; 38 | params.skip_fanout_limit_for_roots = skip_fanout_limit_for_roots; 39 | params.skip_fanout_limit_for_divisors = skip_fanout_limit_for_divisors; 40 | params.verbose = verbose; 41 | params.use_dont_cares = use_dont_cares; 42 | params.window_size = window_size; 43 | params.preserve_depth = preserve_depth; 44 | 45 | mockturtle::aig_resubstitution(ntk, params); 46 | 47 | ntk = mockturtle::cleanup_dangling(ntk); 48 | }, 49 | "ntk"_a, "max_pis"_a = 8, "max_divisors"_a = 150, "max_inserts"_a = 2, "skip_fanout_limit_for_roots"_a = 1000, 50 | "skip_fanout_limit_for_divisors"_a = 100, "verbose"_a = false, "use_dont_cares"_a = false, "window_size"_a = 12, 51 | "preserve_depth"_a = false, pybind11::call_guard<pybind11::gil_scoped_release>()); 52 | } 53 | 54 | // Explicit instantiation for AIG 55 | template void resubstitution<aigverse::aig>(pybind11::module_& m); 56 | 57 | } // namespace detail 58 | 59 | void bind_resubstitution(pybind11::module_& m) 60 | { 61 | detail::resubstitution<aigverse::aig>(m); 62 | } 63 | 64 | } // namespace aigverse 65 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/refactoring.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/refactoring.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <mockturtle/algorithms/cleanup.hpp> 11 | #include <mockturtle/algorithms/node_resynthesis/sop_factoring.hpp> 12 | #include <mockturtle/algorithms/refactoring.hpp> 13 | #include <pybind11/pybind11.h> 14 | 15 | #include <cstdint> 16 | #include <stdexcept> 17 | 18 | namespace aigverse 19 | { 20 | 21 | namespace detail 22 | { 23 | 24 | template <typename Ntk> 25 | void refactoring(pybind11::module_& m) 26 | { 27 | using namespace pybind11::literals; 28 | 29 | m.def( 30 | "sop_refactoring", 31 | [](Ntk& ntk, const uint32_t max_pis = 6, const bool allow_zero_gain = false, 32 | const bool use_reconvergence_cut = false, const bool use_dont_cares = false, 33 | const bool verbose = false) -> void 34 | { 35 | try 36 | { 37 | mockturtle::refactoring_params params{}; 38 | params.max_pis = max_pis; 39 | params.allow_zero_gain = allow_zero_gain; 40 | params.use_reconvergence_cut = use_reconvergence_cut; 41 | params.use_dont_cares = use_dont_cares; 42 | params.verbose = verbose; 43 | 44 | mockturtle::sop_factoring<Ntk> sop_resyn_engine{}; 45 | 46 | mockturtle::refactoring(ntk, sop_resyn_engine, params); 47 | 48 | // create a temporary network with dangling nodes cleaned up 49 | auto cleaned = mockturtle::cleanup_dangling(ntk); 50 | 51 | ntk = std::move(cleaned); 52 | } 53 | catch (const std::exception& e) 54 | { 55 | throw std::runtime_error(fmt::format("Error in mockturtle::sop_refactoring: {}", e.what())); 56 | } 57 | catch (...) 58 | { 59 | throw std::runtime_error("Unknown error in mockturtle::sop_refactoring"); 60 | } 61 | }, 62 | "ntk"_a, "max_pis"_a = 6, "allow_zero_gain"_a = false, "use_reconvergence_cut"_a = false, 63 | "use_dont_cares"_a = false, "verbose"_a = false, pybind11::call_guard<pybind11::gil_scoped_release>()); 64 | } 65 | 66 | // Explicit instantiation for AIG 67 | template void refactoring<aigverse::aig>(pybind11::module_& m); 68 | 69 | } // namespace detail 70 | 71 | void bind_refactoring(pybind11::module_& m) 72 | { 73 | detail::refactoring<aigverse::aig>(m); 74 | } 75 | 76 | } // namespace aigverse 77 | -------------------------------------------------------------------------------- /src/aigverse/io/read_aiger.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/io/read_aiger.hpp" 6 | 7 | #include <fmt/format.h> 8 | #include <lorina/aiger.hpp> 9 | #include <lorina/diagnostics.hpp> 10 | #include <mockturtle/io/aiger_reader.hpp> 11 | #include <pybind11/pybind11.h> 12 | #include <pybind11/stl/filesystem.h> 13 | 14 | #include <filesystem> 15 | #include <stdexcept> 16 | #include <string> 17 | 18 | namespace aigverse 19 | { 20 | 21 | namespace detail 22 | { 23 | 24 | template <typename Ntk> 25 | void read_aiger(pybind11::module_& m, const std::string& network_name) 26 | { 27 | using namespace pybind11::literals; 28 | 29 | m.def( 30 | fmt::format("read_aiger_into_{}", network_name).c_str(), 31 | [](const std::filesystem::path& filename) 32 | { 33 | Ntk ntk{}; 34 | 35 | lorina::text_diagnostics consumer{}; 36 | lorina::diagnostic_engine diag{&consumer}; 37 | 38 | const auto read_aiger_result = 39 | lorina::read_aiger(filename.string(), mockturtle::aiger_reader<Ntk>(ntk), &diag); 40 | 41 | if (read_aiger_result != lorina::return_code::success) 42 | { 43 | throw std::runtime_error("Error reading AIGER file"); 44 | } 45 | 46 | return ntk; 47 | }, 48 | "filename"_a); 49 | 50 | m.def( 51 | fmt::format("read_ascii_aiger_into_{}", network_name).c_str(), 52 | [](const std::filesystem::path& filename) 53 | { 54 | Ntk ntk{}; 55 | 56 | lorina::text_diagnostics consumer{}; 57 | lorina::diagnostic_engine diag{&consumer}; 58 | 59 | const auto read_ascii_aiger_result = 60 | lorina::read_ascii_aiger(filename.string(), mockturtle::aiger_reader<Ntk>(ntk), &diag); 61 | 62 | if (read_ascii_aiger_result != lorina::return_code::success) 63 | { 64 | throw std::runtime_error("Error reading ASCII AIGER file"); 65 | } 66 | 67 | return ntk; 68 | }, 69 | "filename"_a); 70 | } 71 | 72 | // Explicit instantiations for named AIG and sequential AIG 73 | template void read_aiger<aigverse::named_aig>(pybind11::module_& m, const std::string& network_name); 74 | template void read_aiger<aigverse::sequential_aig>(pybind11::module_& m, const std::string& network_name); 75 | 76 | } // namespace detail 77 | 78 | void bind_read_aiger(pybind11::module_& m) 79 | { 80 | detail::read_aiger<aigverse::named_aig>(m, "aig"); 81 | detail::read_aiger<aigverse::sequential_aig>(m, "sequential_aig"); 82 | } 83 | 84 | } // namespace aigverse 85 | -------------------------------------------------------------------------------- /cmake/ExternalDependencies.cmake: -------------------------------------------------------------------------------- 1 | # Declare all external dependencies and make sure that they are available. 2 | 3 | include(FetchContent) 4 | include(CMakeDependentOption) 5 | 6 | cmake_dependent_option( 7 | AIGVERSE_VENDOR_PYBIND11 8 | "Fetch pybind11 automatically if not found" 9 | ON 10 | "NOT SKBUILD" 11 | OFF 12 | ) 13 | set(AIGVERSE_PYBIND11_VERSION 2.13.6 CACHE STRING "Desired pybind11 version") 14 | 15 | if(NOT SKBUILD) 16 | # Need Development.Module so that python_add_library/python3_add_library are defined 17 | find_package(Python REQUIRED COMPONENTS Interpreter Development.Module) 18 | # Try to discover a pybind11 installation provided by Python package (pip) 19 | execute_process( 20 | COMMAND "${Python_EXECUTABLE}" -m pybind11 --cmakedir 21 | OUTPUT_STRIP_TRAILING_WHITESPACE 22 | OUTPUT_VARIABLE pybind11_DIR 23 | RESULT_VARIABLE pybind11_exec_res 24 | ERROR_VARIABLE pybind11_exec_err 25 | ) 26 | if(pybind11_exec_res EQUAL 0 AND pybind11_DIR) 27 | list(PREPEND CMAKE_PREFIX_PATH "${pybind11_DIR}") 28 | else() 29 | message(DEBUG "pybind11 --cmakedir query failed (code=${pybind11_exec_res}). stderr: ${pybind11_exec_err}") 30 | endif() 31 | endif() 32 | 33 | if(Python_EXECUTABLE) 34 | message(STATUS "Python executable: ${Python_EXECUTABLE}") 35 | else() 36 | message(FATAL_ERROR "Python executable not found") 37 | endif() 38 | 39 | # First attempt to find an existing pybind11 (quiet; we'll vendor if missing) 40 | find_package(pybind11 ${AIGVERSE_PYBIND11_VERSION} CONFIG QUIET) 41 | 42 | if(NOT pybind11_FOUND) 43 | if(AIGVERSE_VENDOR_PYBIND11) 44 | message(STATUS "pybind11 not found; fetching v${AIGVERSE_PYBIND11_VERSION} via FetchContent") 45 | FetchContent_Declare( 46 | pybind11 47 | GIT_REPOSITORY https://github.com/pybind/pybind11.git 48 | GIT_TAG v${AIGVERSE_PYBIND11_VERSION} 49 | GIT_SHALLOW TRUE 50 | FIND_PACKAGE_ARGS ${AIGVERSE_PYBIND11_VERSION} CONFIG 51 | ) 52 | # Prevent polluting parent projects if this is used as a subtree 53 | set(FETCHCONTENT_QUIET OFF) 54 | FetchContent_MakeAvailable(pybind11) 55 | # After vendoring, targets like pybind11::pybind11 should exist 56 | if(TARGET pybind11::pybind11) 57 | message(STATUS "Vendored pybind11 v${AIGVERSE_PYBIND11_VERSION} ready") 58 | set(pybind11_FOUND TRUE) 59 | endif() 60 | else() 61 | message(FATAL_ERROR "pybind11 (>=${AIGVERSE_PYBIND11_VERSION}) not found and AIGVERSE_VENDOR_PYBIND11=OFF. Set pybind11_DIR or enable vendorization.") 62 | endif() 63 | endif() 64 | 65 | if(NOT pybind11_FOUND) 66 | message(FATAL_ERROR "pybind11 still not available after attempted fetch.") 67 | endif() 68 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveAssignments: Consecutive 4 | AlignConsecutiveDeclarations: Consecutive 5 | AlignConsecutiveBitFields: Consecutive 6 | AlignConsecutiveMacros: Consecutive 7 | AlignEscapedNewlines: Left 8 | AlignOperands: Align 9 | AlignTrailingComments: true 10 | AllowAllArgumentsOnNextLine: false 11 | AllowAllParametersOfDeclarationOnNextLine: false 12 | AllowAllConstructorInitializersOnNextLine: false 13 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 14 | AllowShortBlocksOnASingleLine: Empty 15 | AllowShortCaseLabelsOnASingleLine: true 16 | AllowShortFunctionsOnASingleLine: Empty 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLambdasOnASingleLine: All 19 | AllowShortLoopsOnASingleLine: true 20 | AlwaysBreakTemplateDeclarations: Yes 21 | BreakBeforeBraces: Custom 22 | BraceWrapping: 23 | AfterCaseLabel: true 24 | AfterClass: true 25 | AfterControlStatement: Always 26 | AfterEnum: true 27 | AfterFunction: true 28 | AfterNamespace: true 29 | AfterObjCDeclaration: true 30 | AfterStruct: true 31 | AfterUnion: true 32 | AfterExternBlock: true 33 | BeforeCatch: true 34 | BeforeElse: true 35 | BeforeLambdaBody: true 36 | BeforeWhile: false 37 | IndentBraces: false 38 | SplitEmptyFunction: false 39 | SplitEmptyRecord: false 40 | SplitEmptyNamespace: false 41 | BreakBeforeTernaryOperators: false 42 | PointerAlignment: Left 43 | BreakConstructorInitializers: AfterColon 44 | BreakInheritanceList: BeforeColon 45 | ColumnLimit: 120 46 | ConstructorInitializerIndentWidth: 8 47 | Cpp11BracedListStyle: true 48 | DisableFormat: false 49 | FixNamespaceComments: true 50 | IncludeBlocks: Regroup 51 | IncludeCategories: 52 | - Regex: '"aigverse\/[a-zA-Z0-9_\/]+.+"$' 53 | Priority: 1 54 | - Regex: '"[a-zA-Z0-9_\.]+.+"$' 55 | Priority: 2 56 | - Regex: '<aigverse\/[a-zA-Z0-9_]+.+>$' 57 | Priority: 3 58 | - Regex: '<(alice|lorina|mockturtle|kitty|bill|nlohmann|fmt|phmap|btree|pybind11)(\/[a-zA-Z0-9_])*.+>$' 59 | Priority: 4 60 | - Regex: "<[a-zA-Z0-9_]+>$" 61 | Priority: 5 62 | IndentCaseLabels: true 63 | IndentWidth: 4 64 | Language: Cpp 65 | ReflowComments: true 66 | SortIncludes: CaseSensitive 67 | SortUsingDeclarations: true 68 | SpaceAfterLogicalNot: false 69 | SpaceAfterTemplateKeyword: true 70 | SpaceBeforeAssignmentOperators: true 71 | SpaceBeforeCpp11BracedList: false 72 | SpaceBeforeCtorInitializerColon: true 73 | SpaceBeforeInheritanceColon: true 74 | SpaceBeforeParens: ControlStatements 75 | SpaceBeforeRangeBasedForLoopColon: true 76 | SpaceInEmptyParentheses: false 77 | SpacesBeforeTrailingComments: 2 78 | SpacesInAngles: false 79 | SpacesInCStyleCastParentheses: false 80 | SpacesInContainerLiterals: false 81 | SpacesInParentheses: false 82 | SpacesInSquareBrackets: false 83 | Standard: c++17 84 | UseTab: Never 85 | LineEnding: LF 86 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/rewriting.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/rewriting.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <mockturtle/algorithms/cut_rewriting.hpp> 10 | #include <mockturtle/algorithms/node_resynthesis/xag_npn.hpp> 11 | #include <pybind11/pybind11.h> 12 | 13 | #include <cstdint> 14 | #include <optional> 15 | 16 | namespace aigverse 17 | { 18 | 19 | namespace detail 20 | { 21 | 22 | template <typename Ntk> 23 | void rewriting(pybind11::module_& m) 24 | { 25 | using namespace pybind11::literals; 26 | 27 | m.def( 28 | "aig_cut_rewriting", 29 | [](Ntk& ntk, const uint32_t cut_size = 4, const uint32_t cut_limit = 8, const bool minimize_truth_table = true, 30 | const bool allow_zero_gain = false, const bool use_dont_cares = false, const uint32_t min_cand_cut_size = 3, 31 | const std::optional<uint32_t> min_cand_cut_size_override = std::nullopt, const bool preserve_depth = false, 32 | const bool verbose = false, const bool very_verbose = false) -> void 33 | { 34 | mockturtle::cut_rewriting_params params{}; 35 | params.cut_enumeration_ps.cut_size = cut_size; 36 | params.cut_enumeration_ps.cut_limit = cut_limit; 37 | params.cut_enumeration_ps.minimize_truth_table = minimize_truth_table; 38 | params.allow_zero_gain = allow_zero_gain; 39 | params.use_dont_cares = use_dont_cares; 40 | params.min_cand_cut_size = min_cand_cut_size; 41 | params.min_cand_cut_size_override = min_cand_cut_size_override; 42 | params.preserve_depth = preserve_depth; 43 | params.verbose = verbose; 44 | params.very_verbose = very_verbose; 45 | 46 | const mockturtle::xag_npn_resynthesis<Ntk, aigverse::aig, mockturtle::xag_npn_db_kind::aig_complete> 47 | aig_npn_resyn_engine{}; 48 | 49 | ntk = mockturtle::cut_rewriting(ntk, aig_npn_resyn_engine, params); 50 | }, 51 | "ntk"_a, "cut_size"_a = 4, "cut_limit"_a = 8, "minimize_truth_table"_a = true, "allow_zero_gain"_a = false, 52 | "use_dont_cares"_a = false, "min_cand_cut_size"_a = 3, "min_cand_cut_size_override"_a = std::nullopt, 53 | "preserve_depth"_a = false, "verbose"_a = false, "very_verbose"_a = false, 54 | pybind11::call_guard<pybind11::gil_scoped_release>()); 55 | } 56 | 57 | // Explicit instantiation for AIG 58 | template void rewriting<aigverse::aig>(pybind11::module_& m); 59 | 60 | } // namespace detail 61 | 62 | void bind_rewriting(pybind11::module_& m) 63 | { 64 | detail::rewriting<aigverse::aig>(m); 65 | } 66 | 67 | } // namespace aigverse 68 | -------------------------------------------------------------------------------- /.github/workflows/aigverse-python-tests.yml: -------------------------------------------------------------------------------- 1 | # this file is heavily based on https://github.com/cda-tum/mqt-workflows/blob/main/.github/workflows/reusable-python-ci.yml 2 | 3 | name: 🐍 • CI 4 | 5 | on: 6 | merge_group: 7 | push: 8 | branches: ["main"] 9 | paths: 10 | - "src/aigverse/**" 11 | - "test/**" 12 | - "**/*.py" 13 | - "**/*.hpp" 14 | - "**/*.cpp" 15 | - "**/*.cmake" 16 | - "**/CMakeLists.txt" 17 | - "libs/**" 18 | - "pyproject.toml" 19 | - "uv.lock" 20 | - ".github/workflows/aigverse-python-tests.yml" 21 | pull_request: 22 | branches: ["main"] 23 | paths: 24 | - "src/aigverse/**" 25 | - "test/**" 26 | - "**/*.py" 27 | - "**/*.hpp" 28 | - "**/*.cpp" 29 | - "**/*.cmake" 30 | - "**/CMakeLists.txt" 31 | - "libs/**" 32 | - "pyproject.toml" 33 | - "uv.lock" 34 | - ".github/workflows/aigverse-python-tests.yml" 35 | 36 | concurrency: 37 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 38 | cancel-in-progress: true 39 | 40 | jobs: 41 | lint: 42 | name: 🚨 Lint 43 | runs-on: ubuntu-latest 44 | timeout-minutes: 10 45 | steps: 46 | - name: Clone repository 47 | uses: actions/checkout@v6 48 | with: 49 | submodules: recursive 50 | fetch-depth: 0 51 | 52 | - name: Setup ccache 53 | uses: hendrikmuhs/ccache-action@v1.2 54 | with: 55 | key: "${{matrix.runs-on}}-aigverse" 56 | save: true 57 | max-size: 10G 58 | 59 | - name: Setup mold 60 | uses: rui314/setup-mold@v1 61 | 62 | - name: Install the latest version of uv 63 | uses: astral-sh/setup-uv@v7 64 | 65 | - name: Run mypy 66 | run: uvx --with pre-commit-uv pre-commit run -a mypy 67 | 68 | - name: Run check-sdist 69 | run: uvx check-sdist --inject-junk 70 | 71 | python-tests: 72 | name: 🐍 ${{ matrix.runs-on }} 73 | runs-on: ${{ matrix.runs-on }} 74 | strategy: 75 | fail-fast: false 76 | matrix: 77 | runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, windows-2025] 78 | steps: 79 | - name: Clone Repository 80 | uses: actions/checkout@v6 81 | with: 82 | submodules: recursive 83 | fetch-depth: 0 84 | 85 | - name: Setup ccache 86 | uses: hendrikmuhs/ccache-action@v1.2 87 | with: 88 | key: "${{matrix.runs-on}}-aigverse" 89 | save: true 90 | max-size: 10G 91 | 92 | - if: runner.os == 'Linux' 93 | name: Setup mold 94 | uses: rui314/setup-mold@v1 95 | 96 | - name: Install the latest version of uv 97 | uses: astral-sh/setup-uv@v7 98 | 99 | - name: 🐍 Test 100 | run: uvx nox -s tests --verbose 101 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/balancing.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/balancing.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <mockturtle/algorithms/balancing.hpp> 11 | #include <mockturtle/algorithms/balancing/esop_balancing.hpp> 12 | #include <mockturtle/algorithms/balancing/sop_balancing.hpp> 13 | #include <mockturtle/networks/aig.hpp> 14 | #include <pybind11/pybind11.h> 15 | 16 | #include <stdexcept> 17 | #include <string_view> 18 | 19 | namespace aigverse 20 | { 21 | 22 | namespace detail 23 | { 24 | 25 | template <typename Ntk> 26 | void balancing(pybind11::module_& m) 27 | { 28 | using namespace pybind11::literals; 29 | 30 | m.def( 31 | "balancing", 32 | [](Ntk& ntk, const uint32_t cut_size = 4, const uint32_t cut_limit = 8, const bool minimize_truth_table = true, 33 | const bool only_on_critical_path = false, const std::string_view& rebalance_function = "sop", 34 | const bool sop_both_phases = true, const bool verbose = false) -> void 35 | { 36 | mockturtle::balancing_params ps{}; 37 | ps.cut_enumeration_ps.cut_size = cut_size; 38 | ps.cut_enumeration_ps.cut_limit = cut_limit; 39 | ps.cut_enumeration_ps.minimize_truth_table = minimize_truth_table; 40 | ps.only_on_critical_path = only_on_critical_path; 41 | ps.verbose = verbose; 42 | 43 | if (rebalance_function == "sop") 44 | { 45 | mockturtle::sop_rebalancing<Ntk> rebalance_fn{}; 46 | rebalance_fn.both_phases_ = sop_both_phases; 47 | 48 | ntk = mockturtle::balancing(ntk, {rebalance_fn}, ps); 49 | } 50 | else if (rebalance_function == "esop") 51 | { 52 | mockturtle::esop_rebalancing<Ntk> rebalance_fn{}; 53 | rebalance_fn.both_phases = sop_both_phases; 54 | 55 | ntk = mockturtle::balancing(ntk, {rebalance_fn}, ps); 56 | } 57 | else 58 | { 59 | throw std::invalid_argument(fmt::format( 60 | "Unknown rebalance function: '{}'. Possible values are 'sop' and 'esop'.", rebalance_function)); 61 | } 62 | }, 63 | "ntk"_a, "cut_size"_a = 4, "cut_limit"_a = 8, "minimize_truth_table"_a = true, 64 | "only_on_critical_path"_a = false, "rebalance_function"_a = "sop", "sop_both_phases"_a = true, 65 | "verbose"_a = false, pybind11::call_guard<pybind11::gil_scoped_release>()); 66 | } 67 | 68 | // Explicit instantiation for AIG 69 | template void balancing<aigverse::aig>(pybind11::module_& m); 70 | 71 | } // namespace detail 72 | 73 | void bind_balancing(pybind11::module_& m) 74 | { 75 | detail::balancing<aigverse::aig>(m); 76 | } 77 | 78 | } // namespace aigverse 79 | -------------------------------------------------------------------------------- /.github/workflows/aigverse-codeql.yml: -------------------------------------------------------------------------------- 1 | name: 📝 • CodeQL 2 | 3 | on: 4 | push: 5 | branches: ["main"] 6 | paths: 7 | - "**/*.hpp" 8 | - "**/*.cpp" 9 | - "**/*.cmake" 10 | - "**/CMakeLists.txt" 11 | - "**/*.py" 12 | - "libs/**" 13 | - ".github/workflows/aigverse-codeql.yml" 14 | pull_request: 15 | branches: ["main"] 16 | paths: 17 | - "**/*.hpp" 18 | - "**/*.cpp" 19 | - "**/*.cmake" 20 | - "**/CMakeLists.txt" 21 | - "**/*.py" 22 | - "libs/**" 23 | - ".github/workflows/aigverse-codeql.yml" 24 | merge_group: 25 | schedule: 26 | - cron: "30 5 * * 6" 27 | 28 | concurrency: 29 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 30 | cancel-in-progress: true 31 | 32 | jobs: 33 | analyze: 34 | name: ${{matrix.emoji}} Analysis 35 | runs-on: ubuntu-latest 36 | permissions: 37 | actions: read 38 | contents: read 39 | security-events: write 40 | 41 | strategy: 42 | fail-fast: false 43 | matrix: 44 | language: ["cpp", "python"] 45 | include: 46 | - language: cpp 47 | emoji: 🇨 48 | - language: python 49 | emoji: 🐍 50 | 51 | steps: 52 | - name: Checkout repository 53 | uses: actions/checkout@v6 54 | with: 55 | submodules: recursive 56 | fetch-depth: 0 57 | 58 | - name: Setup ccache 59 | uses: hendrikmuhs/ccache-action@v1.2 60 | with: 61 | key: "ubuntu-latest-aigverse" 62 | save: false 63 | max-size: 10G 64 | 65 | - name: Set up mold as linker 66 | uses: rui314/setup-mold@v1 67 | 68 | - name: Initialize CodeQL 69 | uses: github/codeql-action/init@v4 70 | with: 71 | languages: ${{matrix.language}} 72 | config: | 73 | queries: 74 | - uses: security-and-quality 75 | 76 | - name: Configure CMake 77 | if: matrix.language == 'cpp' 78 | run: cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug 79 | 80 | - name: Build 81 | if: matrix.language == 'cpp' 82 | run: cmake --build build -j4 83 | 84 | - name: Perform CodeQL Analysis 85 | uses: github/codeql-action/analyze@v4 86 | with: 87 | upload: False 88 | output: sarif-results 89 | 90 | - name: Filter SARIF file to exclude library warnings 91 | uses: advanced-security/filter-sarif@main 92 | with: 93 | patterns: | 94 | -**/libs/** 95 | -**/docs/** 96 | -**/experiments/** 97 | input: sarif-results/${{ matrix.language }}.sarif 98 | output: sarif-results/${{ matrix.language }}.sarif 99 | 100 | - name: Upload SARIF 101 | uses: github/codeql-action/upload-sarif@v4 102 | with: 103 | sarif_file: sarif-results/${{ matrix.language }}.sarif 104 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # `aigverse` - A Python Library for Logic Networks, Synthesis, and Optimization 2 | 3 | ```{raw} latex 4 | \begin{abstract} 5 | ``` 6 | 7 | `aigverse` is an open-source C++17 and Python library for working with logic networks, synthesis, and optimization. It is developed by Marcel Walter at the [Technical University of Munich](https://www.tum.de/). 8 | 9 | It builds directly upon the [EPFL Logic Synthesis Libraries](https://arxiv.org/abs/1805.05121), particularly [mockturtle](https://github.com/lsils/mockturtle), providing a high-level Python interface to these powerful C++ libraries. This foundation gives `aigverse` access to state-of-the-art algorithms for And‑Inverter Graph (AIG) manipulation and logic synthesis. As an infrastructure project toward machine learning in logic synthesis, `aigverse` enables integration of logic synthesis and optimization tasks into ML pipelines. 10 | 11 | Key features include: 12 | 13 | - Efficient logic representation using And-Inverter Graphs (AIGs) 14 | - Support for various file formats (AIGER, Verilog, Bench, PLA) 15 | - High-performance C++ backend with a Pythonic interface 16 | - Integration capabilities with machine learning and data science workflows 17 | - Comprehensive tools for logic synthesis and optimization 18 | 19 | This documentation provides a comprehensive guide to the `aigverse` library, including {doc}`installation instructions <installation>`, a {doc}`quickstart guide <aigs>`, and detailed {doc}`API documentation <api/aigverse/index>`. 20 | The source code of `aigverse` is publicly available on GitHub at [marcelwa/aigverse](https://github.com/marcelwa/aigverse), while pre-built binaries are available via [PyPI](https://pypi.org/project/aigverse/) for all major operating systems and all modern Python versions. 21 | 22 | ```{seealso} 23 | For a deeper dive into the vision and technical details behind `aigverse`, see the presentation **"aigverse: Toward machine learning-driven logic synthesis"** from the [Free Silicon Conference (FSiC) 2025](https://wiki.f-si.org/index.php?title=FSiC2025). The [slides are available on the FSiC wiki](https://wiki.f-si.org/index.php?title=Aigverse:_Toward_machine_learning-driven_logic_synthesis) and cover the motivation, architecture, and future directions of the `aigverse` project. 24 | ``` 25 | 26 | ````{only} latex 27 | ```{note} 28 | A live version of this document is available at [aigverse.readthedocs.io](https://aigverse.readthedocs.io). 29 | ``` 30 | ```` 31 | 32 | ```{raw} latex 33 | \end{abstract} 34 | 35 | \sphinxtableofcontents 36 | ``` 37 | 38 | ```{toctree} 39 | :hidden: 40 | 41 | self 42 | ``` 43 | 44 | ```{toctree} 45 | :maxdepth: 2 46 | :caption: User Guide 47 | 48 | installation 49 | aigs 50 | truth_tables 51 | algorithms 52 | machine_learning 53 | ``` 54 | 55 | ````{only} not latex 56 | ```{toctree} 57 | :maxdepth: 2 58 | :titlesonly: 59 | :caption: Developers 60 | :glob: 61 | 62 | contributing 63 | support 64 | DevelopmentGuide 65 | ``` 66 | ```` 67 | 68 | ```{toctree} 69 | :hidden: 70 | :caption: Python API Reference 71 | 72 | api/aigverse/index 73 | ``` 74 | -------------------------------------------------------------------------------- /src/aigverse/algorithms/simulation.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 03.09.25. 3 | // 4 | 5 | #include "aigverse/algorithms/simulation.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <kitty/dynamic_truth_table.hpp> 10 | #include <mockturtle/algorithms/simulation.hpp> 11 | #include <pybind11/pybind11.h> 12 | #include <pybind11/stl.h> 13 | 14 | #include <iostream> 15 | #include <new> 16 | #include <unordered_map> 17 | #include <vector> 18 | 19 | namespace aigverse 20 | { 21 | 22 | namespace detail 23 | { 24 | 25 | template <typename Ntk> 26 | void simulation(pybind11::module_& m) 27 | { 28 | namespace py = pybind11; 29 | using namespace py::literals; 30 | 31 | m.def( 32 | "simulate", 33 | [](const Ntk& ntk) -> std::vector<kitty::dynamic_truth_table> 34 | { 35 | if (ntk.num_pis() > 16) 36 | { 37 | std::cout << "[w] trying to simulate a network with more than 16 inputs; this might take while and " 38 | "potentially cause memory issues\n"; 39 | } 40 | 41 | try 42 | { 43 | return mockturtle::simulate<kitty::dynamic_truth_table>( 44 | ntk, 45 | // NOLINTNEXTLINE 46 | mockturtle::default_simulator<kitty::dynamic_truth_table>{static_cast<unsigned>(ntk.num_pis())}); 47 | } 48 | catch (const std::bad_alloc&) 49 | { 50 | std::cout << "[e] network has too many inputs to store its truth table; out of memory!\n"; 51 | throw; 52 | } 53 | }, 54 | "network"_a); 55 | 56 | m.def( 57 | "simulate_nodes", 58 | [](const Ntk& ntk) -> std::unordered_map<uint64_t, kitty::dynamic_truth_table> 59 | { 60 | if (ntk.num_pis() > 16) 61 | { 62 | std::cout << "[w] trying to simulate a network with more than 16 inputs; this might take while and " 63 | "potentially cause memory issues\n"; 64 | } 65 | 66 | try 67 | { 68 | const auto n_map = mockturtle::simulate_nodes<kitty::dynamic_truth_table>( 69 | ntk, 70 | // NOLINTNEXTLINE 71 | mockturtle::default_simulator<kitty::dynamic_truth_table>{static_cast<unsigned>(ntk.num_pis())}); 72 | 73 | std::unordered_map<mockturtle::node<Ntk>, kitty::dynamic_truth_table> node_to_tt{}; 74 | 75 | // convert vector implementation to unordered_map 76 | ntk.foreach_node([&n_map, &node_to_tt](const auto& n) { node_to_tt[n] = n_map[n]; }); 77 | return node_to_tt; 78 | } 79 | catch (const std::bad_alloc&) 80 | { 81 | std::cout << "[e] network has too many inputs to store its truth table; out of memory!\n"; 82 | throw; 83 | } 84 | }, 85 | "network"_a, pybind11::call_guard<pybind11::gil_scoped_release>()); 86 | } 87 | 88 | // Explicit instantiation for AIG 89 | template void simulation<aigverse::aig>(pybind11::module_& m); 90 | 91 | } // namespace detail 92 | 93 | void bind_simulation(pybind11::module_& m) 94 | { 95 | detail::simulation<aigverse::aig>(m); 96 | } 97 | 98 | } // namespace aigverse 99 | -------------------------------------------------------------------------------- /test/adapters/test_numpy.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | try: 6 | import numpy as np 7 | except ImportError: 8 | pytest.skip( 9 | "NumPy could not be imported. The TruthTable to NumPy conversion will not be available. " 10 | "Skipping NumPy conversion tests. To enable this functionality, install numpy:\n\n" 11 | " uv pip install numpy\n", 12 | allow_module_level=True, 13 | ) 14 | 15 | from aigverse import TruthTable 16 | 17 | 18 | class TestNumpyConversion: 19 | """Test suite for the TruthTable to NumPy conversion functionality.""" 20 | 21 | @staticmethod 22 | def test_to_numpy_array() -> None: 23 | """Test conversion to different types of numpy arrays via np.array.""" 24 | tt = TruthTable(3) # 8 bits 25 | tt.create_from_binary_string("10100101") 26 | 27 | # Expected data 28 | expected_bool = [True, False, True, False, False, True, False, True] 29 | expected_int = [1, 0, 1, 0, 0, 1, 0, 1] 30 | expected_float = [1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 1.0] 31 | 32 | # Default conversion (should be bool) 33 | arr_bool = np.array(tt) 34 | assert arr_bool.dtype == np.bool_ 35 | assert arr_bool.shape == (8,) 36 | np.testing.assert_array_equal(arr_bool, expected_bool) 37 | 38 | # Integer conversion 39 | arr_int = np.array(tt, dtype=np.int32) 40 | assert arr_int.dtype == np.int32 41 | assert arr_int.shape == (8,) 42 | np.testing.assert_array_equal(arr_int, expected_int) 43 | 44 | # Floating point conversion 45 | arr_float = np.array(tt, dtype=np.float64) 46 | assert arr_float.dtype == np.float64 47 | assert arr_float.shape == (8,) 48 | np.testing.assert_allclose(arr_float, expected_float) 49 | 50 | @staticmethod 51 | def test_to_numpy_asarray() -> None: 52 | """Test conversion to numpy array via np.asarray.""" 53 | tt = TruthTable(3) # 8 bits 54 | tt.create_from_binary_string("10100101") 55 | 56 | expected_data = [1, 0, 1, 0, 0, 1, 0, 1] 57 | 58 | # asarray should create a new array from the TruthTable sequence 59 | arr = np.asarray(tt, dtype=np.uint8) 60 | assert isinstance(arr, np.ndarray) 61 | assert arr.dtype == np.uint8 62 | assert arr.shape == (8,) 63 | np.testing.assert_array_equal(arr, expected_data) 64 | 65 | # Modifying the original TruthTable should not affect the array because 66 | # TruthTable is not a numpy array, so asarray makes a copy. 67 | tt[0] = False 68 | np.testing.assert_array_equal(arr, expected_data) # arr should be unchanged 69 | 70 | @staticmethod 71 | def test_small_truth_table_to_numpy() -> None: 72 | """Test conversion of a small (0-var, 1-bit) truth table.""" 73 | tt = TruthTable(0) # 0 vars, 1 bit 74 | assert len(tt) == 1 75 | 76 | # bit is 0 77 | arr = np.array(tt) 78 | assert arr.shape == (1,) 79 | assert arr.dtype == np.bool_ 80 | np.testing.assert_array_equal(arr, [False]) 81 | 82 | # bit is 1 83 | tt[0] = True 84 | arr = np.array(tt) 85 | assert arr.shape == (1,) 86 | assert arr.dtype == np.bool_ 87 | np.testing.assert_array_equal(arr, [True]) 88 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | FormatStyle: file 2 | 3 | Checks: | 4 | clang-diagnostic-*, 5 | clang-analyzer-*, 6 | bugprone-*, 7 | -bugprone-easily-swappable-parameters, 8 | cert-*, 9 | -cert-dcl16-c, 10 | -cert-dcl21-cpp, 11 | -cert-dcl58-cpp, 12 | -cert-err58-cpp, 13 | concurrency-*, 14 | cppcoreguidelines-*, 15 | -cppcoreguidelines-avoid-magic-numbers, 16 | -cppcoreguidelines-non-private-member-variables-in-classes, 17 | -cppcoreguidelines-pro-type-union-access, 18 | google-*, 19 | -google-readability-todo, 20 | -google-build-using-namespace, 21 | hicpp-*, 22 | -hicpp-named-parameter, 23 | -hicpp-uppercase-literal-suffix, 24 | misc-*, 25 | -misc-no-recursion, 26 | -misc-non-private-member-variables-in-classes, 27 | -misc-use-anonymous-namespace, 28 | modernize-*, 29 | -modernize-use-trailing-return-type, 30 | performance-*, 31 | portability-*, 32 | readability-*, 33 | -readability-function-cognitive-complexity, 34 | -readability-identifier-length, 35 | -readability-isolate-declaration, 36 | -readability-magic-numbers, 37 | -readability-named-parameter, 38 | -readability-uppercase-literal-suffix 39 | 40 | CheckOptions: 41 | - key: cppcoreguidelines-avoid-do-while.IgnoreMacros 42 | value: "true" 43 | - key: readability-identifier-naming.NamespaceCase 44 | value: lower_case 45 | - key: readability-identifier-naming.ClassCase 46 | value: lower_case 47 | - key: readability-identifier-naming.StructCase 48 | value: lower_case 49 | - key: readability-identifier-naming.MethodCase 50 | value: lower_case 51 | - key: readability-identifier-naming.MemberCase 52 | value: lower_case 53 | - key: readability-identifier-naming.FunctionCase 54 | value: lower_case 55 | - key: readability-identifier-naming.ParameterCase 56 | value: lower_case 57 | - key: readability-identifier-naming.ConstantParameterCase 58 | value: lower_case 59 | - key: readability-identifier-naming.TemplateParameterCase 60 | value: CamelCase 61 | - key: readability-identifier-naming.TypeTemplateParameterCase 62 | value: CamelCase 63 | - key: readability-identifier-naming.ValueTemplateParameterCase 64 | value: aNy_CasE 65 | - key: readability-identifier-naming.VariableCase 66 | value: lower_case 67 | - key: readability-identifier-naming.LocalVariableCase 68 | value: lower_case 69 | - key: readability-identifier-naming.StaticVariableCase 70 | value: lower_case 71 | - key: readability-identifier-naming.EnumCase 72 | value: lower_case 73 | - key: readability-identifier-naming.EnumConstantCase 74 | value: UPPER_CASE 75 | - key: readability-identifier-naming.ScopedEnumConstantCase 76 | value: UPPER_CASE 77 | - key: readability-identifier-naming.GlobalConstantCase 78 | value: UPPER_CASE 79 | - key: readability-identifier-naming.GlobalConstantIgnoredRegexp 80 | value: "^has_.*_v$|^is_.*_v$|^.*_name$" 81 | - key: readability-identifier-naming.LocalConstantCase 82 | value: lower_case 83 | - key: readability-identifier-naming.StaticConstantCase 84 | value: lower_case 85 | - key: readability-identifier-naming.TypeAliasCase 86 | value: lower_case 87 | - key: readability-identifier-naming.TypeAliasIgnoredRegexp 88 | value: ".*Ntk.*|.*Lyt.*|.*Network.*|.*Layout.*" 89 | - key: readability-identifier-naming.TypedefCase 90 | value: lower_case 91 | - key: readability-identifier-naming.UnionCase 92 | value: lower_case 93 | - key: readability-identifier-naming.MacroDefinitionCase 94 | value: UPPER_CASE 95 | - key: readability-identifier-naming.IgnoreMainLikeFunctions 96 | value: "true" 97 | -------------------------------------------------------------------------------- /test/algorithms/test_rewriting.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import Aig, aig_cut_rewriting, equivalence_checking 4 | 5 | 6 | def test_empty_aigs() -> None: 7 | aig1 = Aig() 8 | aig2 = aig1.clone() 9 | 10 | aig_cut_rewriting(aig1) 11 | 12 | assert equivalence_checking(aig1, aig2) 13 | 14 | 15 | def test_simple_aigs() -> None: 16 | aig1 = Aig() 17 | aig2 = Aig() 18 | 19 | a1 = aig1.create_pi() 20 | b1 = aig1.create_pi() 21 | 22 | and1 = aig1.create_and(a1, b1) 23 | 24 | aig1.create_po(and1) 25 | aig1.create_po(b1) 26 | 27 | a2 = aig2.create_pi() 28 | b2 = aig2.create_pi() 29 | 30 | and2 = aig2.create_and(a2, b2) 31 | 32 | aig2.create_po(and2) 33 | aig2.create_po(b2) 34 | 35 | aig_cut_rewriting(aig1) 36 | 37 | assert equivalence_checking(aig1, aig2) 38 | assert equivalence_checking(aig1, aig1.clone()) 39 | 40 | 41 | def test_aig_and_its_negated_copy() -> None: 42 | aig1 = Aig() 43 | 44 | a1 = aig1.create_pi() 45 | b1 = aig1.create_pi() 46 | c1 = aig1.create_pi() 47 | 48 | and1 = aig1.create_and(a1, b1) 49 | and2 = aig1.create_and(~a1, c1) 50 | and3 = aig1.create_and(and1, and2) 51 | 52 | aig2 = aig1.clone() 53 | 54 | aig1.create_po(and3) 55 | 56 | aig2.create_po(~and3) 57 | 58 | aig_cut_rewriting(aig1) 59 | 60 | assert not equivalence_checking(aig1, aig2) 61 | 62 | aig_cut_rewriting(aig2) 63 | 64 | assert not equivalence_checking(aig1, aig2) 65 | 66 | 67 | def test_equivalent_node_merger() -> None: 68 | # x0 * !(!x0 * !x1) == > x0 69 | aig1 = Aig() 70 | x0 = aig1.create_pi() 71 | x1 = aig1.create_pi() 72 | n0 = aig1.create_and(~x0, ~x1) 73 | n1 = aig1.create_and(x0, ~n0) 74 | aig1.create_po(n1) 75 | 76 | aig_before = aig1.clone() 77 | 78 | aig_cut_rewriting(aig1) 79 | 80 | assert equivalence_checking(aig1, aig_before) 81 | 82 | 83 | def test_positive_divisor_substitution() -> None: 84 | # x1 * ( x0 * x1 ) ==> x0 * x1 85 | aig2 = Aig() 86 | x0 = aig2.create_pi() 87 | x1 = aig2.create_pi() 88 | n0 = aig2.create_and(x0, x1) 89 | n1 = aig2.create_and(x1, n0) 90 | aig2.create_po(n1) 91 | 92 | aig_before = aig2.clone() 93 | 94 | aig_cut_rewriting(aig2) 95 | 96 | assert equivalence_checking(aig2, aig_before) 97 | 98 | 99 | def test_negative_divisor_substitution() -> None: 100 | # !x0 * !(!x0 * !x1) == > !x0 * x1 101 | aig = Aig() 102 | x0 = aig.create_pi() 103 | x1 = aig.create_pi() 104 | n0 = aig.create_and(~x0, ~x1) 105 | n1 = aig.create_and(x0, ~n0) 106 | aig.create_po(n1) 107 | 108 | aig_before = aig.clone() 109 | 110 | aig_cut_rewriting(aig) 111 | 112 | assert equivalence_checking(aig, aig_before) 113 | 114 | 115 | def test_parameters() -> None: 116 | aig = Aig() 117 | 118 | a = aig.create_pi() 119 | b = aig.create_pi() 120 | 121 | and1 = aig.create_and(~a, ~b) 122 | and2 = aig.create_and(a, ~and1) 123 | 124 | aig.create_po(and2) 125 | 126 | aig2 = aig.clone() 127 | 128 | aig_cut_rewriting( 129 | aig, 130 | cut_size=8, 131 | cut_limit=12, 132 | minimize_truth_table=False, 133 | allow_zero_gain=True, 134 | use_dont_cares=True, 135 | min_cand_cut_size=4, 136 | min_cand_cut_size_override=5, 137 | preserve_depth=True, 138 | verbose=True, 139 | very_verbose=True, 140 | ) 141 | 142 | assert equivalence_checking(aig, aig2) 143 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function( 2 | aigverse_enable_sanitizers 3 | project_name 4 | ENABLE_SANITIZER_ADDRESS 5 | ENABLE_SANITIZER_LEAK 6 | ENABLE_SANITIZER_UNDEFINED_BEHAVIOR 7 | ENABLE_SANITIZER_THREAD 8 | ENABLE_SANITIZER_MEMORY) 9 | 10 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 11 | ".*Clang") 12 | set(SANITIZERS "") 13 | 14 | if(${ENABLE_SANITIZER_ADDRESS}) 15 | list(APPEND SANITIZERS "address") 16 | endif() 17 | 18 | if(${ENABLE_SANITIZER_LEAK}) 19 | if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") 20 | message(SEND_ERROR "Leak sanitizer is not supported on Apple Clang.") 21 | else() 22 | list(APPEND SANITIZERS "leak") 23 | endif() 24 | endif() 25 | 26 | if(${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR}) 27 | list(APPEND SANITIZERS "undefined") 28 | endif() 29 | 30 | if(${ENABLE_SANITIZER_THREAD}) 31 | if("address" IN_LIST SANITIZERS OR "leak" IN_LIST SANITIZERS) 32 | message( 33 | WARNING 34 | "Thread sanitizer does not work with Address and Leak sanitizer enabled" 35 | ) 36 | else() 37 | list(APPEND SANITIZERS "thread") 38 | endif() 39 | endif() 40 | 41 | if(${ENABLE_SANITIZER_MEMORY} AND CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 42 | message( 43 | WARNING 44 | "Memory sanitizer requires all the code (including libc++) to be MSan-instrumented otherwise it reports false positives" 45 | ) 46 | if("address" IN_LIST SANITIZERS 47 | OR "thread" IN_LIST SANITIZERS 48 | OR "leak" IN_LIST SANITIZERS) 49 | message( 50 | WARNING 51 | "Memory sanitizer does not work with Address, Thread or Leak sanitizer enabled" 52 | ) 53 | else() 54 | list(APPEND SANITIZERS "memory") 55 | endif() 56 | endif() 57 | elseif(MSVC) 58 | if(${ENABLE_SANITIZER_ADDRESS}) 59 | list(APPEND SANITIZERS "address") 60 | endif() 61 | if(${ENABLE_SANITIZER_LEAK} 62 | OR ${ENABLE_SANITIZER_UNDEFINED_BEHAVIOR} 63 | OR ${ENABLE_SANITIZER_THREAD} 64 | OR ${ENABLE_SANITIZER_MEMORY}) 65 | message(WARNING "MSVC only supports address sanitizer") 66 | endif() 67 | endif() 68 | 69 | list(JOIN SANITIZERS "," LIST_OF_SANITIZERS) 70 | 71 | if(LIST_OF_SANITIZERS) 72 | if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") 73 | if(NOT MSVC) 74 | target_compile_options(${project_name} 75 | INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 76 | target_link_options(${project_name} INTERFACE 77 | -fsanitize=${LIST_OF_SANITIZERS}) 78 | else() 79 | string(FIND "$ENV{PATH}" "$ENV{VSINSTALLDIR}" index_of_vs_install_dir) 80 | if("${index_of_vs_install_dir}" STREQUAL "-1") 81 | message( 82 | SEND_ERROR 83 | "Using MSVC sanitizers requires setting the MSVC environment before building the project. Please manually open the MSVC command prompt and rebuild the project." 84 | ) 85 | endif() 86 | target_compile_options( 87 | ${project_name} INTERFACE /fsanitize=${LIST_OF_SANITIZERS} /Zi 88 | /INCREMENTAL:NO) 89 | target_compile_definitions( 90 | ${project_name} INTERFACE _DISABLE_VECTOR_ANNOTATION 91 | _DISABLE_STRING_ANNOTATION) 92 | target_link_options(${project_name} INTERFACE /INCREMENTAL:NO) 93 | endif() 94 | endif() 95 | endif() 96 | 97 | endfunction() 98 | -------------------------------------------------------------------------------- /test/algorithms/test_refactoring.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import Aig, equivalence_checking, sop_refactoring 4 | 5 | 6 | def test_empty_aigs() -> None: 7 | aig1 = Aig() 8 | aig2 = aig1.clone() 9 | 10 | sop_refactoring(aig1) 11 | 12 | assert equivalence_checking(aig1, aig2) 13 | 14 | 15 | def test_simple_aigs() -> None: 16 | aig1 = Aig() 17 | aig2 = Aig() 18 | 19 | a1 = aig1.create_pi() 20 | b1 = aig1.create_pi() 21 | 22 | and1 = aig1.create_and(a1, b1) 23 | 24 | aig1.create_po(and1) 25 | aig1.create_po(b1) 26 | 27 | a2 = aig2.create_pi() 28 | b2 = aig2.create_pi() 29 | 30 | and2 = aig2.create_and(a2, b2) 31 | 32 | aig2.create_po(and2) 33 | aig2.create_po(b2) 34 | 35 | sop_refactoring(aig1) 36 | 37 | assert equivalence_checking(aig1, aig2) 38 | assert equivalence_checking(aig1, aig1.clone()) 39 | 40 | 41 | def test_aig_and_its_negated_copy() -> None: 42 | aig1 = Aig() 43 | 44 | a1 = aig1.create_pi() 45 | b1 = aig1.create_pi() 46 | c1 = aig1.create_pi() 47 | 48 | and1 = aig1.create_and(a1, b1) 49 | and2 = aig1.create_and(~a1, c1) 50 | and3 = aig1.create_and(and1, and2) 51 | 52 | aig2 = aig1.clone() 53 | 54 | aig1.create_po(and3) 55 | 56 | aig2.create_po(~and3) 57 | 58 | sop_refactoring(aig1) 59 | 60 | assert not equivalence_checking(aig1, aig2) 61 | 62 | sop_refactoring(aig2) 63 | 64 | assert not equivalence_checking(aig1, aig2) 65 | 66 | 67 | def test_equivalent_node_merger() -> None: 68 | # x0 * !(!x0 * !x1) == > x0 (reduction of 2 nodes) 69 | aig1 = Aig() 70 | x0 = aig1.create_pi() 71 | x1 = aig1.create_pi() 72 | n0 = aig1.create_and(~x0, ~x1) 73 | n1 = aig1.create_and(x0, ~n0) 74 | aig1.create_po(n1) 75 | 76 | aig_before = aig1.clone() 77 | 78 | sop_refactoring(aig1) 79 | 80 | assert aig1.size() == aig_before.size() - 2 81 | 82 | assert equivalence_checking(aig1, aig_before) 83 | 84 | 85 | def test_positive_divisor_substitution() -> None: 86 | # x1 * ( x0 * x1 ) ==> x0 * x1 (reduction of 1 node) 87 | aig2 = Aig() 88 | x0 = aig2.create_pi() 89 | x1 = aig2.create_pi() 90 | n0 = aig2.create_and(x0, x1) 91 | n1 = aig2.create_and(x1, n0) 92 | aig2.create_po(n1) 93 | 94 | aig_before = aig2.clone() 95 | 96 | sop_refactoring(aig2) 97 | 98 | assert aig2.size() == aig_before.size() - 1 99 | 100 | assert equivalence_checking(aig2, aig_before) 101 | 102 | 103 | def test_negative_divisor_substitution() -> None: 104 | # !x0 * !(!x0 * !x1) == > !x0 * x1 (reduction of 2 nodes) 105 | aig = Aig() 106 | x0 = aig.create_pi() 107 | x1 = aig.create_pi() 108 | n0 = aig.create_and(~x0, ~x1) 109 | n1 = aig.create_and(x0, ~n0) 110 | aig.create_po(n1) 111 | 112 | aig_before = aig.clone() 113 | 114 | sop_refactoring(aig) 115 | 116 | assert aig.size() == aig_before.size() - 2 117 | 118 | assert equivalence_checking(aig, aig_before) 119 | 120 | 121 | def test_parameters() -> None: 122 | aig = Aig() 123 | 124 | a = aig.create_pi() 125 | b = aig.create_pi() 126 | 127 | and1 = aig.create_and(~a, ~b) 128 | and2 = aig.create_and(a, ~and1) 129 | 130 | aig.create_po(and2) 131 | 132 | aig2 = aig.clone() 133 | 134 | sop_refactoring( 135 | aig, 136 | max_pis=2, 137 | allow_zero_gain=True, 138 | use_reconvergence_cut=False, 139 | use_dont_cares=True, 140 | verbose=True, 141 | ) 142 | 143 | assert equivalence_checking(aig, aig2) 144 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # To run all pre-commit checks, use: 2 | # 3 | # pre-commit run -a 4 | # 5 | # To install pre-commit hooks that run every time you commit: 6 | # 7 | # pre-commit install 8 | # 9 | 10 | ci: 11 | autoupdate_commit_msg: "⬆️ Bump pre-commit hooks" 12 | autoupdate_schedule: quarterly 13 | autofix_commit_msg: "🎨 Incorporated pre-commit fixes" 14 | skip: [mypy] 15 | 16 | exclude: "^(libs/|docs/examples/)" 17 | 18 | repos: 19 | # Standard hooks 20 | - repo: https://github.com/pre-commit/pre-commit-hooks 21 | rev: v6.0.0 22 | hooks: 23 | - id: check-added-large-files 24 | - id: check-case-conflict 25 | - id: check-docstring-first 26 | - id: check-merge-conflict 27 | - id: check-symlinks 28 | - id: check-toml 29 | - id: check-yaml 30 | - id: debug-statements 31 | - id: end-of-file-fixer 32 | - id: mixed-line-ending 33 | - id: requirements-txt-fixer 34 | - id: trailing-whitespace 35 | 36 | # Handle unwanted unicode characters 37 | - repo: https://github.com/sirosen/texthooks 38 | rev: 0.7.1 39 | hooks: 40 | - id: fix-ligatures 41 | - id: fix-smartquotes 42 | 43 | # Check for spelling 44 | - repo: https://github.com/crate-ci/typos 45 | rev: v1.40.0 46 | hooks: 47 | - id: typos 48 | 49 | # Check for common RST mistakes 50 | - repo: https://github.com/pre-commit/pygrep-hooks 51 | rev: v1.10.0 52 | hooks: 53 | - id: rst-backticks 54 | - id: rst-directive-colons 55 | - id: rst-inline-touching-normal 56 | 57 | # Python linting using ruff 58 | - repo: https://github.com/astral-sh/ruff-pre-commit 59 | rev: v0.14.9 60 | hooks: 61 | - id: ruff 62 | args: ["--fix", "--show-fixes"] 63 | - id: ruff-format 64 | 65 | # Check static types with mypy 66 | - repo: https://github.com/pre-commit/mirrors-mypy 67 | rev: v1.19.0 68 | hooks: 69 | - id: mypy 70 | files: ^(src/aigverse|noxfile.py) 71 | args: [] 72 | additional_dependencies: 73 | - nox 74 | - pytest 75 | - matplotlib-stubs 76 | - networkx-stubs 77 | 78 | # clang-format the C++ part of the code base 79 | - repo: https://github.com/pre-commit/mirrors-clang-format 80 | rev: v21.1.7 81 | hooks: 82 | - id: clang-format 83 | types_or: [c++, c] 84 | args: ["-style=file"] 85 | 86 | # Clean jupyter notebooks 87 | - repo: https://github.com/srstevenson/nb-clean 88 | rev: 4.0.1 89 | hooks: 90 | - id: nb-clean 91 | args: 92 | - --remove-empty-cells 93 | - --preserve-cell-metadata 94 | - raw_mimetype 95 | - -- 96 | 97 | # Also run Black on examples in the documentation 98 | - repo: https://github.com/adamchainz/blacken-docs 99 | rev: 1.20.0 100 | hooks: 101 | - id: blacken-docs 102 | additional_dependencies: [black==24.*] 103 | 104 | # Format configuration files with prettier 105 | - repo: https://github.com/rbubley/mirrors-prettier 106 | rev: v3.7.4 107 | hooks: 108 | - id: prettier 109 | types_or: [yaml, markdown, html, css, scss, javascript, json] 110 | 111 | # Check JSON schemata 112 | - repo: https://github.com/python-jsonschema/check-jsonschema 113 | rev: 0.35.0 114 | hooks: 115 | - id: check-dependabot 116 | - id: check-github-workflows 117 | - id: check-readthedocs 118 | 119 | # Check the pyproject.toml file 120 | - repo: https://github.com/henryiii/validate-pyproject-schema-store 121 | rev: 2025.10.03 122 | hooks: 123 | - id: validate-pyproject 124 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | .ccache/ 9 | cmake-build-* 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | out/build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | wheelhouse/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | cover/ 57 | *.profraw 58 | 59 | # Translations 60 | *.mo 61 | *.pot 62 | 63 | # Django stuff: 64 | *.log 65 | local_settings.py 66 | db.sqlite3 67 | db.sqlite3-journal 68 | 69 | # Flask stuff: 70 | instance/ 71 | .webassets-cache 72 | 73 | # Scrapy stuff: 74 | .scrapy 75 | 76 | # Sphinx documentation 77 | docs/_build/ 78 | docs/**/build 79 | docs/api/ 80 | docs/example.aig 81 | docs/example.v 82 | 83 | # PyBuilder 84 | .pybuilder/ 85 | target/ 86 | 87 | # Jupyter Notebook 88 | .ipynb_checkpoints 89 | 90 | # IPython 91 | profile_default/ 92 | ipython_config.py 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env* 106 | .venv* 107 | env*/ 108 | venv*/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # pytype static type analyzer 132 | .pytype/ 133 | 134 | # Cython debug symbols 135 | cython_debug/ 136 | 137 | # setuptools_scm 138 | src/aigverse/_version.py 139 | 140 | # SKBuild cache dir 141 | _skbuild/ 142 | 143 | # Any build dirs in the tests 144 | test/**/build/ 145 | test/resources/test.aig 146 | 147 | # Common editor files 148 | *~ 149 | *.swp 150 | 151 | # RPM spec file 152 | !/distro/*.spec 153 | /distro/*.tar.gz 154 | *.rpm 155 | 156 | # ruff 157 | .ruff_cache/ 158 | 159 | # OS specific stuff 160 | .DS_Store 161 | .DS_Store? 162 | ._* 163 | .Spotlight-V100 164 | .Trashes 165 | ehthumbs.db 166 | Thumbs.db 167 | 168 | # Prerequisites 169 | *.d 170 | 171 | # Compiled Object files 172 | *.slo 173 | *.lo 174 | *.o 175 | *.obj 176 | 177 | # Precompiled Headers 178 | *.gch 179 | *.pch 180 | 181 | # Compiled Dynamic libraries 182 | *.dylib 183 | *.dll 184 | 185 | # Fortran module files 186 | *.mod 187 | *.smod 188 | 189 | # Compiled Static libraries 190 | *.lai 191 | *.la 192 | *.a 193 | *.lib 194 | 195 | # Executables 196 | *.exe 197 | *.out 198 | *.app 199 | 200 | # LaTeX files 201 | *.aux 202 | *.pdf 203 | *.synctex.gz 204 | 205 | # Doxygen files 206 | *.bak 207 | 208 | # IDE files 209 | *.directory 210 | *.idea 211 | *.vs 212 | 213 | # Experiment files 214 | *.json 215 | *.csv 216 | 217 | # Coverage files 218 | lcov.info 219 | 220 | # Python 221 | __pycache__ 222 | *.egg-info 223 | *.env 224 | 225 | # Archive files 226 | *.zip 227 | *.tar.* 228 | *.7z 229 | *.rar 230 | -------------------------------------------------------------------------------- /test/algorithms/test_resubstitution.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import Aig, aig_resubstitution, equivalence_checking 4 | 5 | 6 | def test_empty_aigs() -> None: 7 | aig1 = Aig() 8 | aig2 = aig1.clone() 9 | 10 | aig_resubstitution(aig1) 11 | 12 | assert equivalence_checking(aig1, aig2) 13 | 14 | 15 | def test_simple_aigs() -> None: 16 | aig1 = Aig() 17 | aig2 = Aig() 18 | 19 | a1 = aig1.create_pi() 20 | b1 = aig1.create_pi() 21 | 22 | and1 = aig1.create_and(a1, b1) 23 | 24 | aig1.create_po(and1) 25 | aig1.create_po(b1) 26 | 27 | a2 = aig2.create_pi() 28 | b2 = aig2.create_pi() 29 | 30 | and2 = aig2.create_and(a2, b2) 31 | 32 | aig2.create_po(and2) 33 | aig2.create_po(b2) 34 | 35 | aig_resubstitution(aig1) 36 | 37 | assert equivalence_checking(aig1, aig2) 38 | assert equivalence_checking(aig1, aig1.clone()) 39 | 40 | 41 | def test_aig_and_its_negated_copy() -> None: 42 | aig1 = Aig() 43 | 44 | a1 = aig1.create_pi() 45 | b1 = aig1.create_pi() 46 | c1 = aig1.create_pi() 47 | 48 | and1 = aig1.create_and(a1, b1) 49 | and2 = aig1.create_and(~a1, c1) 50 | and3 = aig1.create_and(and1, and2) 51 | 52 | aig2 = aig1.clone() 53 | 54 | aig1.create_po(and3) 55 | 56 | aig2.create_po(~and3) 57 | 58 | aig_resubstitution(aig1) 59 | 60 | assert not equivalence_checking(aig1, aig2) 61 | 62 | aig_resubstitution(aig2) 63 | 64 | assert not equivalence_checking(aig1, aig2) 65 | 66 | 67 | def test_equivalent_node_merger() -> None: 68 | # x0 * !(!x0 * !x1) == > x0 (reduction of 2 nodes) 69 | aig1 = Aig() 70 | x0 = aig1.create_pi() 71 | x1 = aig1.create_pi() 72 | n0 = aig1.create_and(~x0, ~x1) 73 | n1 = aig1.create_and(x0, ~n0) 74 | aig1.create_po(n1) 75 | 76 | aig_before = aig1.clone() 77 | 78 | aig_resubstitution(aig1) 79 | 80 | assert aig1.size() == aig_before.size() - 2 81 | 82 | assert equivalence_checking(aig1, aig_before) 83 | 84 | 85 | def test_positive_divisor_substitution() -> None: 86 | # x1 * ( x0 * x1 ) ==> x0 * x1 (reduction of 1 node) 87 | aig2 = Aig() 88 | x0 = aig2.create_pi() 89 | x1 = aig2.create_pi() 90 | n0 = aig2.create_and(x0, x1) 91 | n1 = aig2.create_and(x1, n0) 92 | aig2.create_po(n1) 93 | 94 | aig_before = aig2.clone() 95 | 96 | aig_resubstitution(aig2) 97 | 98 | assert aig2.size() == aig_before.size() - 1 99 | 100 | assert equivalence_checking(aig2, aig_before) 101 | 102 | 103 | def test_negative_divisor_substitution() -> None: 104 | # !x0 * !(!x0 * !x1) == > !x0 * x1 (reduction of 2 nodes) 105 | aig = Aig() 106 | x0 = aig.create_pi() 107 | x1 = aig.create_pi() 108 | n0 = aig.create_and(~x0, ~x1) 109 | n1 = aig.create_and(x0, ~n0) 110 | aig.create_po(n1) 111 | 112 | aig_before = aig.clone() 113 | 114 | aig_resubstitution(aig) 115 | 116 | assert aig.size() == aig_before.size() - 2 117 | 118 | assert equivalence_checking(aig, aig_before) 119 | 120 | 121 | def test_parameters() -> None: 122 | aig = Aig() 123 | 124 | a = aig.create_pi() 125 | b = aig.create_pi() 126 | 127 | and1 = aig.create_and(~a, ~b) 128 | and2 = aig.create_and(a, ~and1) 129 | 130 | aig.create_po(and2) 131 | 132 | aig2 = aig.clone() 133 | 134 | aig_resubstitution( 135 | aig, 136 | max_pis=2, 137 | max_divisors=10, 138 | max_inserts=3, 139 | skip_fanout_limit_for_roots=10, 140 | skip_fanout_limit_for_divisors=10, 141 | verbose=True, 142 | use_dont_cares=True, 143 | window_size=6, 144 | preserve_depth=True, 145 | ) 146 | 147 | assert equivalence_checking(aig, aig2) 148 | -------------------------------------------------------------------------------- /docs/installation.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | `aigverse` offers Python bindings on top of the [EPFL Logic Synthesis Libraries](https://arxiv.org/abs/1805.05121) together with custom adapters that enable convenient integration into machine learning pipelines. 4 | The resulting Python package is available on [PyPI](https://pypi.org/project/aigverse/) and can be installed on all major operating systems using all modern Python versions. 5 | 6 | :::::{tip} 7 | We highly recommend using [`uv`](https://docs.astral.sh/uv/) for working with Python projects. 8 | It is an extremely fast Python package and project manager, written in Rust and developed by [Astral](https://astral.sh/) (the same team behind [`ruff`](https://docs.astral.sh/ruff/)). 9 | It can act as a drop-in replacement for `pip` and `virtualenv`, and provides a more modern and faster alternative to the traditional Python package management tools. 10 | It automatically handles the creation of virtual environments and the installation of packages, and is much faster than `pip`. 11 | Additionally, it can even set up Python for you if it is not installed yet. 12 | 13 | If you do not have `uv` installed yet, you can install it via: 14 | 15 | ::::{tab-set} 16 | :::{tab-item} macOS and Linux 17 | 18 | ```console 19 | $ curl -LsSf https://astral.sh/uv/install.sh | sh 20 | ``` 21 | 22 | ::: 23 | :::{tab-item} Windows 24 | 25 | ```console 26 | $ powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex" 27 | ``` 28 | 29 | :::: 30 | 31 | Check out their excellent [documentation](https://docs.astral.sh/uv/) for more information. 32 | 33 | ::::: 34 | 35 | ## Core Library 36 | 37 | To install the core `aigverse` library, you can use `uv` or `pip`. 38 | 39 | ::::{tab-set} 40 | :sync-group: installer 41 | 42 | :::{tab-item} uv _(recommended)_ 43 | :sync: uv 44 | 45 | ```console 46 | $ uv pip install aigverse 47 | ``` 48 | 49 | ::: 50 | 51 | :::{tab-item} pip 52 | :sync: pip 53 | 54 | ```console 55 | (.venv) $ python -m pip install aigverse 56 | ``` 57 | 58 | ::: 59 | :::: 60 | 61 | In most practical cases (under 64-bit Linux, macOS incl. Apple Silicon, and Windows), this requires no compilation and merely downloads and installs a platform-specific pre-built wheel. 62 | 63 | Once installed, you can check if the installation was successful by running: 64 | 65 | ```console 66 | (.venv) $ python -c "import aigverse; print(aigverse.__version__)" 67 | ``` 68 | 69 | which should print the installed version of the library. 70 | 71 | --- 72 | 73 | If you want to use the `aigverse` Python package in your own project, you can simply add it as a dependency to your 74 | `pyproject.toml` or `setup.py` file. This will automatically install the `aigverse` package and its dependencies when 75 | your project is installed. 76 | 77 | ::::{tab-set} 78 | 79 | :::{tab-item} uv _(recommended)_ 80 | 81 | ```console 82 | $ uv add aigverse 83 | ``` 84 | 85 | ::: 86 | 87 | :::{tab-item} pyproject.toml 88 | 89 | ```toml 90 | [project] 91 | # ... 92 | dependencies = ["aigverse"] 93 | # ... 94 | ``` 95 | 96 | ::: 97 | 98 | :::{tab-item} setup.py 99 | 100 | ```python 101 | from setuptools import setup 102 | 103 | setup( 104 | # ... 105 | install_requires=["aigverse"], 106 | # ... 107 | ) 108 | ``` 109 | 110 | ::: 111 | :::: 112 | 113 | ## Machine Learning Adapters 114 | 115 | To keep the library as light-weight as possible for default logic synthesis tasks, machine learning integration adapters 116 | for `aigverse` are not installed by default as those require many additional dependencies. Instead, you can opt in to 117 | the adapters by installing the `aigverse[adapters]` extra: 118 | 119 | ::::{tab-set} 120 | :sync-group: installer 121 | 122 | :::{tab-item} uv _(recommended)_ 123 | :sync: uv 124 | 125 | ```console 126 | $ uv pip install "aigverse[adapters]" 127 | ``` 128 | 129 | ::: 130 | 131 | :::{tab-item} pip 132 | :sync: pip 133 | 134 | ```console 135 | (.venv) $ python -m pip install "aigverse[adapters]" 136 | ``` 137 | 138 | ::: 139 | :::: 140 | 141 | The same syntax applies to adding the `aigverse` package with adapters as a dependency to your own project. 142 | -------------------------------------------------------------------------------- /docs/truth_tables.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Truth Tables 15 | 16 | The aigverse library provides support for working with truth tables, which are a fundamental representation of Boolean functions. The {py:class}`~aigverse.TruthTable` class offers efficient manipulation and analysis of Boolean functions. 17 | 18 | :::{note} 19 | Truth tables provide a complete specification of a Boolean function by listing all possible input combinations and their corresponding outputs. They are particularly useful for small functions where exhaustive enumeration is feasible. 20 | ::: 21 | 22 | ## Creating Truth Tables 23 | 24 | Truth tables can be created in several ways: 25 | 26 | ```{code-cell} ipython3 27 | from aigverse import TruthTable 28 | 29 | # Initialize a truth table with 3 variables (2^3 = 8 entries) 30 | tt = TruthTable(3) 31 | 32 | # Create a truth table from a hex string (representing the Majority function) 33 | tt.create_from_hex_string("e8") 34 | print(f"Truth table from hex string: {tt.to_binary()}") 35 | 36 | # Create a truth table for an AND function 37 | tt_and = TruthTable(2) 38 | tt_and.create_from_hex_string("8") 39 | print(f"AND function: {tt_and.to_binary()}") 40 | 41 | # Create a truth table for an OR function 42 | tt_or = TruthTable(2) 43 | tt_or.create_from_hex_string("e") 44 | print(f"OR function: {tt_or.to_binary()}") 45 | ``` 46 | 47 | ## Basic Manipulation 48 | 49 | Truth tables provide various methods for bit manipulation: 50 | 51 | ```{code-cell} ipython3 52 | # Create a truth table 53 | tt = TruthTable(3) 54 | tt.create_from_hex_string("e8") # Majority function 55 | 56 | # Get individual bits 57 | print(f"Original truth table: {tt.to_binary()}") 58 | print(f"Bit at position 0: {int(tt.get_bit(0))}") 59 | print(f"Bit at position 7: {int(tt.get_bit(7))}") 60 | 61 | # Flip bits 62 | tt.flip_bit(0) 63 | tt.flip_bit(7) 64 | print(f"After flipping bits 0 and 7: {tt.to_binary()}") 65 | 66 | # Clear the truth table 67 | tt.clear() 68 | print(f"After clearing: {tt.to_binary()}") 69 | 70 | # Check if constant 71 | print(f"Is constant 0? {tt.is_const0()}") 72 | ``` 73 | 74 | ## Truth Table Properties 75 | 76 | You can analyze various properties of truth tables: 77 | 78 | ```{code-cell} ipython3 79 | # Create a truth table for XOR 80 | tt_xor = TruthTable(2) 81 | tt_xor.create_from_hex_string("6") # XOR function 82 | print(f"XOR function: {tt_xor.to_binary()}") 83 | 84 | # Get number of variables and bits 85 | print(f"Number of variables: {tt_xor.num_vars()}") 86 | print(f"Number of bits: {tt_xor.num_bits()}") 87 | 88 | # Check if the function is balanced (equal number of 0s and 1s) 89 | num_ones = sum(int(tt_xor.get_bit(i)) for i in range(tt_xor.num_bits())) 90 | is_balanced = num_ones == tt_xor.num_bits() // 2 91 | print(f"Is balanced? {is_balanced}") 92 | ``` 93 | 94 | ## Truth Table Simulation 95 | 96 | The simulation of AIGs and other logic networks using truth tables is covered in the [Simulation section](algorithms.md#simulation) of the Algorithms documentation. This approach allows you to obtain the truth tables for outputs and internal nodes of a logic network. 97 | 98 | ## `pickle` Support 99 | 100 | Truth tables support Python's `pickle` protocol, allowing you to serialize and deserialize them for persistent storage or use in data science workflows. 101 | 102 | ```{code-cell} ipython3 103 | import pickle 104 | 105 | # Create a truth table 106 | tt = TruthTable(3) 107 | tt.create_from_hex_string("d8") # ITE function 108 | 109 | # Pickle the truth table 110 | with open("tt.pkl", "wb") as f: 111 | pickle.dump(tt, f) 112 | 113 | # Unpickle the truth table 114 | with open("tt.pkl", "rb") as f: 115 | unpickled_tt = pickle.load(f) 116 | 117 | # Verify that the unpickled object is identical 118 | print(f"Original: {tt.to_binary()}") 119 | print(f"Unpickled: {unpickled_tt.to_binary()}") 120 | print(f"Equivalent: {tt == unpickled_tt}") 121 | ``` 122 | 123 | You can also pickle multiple truth tables at once by storing them in a list or tuple. 124 | -------------------------------------------------------------------------------- /.github/workflows/aigverse-pypi-deployment.yml: -------------------------------------------------------------------------------- 1 | # this file is heavily based on https://github.com/cda-tum/mqt-workflows/blob/main/.github/workflows/reusable-python-packaging.yml 2 | 3 | name: 🐍 • Packaging 4 | 5 | on: 6 | release: 7 | types: [published] 8 | merge_group: 9 | push: 10 | branches: ["main"] 11 | paths: 12 | - "src/aigverse/**" 13 | - "test/**" 14 | - "**/*.py" 15 | - "**/*.hpp" 16 | - "**/*.cpp" 17 | - "**/*.cmake" 18 | - "**/CMakeLists.txt" 19 | - "libs/**" 20 | - "pyproject.toml" 21 | - "uv.lock" 22 | - ".github/workflows/aigverse-pypi-deployment.yml" 23 | pull_request: 24 | branches: ["main"] 25 | paths: 26 | - "src/aigverse/**" 27 | - "test/**" 28 | - "**/*.py" 29 | - "**/*.hpp" 30 | - "**/*.cpp" 31 | - "**/*.cmake" 32 | - "**/CMakeLists.txt" 33 | - "libs/**" 34 | - "pyproject.toml" 35 | - "uv.lock" 36 | - ".github/workflows/aigverse-pypi-deployment.yml" 37 | workflow_dispatch: 38 | 39 | permissions: 40 | attestations: write 41 | contents: read 42 | id-token: write 43 | 44 | concurrency: 45 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 46 | cancel-in-progress: true 47 | 48 | jobs: 49 | build_sdist: 50 | name: 📦 Build Source Distribution 51 | runs-on: ubuntu-latest 52 | steps: 53 | - name: Clone Repository 54 | uses: actions/checkout@v6 55 | with: 56 | submodules: recursive 57 | fetch-depth: 0 58 | 59 | - name: Install the latest version of uv 60 | uses: astral-sh/setup-uv@v7 61 | 62 | - name: Build sdist 63 | run: uv build --sdist 64 | 65 | - name: Check metadata 66 | run: uvx twine check dist/* 67 | 68 | - name: Upload sdist as an artifact 69 | uses: actions/upload-artifact@v6 70 | with: 71 | name: cibw-sdist 72 | path: dist/*.tar.gz 73 | overwrite: true 74 | 75 | build_wheels: 76 | name: 🛞 Wheels for ${{ matrix.runs-on }} 77 | runs-on: ${{ matrix.runs-on }} 78 | 79 | strategy: 80 | fail-fast: false 81 | matrix: 82 | runs-on: [ubuntu-24.04, ubuntu-24.04-arm, macos-15, windows-2025] 83 | 84 | steps: 85 | - uses: actions/checkout@v6 86 | with: 87 | submodules: recursive 88 | fetch-depth: 0 89 | 90 | - name: Set up MSVC development environment (Windows only) 91 | uses: ilammy/msvc-dev-cmd@v1 92 | 93 | - name: Setup ccache 94 | uses: hendrikmuhs/ccache-action@v1.2 95 | with: 96 | key: "${{matrix.runs-on}}-aigverse" 97 | save: true 98 | max-size: 10G 99 | 100 | - if: runner.os == 'Linux' 101 | name: Setup mold 102 | uses: rui314/setup-mold@v1 103 | 104 | - name: Install the latest version of uv 105 | uses: astral-sh/setup-uv@v7 106 | 107 | - name: Build wheels 108 | uses: pypa/cibuildwheel@v3.3 109 | 110 | - name: Upload wheel as an artifact 111 | uses: actions/upload-artifact@v6 112 | with: 113 | name: cibw-wheels-${{ matrix.runs-on }}-${{ strategy.job-index }} 114 | path: wheelhouse/*.whl 115 | overwrite: true 116 | 117 | publish_to_pypi: 118 | needs: [build_sdist, build_wheels] 119 | runs-on: ubuntu-latest 120 | name: 🚀 Publish to PyPI 121 | if: github.event_name == 'release' && github.event.action == 'published' 122 | steps: 123 | - name: Download the previously stored artifacts 124 | uses: actions/download-artifact@v7 125 | with: 126 | pattern: cibw-* 127 | path: dist 128 | merge-multiple: true 129 | 130 | - name: Generate artifact attestation for sdist and wheel(s) 131 | uses: actions/attest-build-provenance@v3.0.0 132 | with: 133 | subject-path: "dist/*" 134 | 135 | - name: Deploy to PyPI 136 | uses: pypa/gh-action-pypi-publish@release/v1 137 | with: 138 | password: ${{ secrets.PYPI_DEPLOY_TOKEN }} 139 | skip-existing: true 140 | verbose: true 141 | attestations: true 142 | -------------------------------------------------------------------------------- /src/aigverse/adapters/edge_list.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 04.09.25. 3 | // 4 | 5 | #include "aigverse/adapters/edge_list.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <fmt/ranges.h> 11 | #include <mockturtle/traits.hpp> 12 | #include <pybind11/pybind11.h> 13 | #include <pybind11/stl.h> 14 | 15 | #include <string> 16 | 17 | namespace aigverse 18 | { 19 | 20 | namespace detail 21 | { 22 | 23 | template <typename Ntk> 24 | void ntk_edge_list(pybind11::module_& m, const std::string& network_name) 25 | { 26 | namespace py = pybind11; 27 | using namespace pybind11::literals; 28 | 29 | /** 30 | * Edge. 31 | */ 32 | using Edge = edge<Ntk>; 33 | py::class_<Edge>(m, fmt::format("{}Edge", network_name).c_str()) 34 | .def(py::init<>()) 35 | .def(py::init<const mockturtle::node<Ntk>&, const mockturtle::node<Ntk>&, const int64_t>(), "source"_a, 36 | "target"_a, "weight"_a = 0) 37 | .def_readwrite("source", &Edge::source) 38 | .def_readwrite("target", &Edge::target) 39 | .def_readwrite("weight", &Edge::weight) 40 | .def("__repr__", [](const Edge& e) { return fmt::format("{}", e); }) 41 | .def("__eq__", 42 | [](const Edge& self, const py::object& other) -> bool 43 | { 44 | if (!py::isinstance<Edge>(other)) 45 | { 46 | return false; 47 | } 48 | 49 | return self == other.cast<const Edge>(); 50 | }) 51 | .def("__ne__", 52 | [](const Edge& self, const py::object& other) -> bool 53 | { 54 | if (!py::isinstance<Edge>(other)) 55 | { 56 | return false; 57 | } 58 | 59 | return self != other.cast<const Edge>(); 60 | }) 61 | 62 | ; 63 | 64 | py::implicitly_convertible<py::tuple, Edge>(); 65 | 66 | /** 67 | * Edge list. 68 | */ 69 | using EdgeList = edge_list<Ntk>; 70 | py::class_<EdgeList>(m, fmt::format("{}EdgeList", network_name).c_str()) 71 | .def(py::init<>()) 72 | .def(py::init<const Ntk&>(), "ntk"_a) 73 | .def(py::init<const Ntk&, const std::vector<Edge>&>(), "ntk"_a, "edges"_a) 74 | .def_readwrite("ntk", &EdgeList::ntk) 75 | .def_readwrite("edges", &EdgeList::edges) 76 | .def( 77 | "append", [](EdgeList& el, const Edge& e) { el.edges.push_back(e); }, "edge"_a) 78 | .def("clear", [](EdgeList& el) { el.edges.clear(); }) 79 | .def( 80 | "__iter__", [](const EdgeList& el) { return py::make_iterator(el.edges); }, py::keep_alive<0, 1>()) 81 | .def("__len__", [](const EdgeList& el) { return el.edges.size(); }) 82 | .def("__getitem__", 83 | [](const EdgeList& el, const std::size_t index) 84 | { 85 | if (index >= el.edges.size()) 86 | { 87 | throw py::index_error(); 88 | } 89 | 90 | return el.edges[index]; 91 | }) 92 | .def("__setitem__", 93 | [](EdgeList& el, const std::size_t index, const Edge& e) 94 | { 95 | if (index >= el.edges.size()) 96 | { 97 | throw py::index_error(); 98 | } 99 | 100 | el.edges[index] = e; 101 | }) 102 | .def("__repr__", [](const EdgeList& el) { return fmt::format("EdgeList({})", el); }) 103 | 104 | ; 105 | 106 | py::implicitly_convertible<py::list, EdgeList>(); 107 | 108 | m.def("to_edge_list", &to_edge_list<mockturtle::sequential<Ntk>>, "ntk"_a, "regular_weight"_a = 0, 109 | "inverted_weight"_a = 1); 110 | m.def("to_edge_list", &to_edge_list<Ntk>, "ntk"_a, "regular_weight"_a = 0, "inverted_weight"_a = 1); 111 | } 112 | 113 | // Explicit instantiation for AIG 114 | template void ntk_edge_list<aigverse::aig>(pybind11::module_& m, const std::string& network_name); 115 | 116 | } // namespace detail 117 | 118 | void bind_to_edge_list(pybind11::module_& m) 119 | { 120 | detail::ntk_edge_list<aigverse::aig>(m, "Aig"); 121 | } 122 | 123 | } // namespace aigverse 124 | -------------------------------------------------------------------------------- /test/algorithms/test_simulation.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import Aig, DepthAig, TruthTable, simulate, simulate_nodes 4 | 5 | 6 | def test_empty_aig() -> None: 7 | aig = Aig() 8 | 9 | tt = simulate(aig) 10 | 11 | assert len(tt) == 0 12 | 13 | n_map = simulate_nodes(aig) 14 | 15 | assert len(n_map) == 1 16 | assert n_map[0].is_const0() 17 | 18 | 19 | def test_const0_aig() -> None: 20 | aig = Aig() 21 | 22 | aig.create_po(aig.make_signal(0)) 23 | 24 | sim = simulate(aig) 25 | 26 | assert len(sim) == 1 27 | assert sim[0].is_const0() 28 | 29 | n_map = simulate_nodes(aig) 30 | 31 | assert len(n_map) == 1 32 | assert n_map[0].is_const0() 33 | 34 | 35 | def test_const1_aig() -> None: 36 | aig = Aig() 37 | 38 | aig.create_po(~aig.make_signal(0)) 39 | 40 | sim = simulate(aig) 41 | 42 | assert len(sim) == 1 43 | assert sim[0].is_const1() 44 | 45 | n_map = simulate_nodes(aig) 46 | 47 | assert len(n_map) == 1 48 | assert n_map[0].is_const0() # node tt is still const0 49 | 50 | 51 | def test_and_aig() -> None: 52 | aig = Aig() 53 | 54 | a = aig.create_pi() 55 | b = aig.create_pi() 56 | 57 | and1 = aig.create_and(a, b) 58 | 59 | aig.create_po(and1) 60 | 61 | sim = simulate(aig) 62 | 63 | conjunction = TruthTable(2) 64 | conjunction.create_from_binary_string("1000") 65 | 66 | assert len(sim) == 1 67 | assert sim[0] == conjunction 68 | 69 | n_map = simulate_nodes(aig) 70 | 71 | id_tt_a = TruthTable(2) 72 | id_tt_a.create_from_binary_string("1010") 73 | id_tt_b = TruthTable(2) 74 | id_tt_b.create_from_binary_string("1100") 75 | 76 | assert len(n_map) == 4 77 | assert n_map[0].is_const0() 78 | assert n_map[1] == id_tt_a 79 | assert n_map[2] == id_tt_b 80 | assert n_map[3] == conjunction 81 | 82 | 83 | def test_or_aig() -> None: 84 | aig = Aig() 85 | 86 | a = aig.create_pi() 87 | b = aig.create_pi() 88 | 89 | or1 = aig.create_or(a, b) 90 | 91 | aig.create_po(or1) 92 | 93 | sim = simulate(aig) 94 | 95 | disjunction = TruthTable(2) 96 | disjunction.create_from_binary_string("1110") 97 | 98 | assert len(sim) == 1 99 | assert sim[0] == disjunction 100 | 101 | n_map = simulate_nodes(aig) 102 | 103 | id_tt_a = TruthTable(2) 104 | id_tt_a.create_nth_var(0) 105 | id_tt_b = TruthTable(2) 106 | id_tt_b.create_nth_var(1) 107 | 108 | assert len(n_map) == 4 109 | assert n_map[0].is_const0() 110 | assert n_map[1] == id_tt_a 111 | assert n_map[2] == id_tt_b 112 | # we're expecting a disjunction at the PO but the last node is a negated disjunction (NAND) 113 | # because the PO's inverted signal is not taken into account in the node simulation 114 | assert n_map[3] == ~disjunction 115 | 116 | 117 | def test_maj_aig() -> None: 118 | aig = Aig() 119 | 120 | a = aig.create_pi() 121 | b = aig.create_pi() 122 | c = aig.create_pi() 123 | 124 | maj1 = aig.create_maj(a, b, c) 125 | 126 | aig.create_po(maj1) 127 | 128 | sim = simulate(aig) 129 | 130 | majority = TruthTable(3) 131 | majority.create_majority() 132 | 133 | print(f"MAJ: {majority.to_binary()}") 134 | 135 | assert len(sim) == 1 136 | assert sim[0] == majority 137 | 138 | n_map = simulate_nodes(aig) 139 | 140 | id_tt_a = TruthTable(3) 141 | id_tt_a.create_from_binary_string("10101010") 142 | id_tt_b = TruthTable(3) 143 | id_tt_b.create_from_binary_string("11001100") 144 | id_tt_c = TruthTable(3) 145 | id_tt_c.create_from_binary_string("11110000") 146 | 147 | for i in range(len(n_map)): 148 | print(f"Node {i}: {n_map[i].to_binary()}") 149 | 150 | assert len(n_map) == 8 151 | assert n_map[0].is_const0() 152 | assert n_map[1] == id_tt_a 153 | assert n_map[2] == id_tt_b 154 | assert n_map[3] == id_tt_c 155 | # we're expecting a negated MAJ at the node because the PO's inverted signal is not taken into account 156 | assert n_map[7] == ~majority 157 | 158 | 159 | def test_multi_output_aig() -> None: 160 | # also test DepthAig 161 | for ntk in [Aig, DepthAig]: 162 | aig = ntk() 163 | 164 | a = aig.create_pi() 165 | b = aig.create_pi() 166 | 167 | and1 = aig.create_and(a, b) 168 | or1 = aig.create_or(a, b) 169 | 170 | aig.create_po(and1) 171 | aig.create_po(or1) 172 | 173 | sim = simulate(aig) 174 | 175 | conjunction = TruthTable(2) 176 | conjunction.create_from_binary_string("1000") 177 | 178 | disjunction = TruthTable(2) 179 | disjunction.create_from_binary_string("1110") 180 | 181 | assert len(sim) == 2 182 | assert sim[0] == conjunction 183 | assert sim[1] == disjunction 184 | -------------------------------------------------------------------------------- /cmake/ProjectOptions.cmake: -------------------------------------------------------------------------------- 1 | include(CMakeDependentOption) 2 | include(CheckCXXCompilerFlag) 3 | include(FetchContent) 4 | 5 | macro(aigverse_supports_sanitizers) 6 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES 7 | ".*GNU.*") AND NOT WIN32) 8 | set(SUPPORTS_UBSAN ON) 9 | else() 10 | set(SUPPORTS_UBSAN OFF) 11 | endif() 12 | 13 | if((CMAKE_CXX_COMPILER_ID MATCHES ".*Clang.*" OR CMAKE_CXX_COMPILER_ID MATCHES 14 | ".*GNU.*") AND WIN32) 15 | set(SUPPORTS_ASAN OFF) 16 | else() 17 | set(SUPPORTS_ASAN ON) 18 | endif() 19 | endmacro() 20 | 21 | macro(aigverse_setup_options) 22 | option(AIGVERSE_ENABLE_HARDENING "Enable hardening" OFF) 23 | option(AIGVERSE_ENABLE_COVERAGE "Enable coverage reporting" OFF) 24 | cmake_dependent_option( 25 | AIGVERSE_ENABLE_GLOBAL_HARDENING 26 | "Attempt to push hardening options to built dependencies" ON 27 | AIGVERSE_ENABLE_HARDENING OFF) 28 | 29 | option(AIGVERSE_ENABLE_IPO "Enable IPO/LTO" OFF) 30 | option(AIGVERSE_WARNINGS_AS_ERRORS "Treat Warnings As Errors" OFF) 31 | option(AIGVERSE_ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" OFF) 32 | option(AIGVERSE_ENABLE_SANITIZER_LEAK "Enable leak sanitizer" OFF) 33 | option(AIGVERSE_ENABLE_SANITIZER_UNDEFINED "Enable undefined sanitizer" OFF) 34 | option(AIGVERSE_ENABLE_SANITIZER_THREAD "Enable thread sanitizer" OFF) 35 | option(AIGVERSE_ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" OFF) 36 | option(AIGVERSE_ENABLE_UNITY_BUILD "Enable unity builds" OFF) 37 | option(AIGVERSE_ENABLE_PCH "Enable precompiled headers" OFF) 38 | option(AIGVERSE_ENABLE_CACHE "Enable ccache" ON) 39 | 40 | if(NOT PROJECT_IS_TOP_LEVEL) 41 | mark_as_advanced( 42 | AIGVERSE_ENABLE_IPO 43 | AIGVERSE_WARNINGS_AS_ERRORS 44 | AIGVERSE_ENABLE_SANITIZER_ADDRESS 45 | AIGVERSE_ENABLE_SANITIZER_LEAK 46 | AIGVERSE_ENABLE_SANITIZER_UNDEFINED 47 | AIGVERSE_ENABLE_SANITIZER_THREAD 48 | AIGVERSE_ENABLE_SANITIZER_MEMORY 49 | AIGVERSE_ENABLE_UNITY_BUILD 50 | AIGVERSE_ENABLE_COVERAGE 51 | AIGVERSE_ENABLE_PCH 52 | AIGVERSE_ENABLE_CACHE) 53 | endif() 54 | 55 | endmacro() 56 | 57 | macro(aigverse_global_options) 58 | if(AIGVERSE_ENABLE_IPO) 59 | include(cmake/InterproceduralOptimization.cmake) 60 | aigverse_enable_ipo() 61 | endif() 62 | 63 | aigverse_supports_sanitizers() 64 | 65 | if(AIGVERSE_ENABLE_HARDENING AND AIGVERSE_ENABLE_GLOBAL_HARDENING) 66 | include(cmake/Hardening.cmake) 67 | if(NOT SUPPORTS_UBSAN 68 | OR AIGVERSE_ENABLE_SANITIZER_UNDEFINED 69 | OR AIGVERSE_ENABLE_SANITIZER_ADDRESS 70 | OR AIGVERSE_ENABLE_SANITIZER_THREAD 71 | OR AIGVERSE_ENABLE_SANITIZER_LEAK) 72 | set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) 73 | else() 74 | set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) 75 | endif() 76 | aigverse_enable_hardening(aigverse_options ON ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 77 | endif() 78 | endmacro() 79 | 80 | macro(aigverse_local_options) 81 | if(PROJECT_IS_TOP_LEVEL) 82 | include(cmake/StandardProjectSettings.cmake) 83 | endif() 84 | 85 | add_library(aigverse_warnings INTERFACE) 86 | add_library(aigverse_options INTERFACE) 87 | 88 | include(cmake/CompilerWarnings.cmake) 89 | aigverse_set_project_warnings(aigverse_warnings ${AIGVERSE_WARNINGS_AS_ERRORS} 90 | "" "" "" "") 91 | 92 | include(cmake/Sanitizers.cmake) 93 | aigverse_enable_sanitizers( 94 | aigverse_options ${AIGVERSE_ENABLE_SANITIZER_ADDRESS} 95 | ${AIGVERSE_ENABLE_SANITIZER_LEAK} ${AIGVERSE_ENABLE_SANITIZER_UNDEFINED} 96 | ${AIGVERSE_ENABLE_SANITIZER_THREAD} ${AIGVERSE_ENABLE_SANITIZER_MEMORY}) 97 | 98 | set_target_properties(aigverse_options 99 | PROPERTIES UNITY_BUILD ${AIGVERSE_ENABLE_UNITY_BUILD}) 100 | 101 | if(AIGVERSE_ENABLE_PCH) 102 | target_precompile_headers(aigverse_options INTERFACE <vector> <string> 103 | <utility>) 104 | endif() 105 | 106 | if(AIGVERSE_ENABLE_CACHE) 107 | include(cmake/Cache.cmake) 108 | aigverse_enable_cache() 109 | endif() 110 | 111 | if(AIGVERSE_ENABLE_COVERAGE) 112 | include(cmake/Coverage.cmake) 113 | aigverse_enable_coverage(aigverse_options) 114 | endif() 115 | 116 | if(AIGVERSE_WARNINGS_AS_ERRORS) 117 | check_cxx_compiler_flag("-Wl,--fatal-warnings" LINKER_FATAL_WARNINGS) 118 | if(LINKER_FATAL_WARNINGS) 119 | # This is not working consistently, so disabling for now 120 | # target_link_options(aigverse_options INTERFACE -Wl,--fatal-warnings) 121 | endif() 122 | endif() 123 | 124 | if(AIGVERSE_ENABLE_HARDENING AND NOT AIGVERSE_ENABLE_GLOBAL_HARDENING) 125 | include(cmake/Hardening.cmake) 126 | if(NOT SUPPORTS_UBSAN 127 | OR AIGVERSE_ENABLE_SANITIZER_UNDEFINED 128 | OR AIGVERSE_ENABLE_SANITIZER_ADDRESS 129 | OR AIGVERSE_ENABLE_SANITIZER_THREAD 130 | OR AIGVERSE_ENABLE_SANITIZER_LEAK) 131 | set(ENABLE_UBSAN_MINIMAL_RUNTIME FALSE) 132 | else() 133 | set(ENABLE_UBSAN_MINIMAL_RUNTIME TRUE) 134 | endif() 135 | aigverse_enable_hardening(aigverse_options OFF 136 | ${ENABLE_UBSAN_MINIMAL_RUNTIME}) 137 | endif() 138 | 139 | endmacro() 140 | -------------------------------------------------------------------------------- /noxfile.py: -------------------------------------------------------------------------------- 1 | """Nox sessions.""" 2 | 3 | from __future__ import annotations 4 | 5 | import argparse 6 | import os 7 | import shutil 8 | import sys 9 | from typing import TYPE_CHECKING 10 | 11 | import nox 12 | 13 | if TYPE_CHECKING: 14 | from collections.abc import Sequence 15 | 16 | nox.needs_version = ">=2024.3.2" 17 | nox.options.default_venv_backend = "uv" 18 | 19 | nox.options.sessions = ["lint", "tests", "minimums"] 20 | 21 | PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"] 22 | 23 | if os.environ.get("CI", None): 24 | nox.options.error_on_missing_interpreters = True 25 | 26 | 27 | @nox.session(reuse_venv=True) 28 | def lint(session: nox.Session) -> None: 29 | """Run the linter.""" 30 | if shutil.which("pre-commit") is None: 31 | session.install("pre-commit") 32 | 33 | session.run("pre-commit", "run", "--all-files", *session.posargs, external=True) 34 | 35 | 36 | def _run_tests( 37 | session: nox.Session, 38 | *, 39 | install_args: Sequence[str] = (), 40 | extra_command: Sequence[str] = (), 41 | pytest_run_args: Sequence[str] = (), 42 | ) -> None: 43 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 44 | if os.environ.get("CI", None) and sys.platform == "win32": 45 | env["SKBUILD_CMAKE_ARGS"] = "-T ClangCL" 46 | 47 | if shutil.which("cmake") is None and shutil.which("cmake3") is None: 48 | session.install("cmake") 49 | if shutil.which("ninja") is None: 50 | session.install("ninja") 51 | 52 | # install build and test dependencies on top of the existing environment 53 | python_flag = f"--python={session.python}" 54 | session.run( 55 | "uv", 56 | "sync", 57 | "--inexact", 58 | "--only-group", 59 | "build", 60 | "--only-group", 61 | "test", 62 | python_flag, 63 | *install_args, 64 | env=env, 65 | ) 66 | session.run( 67 | "uv", 68 | "sync", 69 | "--inexact", 70 | "--no-dev", # do not auto-install dev dependencies 71 | "--no-build-isolation-package", 72 | "aigverse", # build the project without isolation 73 | python_flag, 74 | *install_args, 75 | env=env, 76 | ) 77 | if extra_command: 78 | session.run(*extra_command, env=env) 79 | session.run( 80 | "uv", 81 | "run", 82 | "--no-sync", # do not sync as everything is already installed 83 | python_flag, 84 | *install_args, 85 | "pytest", 86 | *pytest_run_args, 87 | *session.posargs, 88 | "--cov-config=pyproject.toml", 89 | env=env, 90 | ) 91 | 92 | 93 | @nox.session(reuse_venv=True, python=PYTHON_ALL_VERSIONS) 94 | def tests(session: nox.Session) -> None: 95 | """Run the test suite.""" 96 | _run_tests(session) 97 | 98 | 99 | @nox.session(reuse_venv=True, venv_backend="uv", python=PYTHON_ALL_VERSIONS) 100 | def minimums(session: nox.Session) -> None: 101 | """Test the minimum versions of dependencies.""" 102 | _run_tests( 103 | session, 104 | install_args=["--resolution=lowest-direct"], 105 | pytest_run_args=["-Wdefault"], 106 | ) 107 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 108 | session.run("uv", "tree", "--frozen", env=env) 109 | session.run("uv", "lock", "--refresh", env=env) 110 | 111 | 112 | @nox.session(reuse_venv=True) 113 | def docs(session: nox.Session) -> None: 114 | """Build the docs. Use "--non-interactive" to avoid serving. Pass "-b linkcheck" to check links.""" 115 | # Check for graphviz installation 116 | if shutil.which("dot") is None: 117 | session.error( 118 | "Graphviz is required for building the documentation. " 119 | "Please install it using your package manager. For example:\n" 120 | " - macOS: `brew install graphviz`\n" 121 | " - Ubuntu: `sudo apt install graphviz`\n" 122 | " - Windows: `winget install graphviz` or `choco install graphviz`\n" 123 | ) 124 | 125 | parser = argparse.ArgumentParser() 126 | parser.add_argument("-b", dest="builder", default="html", help="Build target (default: html)") 127 | args, posargs = parser.parse_known_args(session.posargs) 128 | 129 | serve = args.builder == "html" and session.interactive 130 | if serve: 131 | session.install("sphinx-autobuild") 132 | 133 | env = {"UV_PROJECT_ENVIRONMENT": session.virtualenv.location} 134 | # install build and docs dependencies on top of the existing environment 135 | session.run( 136 | "uv", 137 | "sync", 138 | "--inexact", 139 | "--only-group", 140 | "build", 141 | "--only-group", 142 | "docs", 143 | env=env, 144 | ) 145 | 146 | shared_args = [ 147 | "-n", # nitpicky mode 148 | "-T", # full tracebacks 149 | f"-b={args.builder}", 150 | "docs", 151 | f"docs/_build/{args.builder}", 152 | *posargs, 153 | ] 154 | 155 | session.run( 156 | "uv", 157 | "run", 158 | "--no-dev", # do not auto-install dev dependencies 159 | "--no-build-isolation-package", 160 | "aigverse", # build the project without isolation 161 | "sphinx-autobuild" if serve else "sphinx-build", 162 | *shared_args, 163 | env=env, 164 | ) 165 | -------------------------------------------------------------------------------- /test/networks/test_named_aig.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | from aigverse import NamedAig 4 | 5 | 6 | def test_named_aig() -> None: 7 | aig = NamedAig() 8 | 9 | # Check that NamedAig has the specific naming methods 10 | assert hasattr(aig, "set_network_name") 11 | assert hasattr(aig, "get_network_name") 12 | assert hasattr(aig, "has_name") 13 | assert hasattr(aig, "set_name") 14 | assert hasattr(aig, "get_name") 15 | assert hasattr(aig, "has_output_name") 16 | assert hasattr(aig, "set_output_name") 17 | assert hasattr(aig, "get_output_name") 18 | 19 | # Test network name 20 | aig.set_network_name("test_circuit") 21 | assert aig.get_network_name() == "test_circuit" 22 | 23 | # Create primary inputs with names 24 | x1 = aig.create_pi("input1") 25 | x2 = aig.create_pi("input2") 26 | x3 = aig.create_pi("input3") 27 | 28 | # Test that inputs have names 29 | assert aig.has_name(x1) 30 | assert aig.has_name(x2) 31 | assert aig.has_name(x3) 32 | assert aig.get_name(x1) == "input1" 33 | assert aig.get_name(x2) == "input2" 34 | assert aig.get_name(x3) == "input3" 35 | 36 | # Create gates 37 | n4 = aig.create_and(~x1, x2) 38 | n5 = aig.create_and(x1, n4) 39 | n6 = aig.create_and(x3, n5) 40 | 41 | # Test setting names on internal signals 42 | aig.set_name(n4, "and_gate1") 43 | aig.set_name(n5, "and_gate2") 44 | assert aig.has_name(n4) 45 | assert aig.has_name(n5) 46 | assert aig.get_name(n4) == "and_gate1" 47 | assert aig.get_name(n5) == "and_gate2" 48 | 49 | # Create primary outputs with names 50 | po1_idx = aig.create_po(n6, "output1") 51 | po2_idx = aig.create_po(n5, "output2") 52 | 53 | # Test output names 54 | assert aig.has_output_name(po1_idx) 55 | assert aig.has_output_name(po2_idx) 56 | assert aig.get_output_name(po1_idx) == "output1" 57 | assert aig.get_output_name(po2_idx) == "output2" 58 | 59 | # Test setting output names after creation 60 | aig.set_output_name(po1_idx, "renamed_output1") 61 | assert aig.get_output_name(po1_idx) == "renamed_output1" 62 | 63 | 64 | def test_named_aig_without_names() -> None: 65 | """Test that NamedAig still works when names are not provided.""" 66 | aig = NamedAig() 67 | 68 | # Create inputs without names (using default empty string) 69 | x1 = aig.create_pi() 70 | x2 = aig.create_pi() 71 | 72 | # Create gate 73 | n3 = aig.create_and(x1, x2) 74 | 75 | # Create output without name 76 | aig.create_po(n3) 77 | 78 | # Basic functionality should still work 79 | assert aig.num_pis() == 2 80 | assert aig.num_pos() == 1 81 | assert aig.num_gates() == 1 82 | 83 | 84 | def test_named_aig_copy_constructor() -> None: 85 | """Test that NamedAig copy constructor preserves names.""" 86 | aig1 = NamedAig() 87 | aig1.set_network_name("original") 88 | 89 | x1 = aig1.create_pi("a") 90 | x2 = aig1.create_pi("b") 91 | n3 = aig1.create_and(x1, x2) 92 | aig1.set_name(n3, "and_gate") 93 | aig1.create_po(n3, "out") 94 | 95 | # Copy constructor 96 | aig2 = NamedAig(aig1) 97 | 98 | # Check that names are preserved 99 | assert aig2.get_network_name() == "original" 100 | assert aig2.num_pis() == 2 101 | assert aig2.num_pos() == 1 102 | 103 | # Get the signals in the new AIG 104 | x1_copy = aig2.make_signal(aig2.pi_at(0)) 105 | x2_copy = aig2.make_signal(aig2.pi_at(1)) 106 | 107 | assert aig2.has_name(x1_copy) 108 | assert aig2.has_name(x2_copy) 109 | assert aig2.get_name(x1_copy) == "a" 110 | assert aig2.get_name(x2_copy) == "b" 111 | assert aig2.has_output_name(0) 112 | assert aig2.get_output_name(0) == "out" 113 | 114 | 115 | def test_named_aig_complex_circuit() -> None: 116 | """Test a more complex circuit with multiple levels and names.""" 117 | aig = NamedAig() 118 | aig.set_network_name("full_adder") 119 | 120 | # Create inputs 121 | a = aig.create_pi("a") 122 | b = aig.create_pi("b") 123 | cin = aig.create_pi("cin") 124 | 125 | # Build full adder logic 126 | # with sum = a ^ b ^ cin 127 | # and cout = (a & b) | (cin & (a ^ b)) 128 | 129 | a_xor_b = aig.create_xor(a, b) 130 | aig.set_name(a_xor_b, "a_xor_b") 131 | 132 | sum_out = aig.create_xor(a_xor_b, cin) 133 | aig.set_name(sum_out, "sum") 134 | 135 | a_and_b = aig.create_and(a, b) 136 | aig.set_name(a_and_b, "a_and_b") 137 | 138 | cin_and_xor = aig.create_and(cin, a_xor_b) 139 | aig.set_name(cin_and_xor, "cin_and_xor") 140 | 141 | cout = aig.create_or(a_and_b, cin_and_xor) 142 | aig.set_name(cout, "cout") 143 | 144 | # Create outputs 145 | sum_idx = aig.create_po(sum_out, "sum") 146 | cout_idx = aig.create_po(cout, "carry_out") 147 | 148 | # Verify structure 149 | assert aig.num_pis() == 3 150 | assert aig.num_pos() == 2 151 | assert aig.get_network_name() == "full_adder" 152 | 153 | # Verify all names 154 | assert aig.get_name(a) == "a" 155 | assert aig.get_name(b) == "b" 156 | assert aig.get_name(cin) == "cin" 157 | assert aig.get_name(a_xor_b) == "a_xor_b" 158 | assert aig.get_name(sum_out) == "sum" 159 | assert aig.get_name(a_and_b) == "a_and_b" 160 | assert aig.get_name(cin_and_xor) == "cin_and_xor" 161 | assert aig.get_name(cout) == "cout" 162 | assert aig.get_output_name(sum_idx) == "sum" 163 | assert aig.get_output_name(cout_idx) == "carry_out" 164 | -------------------------------------------------------------------------------- /src/aigverse/adapters/index_list.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 04.09.25. 3 | // 4 | 5 | #include "aigverse/adapters/index_list.hpp" 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <fmt/ranges.h> 11 | #include <mockturtle/utils/index_list.hpp> 12 | #include <pybind11/pybind11.h> 13 | #include <pybind11/stl.h> 14 | 15 | #include <algorithm> 16 | #include <cctype> 17 | #include <string> 18 | #include <tuple> 19 | #include <type_traits> 20 | #include <vector> 21 | 22 | namespace aigverse 23 | { 24 | 25 | namespace detail 26 | { 27 | 28 | template <typename Ntk> 29 | void ntk_index_list(pybind11::module_& m, const std::string& network_name) 30 | { 31 | namespace py = pybind11; 32 | using namespace pybind11::literals; 33 | 34 | if constexpr (std::is_same_v<Ntk, aigverse::aig>) 35 | { 36 | /** 37 | * Index list. 38 | */ 39 | using IndexList = aigverse::aig_index_list; 40 | py::class_<IndexList>(m, fmt::format("{}IndexList", network_name).c_str()) 41 | .def(py::init<const uint32_t>(), "num_pis"_a = 0) 42 | .def(py::init<const std::vector<uint32_t>&>(), "values"_a) 43 | 44 | .def("raw", &IndexList::raw) 45 | 46 | .def("size", &IndexList::size) 47 | .def("num_gates", &IndexList::num_gates) 48 | .def("num_pis", &IndexList::num_pis) 49 | .def("num_pos", &IndexList::num_pos) 50 | 51 | .def("add_inputs", &IndexList::add_inputs, "n"_a = 1u) 52 | .def("add_and", &IndexList::add_and, "lit0"_a, "lit1"_a) 53 | .def("add_xor", &IndexList::add_xor, "lit0"_a, "lit1"_a) 54 | .def("add_output", &IndexList::add_output, "lit"_a) 55 | 56 | .def("clear", &IndexList::clear) 57 | 58 | .def("gates", 59 | [](const IndexList& il) 60 | { 61 | std::vector<std::tuple<uint32_t, uint32_t>> gates{}; 62 | gates.reserve(il.num_gates()); 63 | 64 | il.foreach_gate([&gates](const auto& lit0, const auto& lit1) { gates.emplace_back(lit0, lit1); }); 65 | 66 | return gates; 67 | }) 68 | 69 | .def("pos", 70 | [](const IndexList& il) 71 | { 72 | std::vector<uint32_t> pos{}; 73 | pos.reserve(il.num_pos()); 74 | 75 | il.foreach_po([&pos](const auto& lit) { pos.push_back(lit); }); 76 | 77 | return pos; 78 | }) 79 | 80 | .def("__iter__", 81 | [](const IndexList& il) 82 | { 83 | const auto raw = il.raw(); 84 | const py::list raw_list = py::cast(raw); 85 | return py::iter(raw_list); 86 | }) 87 | .def("__getitem__", 88 | [](const IndexList& il, const std::size_t i) 89 | { 90 | const auto& v = il.raw(); 91 | if (i >= v.size()) 92 | { 93 | throw py::index_error("index out of range"); 94 | } 95 | return v[i]; 96 | }) 97 | .def("__setitem__", 98 | [](IndexList& il, const std::size_t i, const uint32_t value) 99 | { 100 | auto v = il.raw(); 101 | if (i >= v.size()) 102 | { 103 | throw py::index_error("index out of range"); 104 | } 105 | v[i] = value; 106 | il = IndexList(v); // reconstruct the index list with the new vector 107 | }) 108 | 109 | .def("__len__", [](const IndexList& il) { return il.size(); }) 110 | 111 | .def("__repr__", [](const IndexList& il) { return fmt::format("IndexList({})", il); }) 112 | .def("__str__", [](const IndexList& il) { return mockturtle::to_index_list_string(il); }) 113 | 114 | ; 115 | 116 | py::implicitly_convertible<py::list, IndexList>(); 117 | 118 | m.def( 119 | "to_index_list", 120 | [](const Ntk& ntk) 121 | { 122 | IndexList il{}; 123 | mockturtle::encode(il, ntk); 124 | return il; 125 | }, 126 | "ntk"_a, py::return_value_policy::move); 127 | 128 | auto lower_case_network_name = network_name; 129 | std::transform(lower_case_network_name.begin(), lower_case_network_name.end(), lower_case_network_name.begin(), 130 | [](const auto c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); }); 131 | 132 | m.def( 133 | fmt::format("to_{}", lower_case_network_name).c_str(), 134 | [](const IndexList& il) 135 | { 136 | Ntk ntk{}; 137 | mockturtle::decode(ntk, il); 138 | return ntk; 139 | }, 140 | "il"_a, py::return_value_policy::move); 141 | } 142 | } 143 | 144 | // Explicit instantiation for AIG 145 | template void ntk_index_list<aigverse::aig>(pybind11::module_& m, const std::string& network_name); 146 | 147 | } // namespace detail 148 | 149 | void bind_to_index_list(pybind11::module_& m) 150 | { 151 | detail::ntk_index_list<aigverse::aig>(m, "Aig"); 152 | } 153 | 154 | } // namespace aigverse 155 | -------------------------------------------------------------------------------- /test/inout/test_read_aiger.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | from pathlib import Path 5 | 6 | import pytest 7 | 8 | from aigverse import ( 9 | AigSignal, 10 | read_aiger_into_aig, 11 | read_aiger_into_sequential_aig, 12 | read_ascii_aiger_into_aig, 13 | read_ascii_aiger_into_sequential_aig, 14 | ) 15 | 16 | dir_path = Path(os.path.realpath(__file__)).parent 17 | 18 | 19 | def test_read_aiger_into_aig(): 20 | aig = read_aiger_into_aig(str(dir_path / "../resources/mux21.aig")) 21 | 22 | assert aig.size() == 7 23 | assert aig.nodes() == list(range(7)) 24 | assert aig.num_gates() == 3 25 | assert aig.gates() == [4, 5, 6] 26 | assert aig.pis() == [1, 2, 3] 27 | assert aig.is_and(4) 28 | assert aig.is_and(5) 29 | assert aig.is_and(6) 30 | assert aig.is_constant(0) 31 | assert aig.num_pis() == 3 32 | assert aig.is_pi(2) 33 | assert aig.is_pi(3) 34 | assert aig.num_pos() == 1 35 | assert aig.fanins(0) == [] 36 | assert aig.fanins(1) == [] 37 | assert aig.fanins(2) == [] 38 | assert aig.fanins(3) == [] 39 | assert aig.fanins(5) == [AigSignal(2, False), AigSignal(3, False)] 40 | assert aig.fanins(6) == [AigSignal(4, True), AigSignal(5, True)] 41 | 42 | with pytest.raises(RuntimeError): 43 | read_aiger_into_aig(str(dir_path / "mux41.aig")) 44 | 45 | 46 | def test_read_ascii_aiger_into_aig(): 47 | aig = read_ascii_aiger_into_aig(str(dir_path / "../resources/or.aag")) 48 | 49 | assert aig.size() == 4 50 | assert aig.nodes() == list(range(4)) 51 | assert aig.num_gates() == 1 52 | assert aig.gates() == [3] 53 | assert aig.pis() == [1, 2] 54 | 55 | with pytest.raises(RuntimeError): 56 | read_ascii_aiger_into_aig(str(dir_path / "and.aag")) 57 | 58 | 59 | def test_read_aiger_with_names(): 60 | """Test that AIGER files with names are properly read into NamedAig.""" 61 | aig = read_ascii_aiger_into_aig(str(dir_path / "../resources/and_with_names.aag")) 62 | 63 | # Test basic network properties 64 | assert aig.num_pis() == 2 65 | assert aig.num_pos() == 1 66 | assert aig.num_gates() == 1 67 | 68 | # Test PI names (i0 input_a, i1 input_b) 69 | pi_nodes = aig.pis() 70 | assert len(pi_nodes) == 2 71 | assert aig.has_name(AigSignal(pi_nodes[0], False)) 72 | assert aig.get_name(AigSignal(pi_nodes[0], False)) == "input_a" 73 | assert aig.has_name(AigSignal(pi_nodes[1], False)) 74 | assert aig.get_name(AigSignal(pi_nodes[1], False)) == "input_b" 75 | 76 | # Test PO names (o0 output_and) 77 | assert aig.has_output_name(0) 78 | assert aig.get_output_name(0) == "output_and" 79 | 80 | 81 | def test_read_aiger_into_sequential_aig(): 82 | saig = read_aiger_into_sequential_aig(str(dir_path / "../resources/mux21.aig")) 83 | 84 | assert saig.size() == 7 85 | assert saig.nodes() == list(range(7)) 86 | assert saig.num_gates() == 3 87 | assert saig.gates() == [4, 5, 6] 88 | assert saig.pis() == [1, 2, 3] 89 | assert saig.is_and(4) 90 | assert saig.is_and(5) 91 | assert saig.is_and(6) 92 | assert saig.is_constant(0) 93 | assert saig.num_pis() == 3 94 | assert saig.is_pi(2) 95 | assert saig.is_pi(3) 96 | assert saig.num_pos() == 1 97 | assert saig.fanins(0) == [] 98 | assert saig.fanins(1) == [] 99 | assert saig.fanins(2) == [] 100 | assert saig.fanins(3) == [] 101 | assert saig.fanins(5) == [AigSignal(2, False), AigSignal(3, False)] 102 | assert saig.fanins(6) == [AigSignal(4, True), AigSignal(5, True)] 103 | 104 | with pytest.raises(RuntimeError): 105 | read_aiger_into_sequential_aig(str(dir_path / "mux41.aig")) 106 | 107 | 108 | def test_read_ascii_aiger_into_sequential_aig(): 109 | saig = read_ascii_aiger_into_sequential_aig(str(dir_path / "../resources/or.aag")) 110 | 111 | assert saig.size() == 4 112 | assert saig.nodes() == list(range(4)) 113 | assert saig.num_gates() == 1 114 | assert saig.gates() == [3] 115 | assert saig.pis() == [1, 2] 116 | 117 | with pytest.raises(RuntimeError): 118 | read_ascii_aiger_into_sequential_aig(str(dir_path / "and.aag")) 119 | 120 | 121 | def test_read_sequential_aiger(): 122 | """Test reading a sequential AIGER file with registers.""" 123 | saig = read_ascii_aiger_into_sequential_aig(str(dir_path / "../resources/seq.aag")) 124 | 125 | # Test basic network properties 126 | assert saig.size() == 8 # 0 (constant), 2 PIs, 1 RO, 4 gates 127 | assert saig.num_pis() == 2 128 | assert saig.num_pos() == 2 129 | assert saig.num_gates() == 4 130 | 131 | # Test combinational I/O counts 132 | assert saig.num_cis() == 3 # 2 PIs + 1 RO 133 | assert saig.num_cos() == 3 # 2 POs + 1 RI 134 | 135 | # Test register counts 136 | assert len(saig.ros()) == 1 # 1 register output 137 | assert len(saig.ris()) == 1 # 1 register input 138 | assert saig.num_registers() == 1 139 | 140 | # Get register signals 141 | ro_nodes = saig.ros() 142 | assert len(ro_nodes) == 1 143 | ro_node = ro_nodes[0] 144 | 145 | ri_signals = saig.ris() 146 | assert len(ri_signals) == 1 147 | ri_signal = ri_signals[0] 148 | 149 | # Test register iteration - returns pairs of (ri_signal, ro_node) 150 | registers = saig.registers() 151 | assert len(registers) == 1 152 | ri, ro = registers[0] 153 | 154 | # The ri_signal should match what we got from ris() 155 | assert ri == ri_signal 156 | 157 | # The ro node should match what we got from ros() 158 | assert ro == ro_node 159 | 160 | # Test register access methods 161 | assert saig.ro_at(0) == ro_node 162 | assert saig.ri_at(0) == ri_signal 163 | assert saig.ri_to_ro(ri_signal) == ro_node 164 | -------------------------------------------------------------------------------- /docs/machine_learning.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Machine Learning Integration 15 | 16 | The field of logic synthesis is turning to data science and machine learning to tackle its most complex optimization 17 | challenges. Traditional heuristic methods are being augmented, and in some cases replaced, by ML models that can predict 18 | circuit properties, guide optimization steps, or uncover novel logic structures. This emerging paradigm hinges on the 19 | ability to seamlessly integrate with the rich ecosystems of data science. By converting AIGs into formats such as graphs 20 | and numerical arrays, you can unlock the powerful analytical and predictive capabilities of modern machine learning 21 | workflows. 22 | 23 | ## Adapters 24 | 25 | Adapters provide integration with machine learning workflows. To keep the base library lightweight, these adapters are 26 | not included by default in the `aigverse` package but can be installed separately via the `adapters` extra. See 27 | the [Installation](installation.md#machine-learning-adapters) documentation for more details. 28 | 29 | ### NetworkX 30 | 31 | The [NetworkX](https://networkx.org/) adapter allows you to convert an AIG into a {py:class}`~networkx.DiGraph` object. 32 | This enables you to leverage the rich ecosystem of graph-based machine learning and data science tools that operate on 33 | NetworkX graphs. Once converted, you can easily extract node and edge features, visualize the structure (e.g., with 34 | [Matplotlib](https://matplotlib.org/)), or use it as input to graph ML models. 35 | 36 | ```{code-cell} ipython3 37 | import matplotlib.pyplot as plt 38 | import networkx as nx 39 | from networkx.drawing.nx_agraph import graphviz_layout 40 | import numpy as np 41 | 42 | from aigverse import Aig 43 | import aigverse.adapters 44 | 45 | # Create a sample AIG 46 | aig = Aig() 47 | a = aig.create_pi() 48 | b = aig.create_pi() 49 | c = aig.create_pi() 50 | f1 = aig.create_and(a, b) 51 | f2 = aig.create_or(f1, c) 52 | aig.create_po(f2) 53 | 54 | # Convert the AIG to a NetworkX graph 55 | G = aig.to_networkx(levels=True, fanouts=True, node_tts=True, dtype=np.int32) 56 | 57 | # Generate the initial layout using Graphviz's 'dot' program 58 | pos = graphviz_layout(G, prog="dot") 59 | 60 | # Invert the y-axis to flip the layout upside down 61 | # This places the primary inputs (level 0) at the bottom 62 | for node, position in pos.items(): 63 | pos[node] = (position[0], -position[1]) 64 | 65 | # Prepare the labels for nodes and edges from graph attributes 66 | node_labels = { 67 | node: f"Level: {data['level']}\nFanouts: {data['fanouts']}\nType: {data['type']}\nFunction: {data['function']}" 68 | for node, data in G.nodes(data=True) 69 | } 70 | edge_labels = {(u, v): data["type"] for u, v, data in G.edges(data=True)} 71 | 72 | # Plot the graph 73 | plt.figure(figsize=(12, 8)) 74 | plt.title("AIG with Attribute Labels") 75 | 76 | # Draw the graph structure (just the edges and arrows) 77 | nx.draw_networkx_nodes(G, pos, node_size=0) 78 | 79 | # Draw the node labels with a bounding box 80 | nx.draw_networkx_labels( 81 | G, 82 | pos, 83 | labels=node_labels, 84 | font_size=10, 85 | bbox={"facecolor": "lightblue", "edgecolor": "black", "boxstyle": "round,pad=0.5"}, 86 | ) 87 | 88 | # Draw the network edges 89 | nx.draw_networkx_edges( 90 | G, 91 | pos, 92 | node_size=5000, 93 | arrows=True, 94 | arrowstyle="->", 95 | arrowsize=20, 96 | ) 97 | 98 | # Draw the edge labels to show edge attributes 99 | nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_color="red", font_size=8) 100 | 101 | # Pad and show the plot 102 | plt.margins(x=0.2) 103 | plt.show() 104 | ``` 105 | 106 | ## Truth Tables 107 | 108 | Truth tables can be easily converted to Python lists or [NumPy](https://numpy.org/) arrays, making them compatible with 109 | standard ML libraries such as [scikit-learn](https://scikit-learn.org/), [PyTorch](https://pytorch.org/), or 110 | [TensorFlow](https://www.tensorflow.org/). Since `TruthTable` objects are iterable, this conversion is direct and 111 | intuitive. You can use these arrays as labels or features in supervised learning tasks, or as part of a dataset for 112 | training and evaluating models. 113 | 114 | ```{code-cell} ipython3 115 | from aigverse import TruthTable 116 | import numpy as np 117 | 118 | # Create a simple truth table, e.g., a 3-input majority function 119 | tt = TruthTable(3) 120 | tt.create_from_hex_string("e8") 121 | 122 | # Export to a list 123 | tt_list = list(tt) 124 | print(f"As list: {tt_list}") 125 | 126 | # Export to NumPy arrays of different types 127 | tt_np_bool = np.array(tt) 128 | print(f"As NumPy bool array: {tt_np_bool}") 129 | tt_np_int = np.array(tt, dtype=np.int32) 130 | print(f"As NumPy int array: {tt_np_int}") 131 | tt_np_float = np.array(tt, dtype=np.float64) 132 | print(f"As NumPy float array: {tt_np_float}") 133 | 134 | 135 | # These arrays can now be used as labels for an ML model. 136 | # For example, let's generate the corresponding feature matrix: 137 | def generate_inputs(num_vars): 138 | inputs = [] 139 | for i in range(2**num_vars): 140 | # Convert i to binary and pad with zeros 141 | binary = bin(i)[2:].zfill(num_vars) 142 | inputs.append([int(bit) for bit in binary]) 143 | return np.array(inputs) 144 | 145 | 146 | feature_matrix = generate_inputs(tt.num_vars()) 147 | labels = tt_np_int # Using the integer array as labels 148 | 149 | print("\nFeature matrix (X) and labels (y) for ML:") 150 | print("X:\n", feature_matrix) 151 | print("y:\n", labels) 152 | ``` 153 | -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Available.md 4 | 5 | function( 6 | aigverse_set_project_warnings 7 | project_name 8 | WARNINGS_AS_ERRORS 9 | MSVC_WARNINGS 10 | CLANG_WARNINGS 11 | GCC_WARNINGS 12 | CUDA_WARNINGS) 13 | if("${MSVC_WARNINGS}" STREQUAL "") 14 | set(MSVC_WARNINGS 15 | /W4 # Baseline reasonable warnings 16 | /w14242 # 'identifier': conversion from 'type1' to 'type2', possible 17 | # loss of data 18 | /w14254 # 'operator': conversion from 'type1:field_bits' to 19 | # 'type2:field_bits', possible loss of data 20 | /w14263 # 'function': member function does not override any base class 21 | # virtual member function 22 | /w14265 # 'classname': class has virtual functions, but destructor is 23 | # not virtual instances of this class may not be destructed 24 | # correctly 25 | /w14287 # 'operator': unsigned/negative constant mismatch 26 | /we4289 # nonstandard extension used: 'variable': loop control variable 27 | # declared in the for-loop is used outside the for-loop scope 28 | /w14296 # 'operator': expression is always 'boolean_value' 29 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 30 | /w14545 # expression before comma evaluates to a function which is 31 | # missing an argument list 32 | /w14546 # function call before comma missing argument list 33 | /w14547 # 'operator': operator before comma has no effect; expected 34 | # operator with side-effect 35 | /w14549 # 'operator': operator before comma has no effect; did you 36 | # intend 'operator'? 37 | /w14555 # expression has no effect; expected expression with side- 38 | # effect 39 | /w14619 # pragma warning: there is no warning number 'number' 40 | /w14640 # Enable warning on thread un-safe static member initialization 41 | /w14826 # Conversion from 'type1' to 'type2' is sign-extended. This may 42 | # cause unexpected runtime behavior 43 | /w14905 # wide string literal cast to 'LPSTR' 44 | /w14906 # string literal cast to 'LPWSTR' 45 | /w14928 # illegal copy-initialization; more than one user-defined 46 | # conversion has been implicitly applied 47 | /permissive- # standards conformance mode for MSVC compiler 48 | ) 49 | endif() 50 | 51 | if("${CLANG_WARNINGS}" STREQUAL "") 52 | set(CLANG_WARNINGS 53 | -Wall 54 | -Wextra # reasonable and standard 55 | -Wshadow # warn the user if a variable declaration shadows one from a 56 | # parent context 57 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has 58 | # a non-virtual destructor. This helps catch hard to 59 | # track down memory errors 60 | -Wold-style-cast # warn for c-style casts 61 | -Wcast-align # warn for potential performance problem casts 62 | -Wunused # warn on anything being unused 63 | -Woverloaded-virtual # warn if you overload (not override) a virtual 64 | # function 65 | -Wpedantic # warn if non-standard C++ is used 66 | -Wconversion # warn on type conversions that may lose data 67 | -Wsign-conversion # warn on sign conversions 68 | -Wnull-dereference # warn if a null dereference is detected 69 | -Wdouble-promotion # warn if float is implicit promoted to double 70 | -Wformat=2 # warn on security issues around functions that format output 71 | # (ie printf) 72 | -Wimplicit-fallthrough # warn on statements that fallthrough without an 73 | # explicit annotation 74 | -Wno-unknown-pragmas # do not warn if encountering unknown pragmas 75 | -Wno-pragmas # do not warn if encountering unknown pragma options 76 | ) 77 | endif() 78 | 79 | if("${GCC_WARNINGS}" STREQUAL "") 80 | set(GCC_WARNINGS 81 | ${CLANG_WARNINGS} 82 | -Wmisleading-indentation # warn if indentation implies blocks where 83 | # blocks do not exist 84 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 85 | -Wduplicated-branches # warn if if / else branches have duplicated code 86 | -Wlogical-op # warn about logical operations being used where bitwise 87 | # were probably wanted 88 | -Wuseless-cast # warn if you perform a cast to the same type 89 | ) 90 | endif() 91 | 92 | if("${CUDA_WARNINGS}" STREQUAL "") 93 | set(CUDA_WARNINGS -Wall -Wextra -Wunused -Wconversion -Wshadow 94 | # TODO add more Cuda warnings 95 | ) 96 | endif() 97 | 98 | if(WARNINGS_AS_ERRORS) 99 | message(TRACE "Warnings are treated as errors") 100 | list(APPEND CLANG_WARNINGS -Werror) 101 | list(APPEND GCC_WARNINGS -Werror) 102 | list(APPEND MSVC_WARNINGS /WX) 103 | endif() 104 | 105 | if(MSVC) 106 | set(PROJECT_WARNINGS_CXX ${MSVC_WARNINGS}) 107 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 108 | set(PROJECT_WARNINGS_CXX ${CLANG_WARNINGS}) 109 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 110 | set(PROJECT_WARNINGS_CXX ${GCC_WARNINGS}) 111 | else() 112 | message( 113 | AUTHOR_WARNING 114 | "No compiler warnings set for CXX compiler: '${CMAKE_CXX_COMPILER_ID}'") 115 | # TODO support Intel compiler 116 | endif() 117 | 118 | # use the same warning flags for C 119 | set(PROJECT_WARNINGS_C "${PROJECT_WARNINGS_CXX}") 120 | 121 | set(PROJECT_WARNINGS_CUDA "${CUDA_WARNINGS}") 122 | 123 | target_compile_options( 124 | ${project_name} 125 | INTERFACE # C++ warnings 126 | $<$<COMPILE_LANGUAGE:CXX>:${PROJECT_WARNINGS_CXX}> 127 | # C warnings 128 | $<$<COMPILE_LANGUAGE:C>:${PROJECT_WARNINGS_C}> 129 | # Cuda warnings 130 | $<$<COMPILE_LANGUAGE:CUDA>:${PROJECT_WARNINGS_CUDA}>) 131 | endfunction() 132 | -------------------------------------------------------------------------------- /test/adapters/test_index_list.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import pytest 4 | 5 | from aigverse import Aig, AigIndexList, TruthTable, equivalence_checking, simulate, to_aig, to_index_list 6 | 7 | 8 | def test_decode_empty_index_list_into_aig() -> None: 9 | il = AigIndexList() 10 | 11 | aig = to_aig(il) 12 | 13 | assert aig.num_gates() == 0 14 | assert aig.num_pis() == 0 15 | assert aig.num_pos() == 0 16 | 17 | result = simulate(aig) 18 | 19 | assert len(result) == 0 20 | 21 | 22 | def test_encode_empty_aig_into_index_list() -> None: 23 | aig = Aig() 24 | 25 | il = to_index_list(aig) 26 | 27 | assert il.num_pis() == 0 28 | assert il.num_pos() == 0 29 | assert il.num_gates() == 0 30 | assert il.size() == 3 # [0, 0, 0] 31 | assert isinstance(il.raw(), list) 32 | assert il.raw() == [0, 0, 0] 33 | assert isinstance(str(il), str) 34 | assert str(il) == "{0, 0, 0}" 35 | assert isinstance(repr(il), str) 36 | assert repr(il) == "IndexList(#PIs: 0, #POs: 0, #Gates: 0, Gates: [], POs: [])" 37 | 38 | 39 | def test_decode_pi_only_index_list_into_aig() -> None: 40 | il = AigIndexList() 41 | 42 | il.add_inputs(4) 43 | assert il.num_pis() == 4 44 | 45 | aig = to_aig(il) 46 | 47 | assert aig.num_gates() == 0 48 | assert aig.num_pis() == 4 49 | assert aig.num_pos() == 0 50 | 51 | 52 | def test_encode_pi_only_aig_into_index_list() -> None: 53 | aig = Aig() 54 | aig.create_pi() 55 | aig.create_pi() 56 | aig.create_pi() 57 | aig.create_pi() 58 | 59 | il = to_index_list(aig) 60 | 61 | assert il.num_pis() == 4 62 | assert il.num_pos() == 0 63 | assert il.num_gates() == 0 64 | assert il.size() == 3 # [4, 0, 0] 65 | assert isinstance(il.raw(), list) 66 | assert il.raw() == [4, 0, 0] 67 | assert isinstance(str(il), str) 68 | assert str(il) == "{4, 0, 0}" 69 | assert isinstance(repr(il), str) 70 | assert repr(il) == "IndexList(#PIs: 4, #POs: 0, #Gates: 0, Gates: [], POs: [])" 71 | 72 | 73 | def test_decode_index_list_into_aig() -> None: 74 | il = AigIndexList([4, 1, 3, 2, 4, 6, 8, 12, 10, 14]) 75 | 76 | aig = to_aig(il) 77 | 78 | assert aig.num_gates() == 5 79 | assert aig.num_pis() == 4 80 | assert aig.num_pos() == 1 81 | 82 | tt_spec = TruthTable(4) 83 | tt_spec.create_from_hex_string("7888") 84 | 85 | tt_aig = simulate(aig)[0] 86 | 87 | assert tt_spec == tt_aig 88 | 89 | 90 | def test_implicit_conversion() -> None: 91 | il = [4, 1, 3, 2, 4, 6, 8, 12, 10, 14] 92 | 93 | aig = to_aig(il) # type: ignore[arg-type] 94 | 95 | assert aig.num_gates() == 5 96 | assert aig.num_pis() == 4 97 | assert aig.num_pos() == 1 98 | 99 | tt_spec = TruthTable(4) 100 | tt_spec.create_from_hex_string("7888") 101 | 102 | tt_aig = simulate(aig)[0] 103 | 104 | assert tt_spec == tt_aig 105 | 106 | 107 | def test_encode_aig_into_index_list() -> None: 108 | aig = Aig() 109 | a = aig.create_pi() 110 | b = aig.create_pi() 111 | c = aig.create_pi() 112 | d = aig.create_pi() 113 | t0 = aig.create_and(a, b) 114 | t1 = aig.create_and(c, d) 115 | t2 = aig.create_xor(t0, t1) 116 | aig.create_po(t2) 117 | 118 | il = to_index_list(aig) 119 | 120 | assert il.num_pis() == 4 121 | assert il.num_pos() == 1 122 | assert il.num_gates() == 5 123 | assert il.size() == 14 124 | assert isinstance(il.raw(), list) 125 | assert il.raw() == [4, 1, 5, 2, 4, 6, 8, 10, 13, 11, 12, 15, 17, 19] 126 | assert isinstance(str(il), str) 127 | assert str(il) == "{4, 1, 5, 2, 4, 6, 8, 10, 13, 11, 12, 15, 17, 19}" 128 | assert isinstance(repr(il), str) 129 | assert ( 130 | repr(il) 131 | == "IndexList(#PIs: 4, #POs: 1, #Gates: 5, Gates: [(2, 4), (6, 8), (10, 13), (11, 12), (15, 17)], POs: [19])" 132 | ) 133 | 134 | 135 | def test_encode_decode_aig_with_inverted_signals() -> None: 136 | aig = Aig() 137 | a = aig.create_pi() 138 | b = aig.create_pi() 139 | c = aig.create_pi() 140 | 141 | t0 = aig.create_and(a, b) 142 | t1 = aig.create_and(b, ~c) 143 | t2 = aig.create_and(~t0, ~t1) 144 | 145 | aig.create_po(~t1) 146 | aig.create_po(t2) 147 | 148 | il = to_index_list(aig) 149 | 150 | assert il.num_pis() == 3 151 | assert il.num_pos() == 2 152 | assert il.num_gates() == 3 153 | assert il.size() == 11 154 | assert il.raw() == [3, 2, 3, 2, 4, 4, 7, 9, 11, 11, 12] 155 | 156 | aig2 = to_aig(il) 157 | 158 | assert aig2.num_pis() == 3 159 | assert aig2.num_pos() == 2 160 | assert aig2.num_gates() == 3 161 | 162 | assert equivalence_checking(aig, aig2) 163 | 164 | 165 | def test_aig_index_list_methods() -> None: 166 | il = AigIndexList(3) 167 | assert il.num_pis() == 3 168 | 169 | il.add_and(1, 2) 170 | il.add_and(2, 3) 171 | il.add_output(5) 172 | 173 | assert il.num_gates() == 2 174 | assert il.num_pos() == 1 175 | assert isinstance(il.gates(), list) 176 | assert isinstance(il.pos(), list) 177 | assert len(il) == il.size() 178 | 179 | il.clear() 180 | 181 | # PIs will be left 182 | assert il.size() == 3 183 | 184 | 185 | def test_index_list_iter_get_set_item() -> None: 186 | il = AigIndexList([4, 1, 3, 2, 4, 6, 8, 12, 10, 14]) 187 | 188 | # Test __iter__ 189 | assert list(iter(il)) == [4, 1, 3, 2, 4, 6, 8, 12, 10, 14] 190 | 191 | # Test __getitem__ 192 | assert il[0] == 4 193 | assert il[1] == 1 194 | assert il[2] == 3 195 | 196 | # Test __setitem__ 197 | il[2] = 99 198 | assert il[2] == 99 199 | 200 | # Restore original value 201 | il[2] = 3 202 | assert il[2] == 3 203 | 204 | # Test out-of-bounds 205 | with pytest.raises(IndexError): 206 | _ = il[100] 207 | with pytest.raises(IndexError): 208 | il[100] = 1 209 | 210 | 211 | def test_index_list_to_python_list() -> None: 212 | il = AigIndexList([4, 1, 3, 2, 4, 6, 8, 12, 10, 14]) 213 | pylist = [il.num_pis(), il.num_pos(), il.num_gates(), il.gates(), il.pos()] 214 | assert pylist == [4, 1, 3, [(2, 4), (6, 8), (12, 10)], [14]] 215 | 216 | pylist = [int(i) for i in il] 217 | assert pylist == [4, 1, 3, 2, 4, 6, 8, 12, 10, 14] 218 | -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2025 Chair for Design Automation, TUM 2 | # All rights reserved. 3 | # 4 | # SPDX-License-Identifier: MIT 5 | # 6 | # Licensed under the MIT License 7 | 8 | """Sphinx configuration file.""" 9 | 10 | from __future__ import annotations 11 | 12 | import os 13 | import subprocess 14 | import warnings 15 | from importlib import metadata 16 | from pathlib import Path 17 | 18 | ROOT = Path(__file__).parent.parent.resolve() 19 | 20 | 21 | try: 22 | from aigverse import __version__ as version 23 | except ModuleNotFoundError: 24 | try: 25 | version = metadata.version("aigverse") 26 | except ModuleNotFoundError: 27 | msg = ( 28 | "Package should be installed to produce documentation! " 29 | "Assuming a modern git archive was used for version discovery." 30 | ) 31 | warnings.warn(msg, stacklevel=1) 32 | 33 | from setuptools_scm import get_version 34 | 35 | version = get_version(root=str(ROOT), fallback_root=ROOT) 36 | 37 | # Filter git details from version 38 | release = version.split("+")[0] 39 | 40 | project = "aigverse" 41 | author = "Marcel Walter, Technical University of Munich" 42 | language = "en" 43 | project_copyright = "2025, Marcel Walter, Technical University of Munich" 44 | 45 | master_doc = "index" 46 | 47 | templates_path = ["_templates"] 48 | html_css_files = ["custom.css"] 49 | 50 | extensions = [ 51 | "myst_nb", 52 | "autoapi.extension", 53 | "sphinx.ext.autodoc", 54 | "sphinx.ext.intersphinx", 55 | "sphinx.ext.napoleon", 56 | "sphinx_copybutton", 57 | "sphinx_design", 58 | "sphinxext.opengraph", 59 | "sphinx.ext.viewcode", 60 | "sphinxcontrib.inkscapeconverter", 61 | "breathe", 62 | ] 63 | 64 | source_suffix = [".rst", ".md"] 65 | 66 | exclude_patterns = [ 67 | "_build", 68 | "**.ipynb_checkpoints", 69 | "**.jupyter_cache", 70 | "**jupyter_execute", 71 | "Thumbs.db", 72 | ".DS_Store", 73 | ".env", 74 | ".venv", 75 | ] 76 | 77 | pygments_style = "colorful" 78 | 79 | intersphinx_mapping = { 80 | "python": ("https://docs.python.org/3", None), 81 | "networkx": ("https://networkx.org/documentation/stable/", None), 82 | "numpy": ("https://numpy.org/doc/stable/", None), 83 | } 84 | 85 | myst_enable_extensions = [ 86 | "amsmath", 87 | "colon_fence", 88 | "substitution", 89 | "deflist", 90 | "dollarmath", 91 | ] 92 | myst_substitutions = { 93 | "version": version, 94 | } 95 | myst_heading_anchors = 3 96 | nitpicky = True 97 | 98 | # -- Options for {MyST}NB ---------------------------------------------------- 99 | 100 | nb_execution_mode = "cache" 101 | nb_mime_priority_overrides = [ 102 | # builder name, mime type, priority 103 | ("latex", "image/svg+xml", 15), 104 | ] 105 | 106 | copybutton_prompt_text = r"(?:\(\.?venv\) )?(?:\[.*\] )?\$ " 107 | copybutton_prompt_is_regexp = True 108 | copybutton_line_continuation_character = "\\" 109 | 110 | modindex_common_prefix = ["aigverse."] 111 | 112 | autoapi_dirs = ["../src/aigverse"] 113 | autoapi_python_use_implicit_namespaces = True 114 | autoapi_root = "api" 115 | autoapi_add_toctree_entry = False 116 | autoapi_ignore = [ 117 | "*/**/_version.py", 118 | "*/**/test/*", 119 | ] 120 | autoapi_options = [ 121 | "members", 122 | "imported-members", 123 | "show-inheritance", 124 | "special-members", 125 | "undoc-members", 126 | ] 127 | autoapi_keep_files = True 128 | add_module_names = False 129 | toc_object_entries_show_parents = "hide" 130 | python_use_unqualified_type_names = True 131 | napoleon_google_docstring = True 132 | napoleon_numpy_docstring = False 133 | 134 | 135 | breathe_projects = {"aigverse": "_build/doxygen/xml"} 136 | breathe_default_project = "aigverse" 137 | 138 | read_the_docs_build = os.environ.get("READTHEDOCS", None) == "True" 139 | if read_the_docs_build: 140 | subprocess.call("doxygen", shell=True) # noqa: S602, S607 141 | subprocess.call("mkdir api/cpp & breathe-apidoc -o api/cpp -m -f -T _build/doxygen/xml/", shell=True) # noqa: S602, S607 142 | 143 | # -- Options for HTML output ------------------------------------------------- 144 | html_theme = "furo" 145 | html_static_path = ["_static"] 146 | html_theme_options = { 147 | "light_logo": "aigverse_logo_light_mode.svg", 148 | "dark_logo": "aigverse_logo_dark_mode.svg", 149 | "source_repository": "https://github.com/marcelwa/aigverse/", 150 | "source_branch": "main", 151 | "source_directory": "docs/", 152 | "navigation_with_keys": True, 153 | } 154 | 155 | # -- Options for LaTeX output ------------------------------------------------ 156 | 157 | numfig = True 158 | numfig_secnum_depth = 0 159 | 160 | sd_fontawesome_latex = True 161 | image_converter_args = ["-density", "300"] 162 | latex_engine = "pdflatex" 163 | latex_documents = [ 164 | ( 165 | master_doc, 166 | "aigverse.tex", 167 | r"\texttt{aigverse}\\{\Large A Python library for working with logic networks, synthesis, and optimization}", 168 | r"Marcel Walter\\Technical University of Munich", 169 | "howto", 170 | False, 171 | ), 172 | ] 173 | latex_logo = "_static/aigverse_logo_light_mode.png" 174 | latex_elements = { 175 | "papersize": "a4paper", 176 | "releasename": "Version", 177 | "printindex": r"\footnotesize\raggedright\printindex", 178 | "tableofcontents": "", 179 | "sphinxsetup": "iconpackage=fontawesome", 180 | "extrapackages": r"\usepackage{qrcode,graphicx,calc,amsthm,etoolbox,flushend,mathtools}", 181 | "preamble": r""" 182 | \patchcmd{\thebibliography}{\addcontentsline{toc}{section}{\refname}}{}{}{} 183 | \DeclarePairedDelimiter\abs{\lvert}{\rvert} 184 | \DeclarePairedDelimiter\mket{\lvert}{\rangle} 185 | \DeclarePairedDelimiter\mbra{\langle}{\rvert} 186 | \DeclareUnicodeCharacter{03C0}{$\pi$} 187 | 188 | \newcommand*{\ket}[1]{\ensuremath{\mket{\mkern1mu#1}}} 189 | \newcommand*{\bra}[1]{\ensuremath{\mbra{\mkern1mu#1}}} 190 | \newtheorem{example}{Example} 191 | \clubpenalty=10000 192 | \widowpenalty=10000 193 | \interlinepenalty 10000 194 | \def\subparagraph{} % because IEEE classes don't define this, but titlesec assumes it's present 195 | """, 196 | "extraclassoptions": r"journal, onecolumn", 197 | "fvset": r"\fvset{fontsize=\small}", 198 | "figure_align": "htb", 199 | } 200 | latex_domain_indices = False 201 | latex_docclass = { 202 | "howto": "IEEEtran", 203 | } 204 | -------------------------------------------------------------------------------- /.github/contributing.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for your interest in contributing to this project. 4 | We value contributions from people with all levels of experience. 5 | In particular if this is your first pull request not everything has to be perfect. 6 | We will guide you through the process. 7 | 8 | We use GitHub to [host code](https://github.com/marcelwa/aigverse), to [track issues and feature requests](https://github.com/marcelwa/aigverse/issues), as well as accept [pull requests](https://github.com/marcelwa/aigverse/pulls). 9 | See <https://docs.github.com/en/get-started/quickstart> for a general introduction to working with GitHub and contributing to projects. 10 | 11 | ## Types of Contributions 12 | 13 | You can contribute in several ways: 14 | 15 | - 🐛 Report Bugs 16 | : Report bugs at <https://github.com/marcelwa/aigverse/issues> using the _🐛 Bug report_ issue template. Please make sure to fill out all relevant information in the respective issue form. 17 | 18 | - 🐛 Fix Bugs 19 | : Look through the [GitHub Issues](https://github.com/marcelwa/aigverse/issues) for bugs. Anything tagged with "bug" is open to whoever wants to try and fix it. 20 | 21 | - ✨ Propose New Features 22 | : Propose new features at <https://github.com/marcelwa/aigverse/issues> using the _✨ Feature request_ issue template. Please make sure to fill out all relevant information in the respective issue form. 23 | 24 | - ✨ Implement New Features 25 | : Look through the [GitHub Issues](https://github.com/marcelwa/aigverse/issues) for features. Anything tagged with "feature" or "enhancement" is open to whoever wants to implement it. We highly appreciate external contributions to the project. 26 | 27 | - 📝 Write Documentation 28 | : aigverse could always use some more documentation, and we appreciate any help with that. 29 | 30 | ## Get Started 🎉 31 | 32 | Ready to contribute? Check out the {doc}`Development Guide <DevelopmentGuide>` to set up `aigverse` for local development and learn about the style guidelines and conventions used throughout the project. 33 | 34 | We value contributions from people with all levels of experience. 35 | In particular, if this is your first PR, not everything has to be perfect. 36 | We will guide you through the PR process. 37 | Nevertheless, please try to follow the guidelines below as well as you can to help make the PR process quick and smooth. 38 | 39 | ## Core Guidelines 40 | 41 | - ["Commit early and push often"](https://www.worklytics.co/blog/commit-early-push-often). 42 | - Write meaningful commit messages, preferably using [gitmoji](https://gitmoji.dev) for additional context. 43 | - Focus on a single feature or bug at a time and only touch relevant files. Split multiple features into separate contributions. 44 | - Add tests for new features to ensure they work as intended. Document new features appropriately. 45 | - Add tests for bug fixes to demonstrate that the bug has been resolved. 46 | - Document your code thoroughly and ensure it is readable. 47 | - Keep your code clean by removing debug statements, leftover comments, and unrelated code. 48 | - Check your code for style and linting errors before committing. 49 | - Follow the project's coding standards and conventions. 50 | - Be open to feedback and willing to make necessary changes based on code reviews. 51 | 52 | ## Pull Request Workflow 53 | 54 | - Create PRs early. It is ok to create work-in-progress PRs. You may mark these as draft PRs on GitHub. 55 | - Describe your PR with a descriptive title, reference any related issues by including the issue number in the PR description, and add a comprehensive description of the changes. Follow the provided PR template and do not delete any sections, except for the issue reference if your PR is not related to an issue. 56 | - Whenever a PR is created or updated, several workflows on all supported platforms and versions of Python are executed. These workflows ensure that the project still builds, all tests pass, the code is properly formatted, and no new linting errors are introduced. Your PR must pass all these continuous integration (CI) checks before it can be merged. 57 | - Once your PR is ready, change it from a draft PR to a regular PR and request a review from one of the project maintainers. Only request a review once you are done with your changes and the PR is ready to be reviewed. If you are unsure whether your PR is ready, ask in the PR comments. If you are a first-time contributor, request a review from one of the maintainers by mentioning them in a comment on the PR. 58 | - If your PR gets a "Changes requested" review, address the feedback and update your PR by pushing to the same branch. Do not close the PR and open a new one. Respond to review comments on the PR (e.g., with "done 👍" or "done in @<commit>") to let the reviewer know that you have addressed the feedback. Note that reviewers do not get a notification if you just react to the review comment with an emoji. Write a comment to notify the reviewer. Do not resolve the review comments yourself. The reviewer will mark the comments as resolved once they are satisfied with the changes. 59 | - Be sure to re-request a review once you have made changes after a code review so that maintainers know that the requests have been addressed. 60 | - No need to squash commits before merging; we usually squash them to keep the history clean. We only merge without squashing if the commit history is clean and meaningful. Avoid rebasing or force-pushing your PR branch before merging, as it complicates reviews. You can rebase or clean up commits after addressing all review comments if desired. 61 | 62 | Here are some tips for finding the cause of certain failures: 63 | 64 | - If any of the `🐍 • CI` checks fail, this indicates build errors or test failures in the Python part of the code base. Look through the respective logs on GitHub for any error or failure messages. 65 | - If any of the `codecov/\*` checks fail, this means that your changes are not appropriately covered by tests or that the overall project coverage decreased too much. Ensure that you include tests for all your changes in the PR. 66 | - If the `pre-commit.ci` check fails, some of the `pre-commit` checks failed and could not be fixed automatically by the _pre-commit.ci_ bot. Such failures are most likely related to the Python part of the code base. The individual log messages frequently provide helpful suggestions on how to fix the warnings. 67 | - If the `docs/readthedocs.org:\*` check fails, the documentation could not be built properly. Inspect the corresponding log file for any errors. 68 | 69 | --- 70 | 71 | This document was inspired by and partially adapted from 72 | 73 | - <https://matplotlib.org/stable/devel/coding_guide.html> 74 | - <https://opensource.creativecommons.org/contributing-code/pr-guidelines/> 75 | - <https://yeoman.io/contributing/pull-request.html> 76 | - <https://github.com/scikit-build/scikit-build> 77 | - <https://github.com/cda-tum/mqt-core> 78 | -------------------------------------------------------------------------------- /include/aigverse/adapters/edge_list.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by marcel on 05.09.24. 3 | // 4 | 5 | #pragma once 6 | 7 | #include "aigverse/types.hpp" 8 | 9 | #include <fmt/format.h> 10 | #include <fmt/ranges.h> 11 | #include <mockturtle/traits.hpp> 12 | 13 | #include <cstdint> 14 | #include <string> 15 | #include <tuple> 16 | #include <vector> 17 | 18 | namespace pybind11 19 | { 20 | class module_; 21 | } 22 | 23 | namespace aigverse 24 | { 25 | 26 | /** 27 | * Edge of a network. 28 | * 29 | * @tparam Ntk Network type. 30 | */ 31 | template <typename Ntk> 32 | struct edge 33 | { 34 | /** 35 | * Default constructor. 36 | */ 37 | constexpr edge() noexcept = default; 38 | /** 39 | * Constructor. 40 | * 41 | * @param src Source node of the edge. 42 | * @param tgt Target node of the edge. 43 | * @param w Weight of the edge. 44 | */ 45 | constexpr edge(const mockturtle::node<Ntk>& src, const mockturtle::node<Ntk>& tgt, const int64_t w = 0) noexcept : 46 | source{src}, 47 | target{tgt}, 48 | weight{w} 49 | {} 50 | /** 51 | * Equality operator. 52 | * 53 | * @param other Edge to compare with. 54 | * @return True if the edges are equal, false otherwise. 55 | */ 56 | [[nodiscard]] constexpr bool operator==(const edge& other) const noexcept 57 | { 58 | return source == other.source && target == other.target && weight == other.weight; 59 | } 60 | /** 61 | * Inequality operator. 62 | * 63 | * @param other Edge to compare with. 64 | * @return True if the edges are not equal, false otherwise. 65 | */ 66 | [[nodiscard]] constexpr bool operator!=(const edge& other) const noexcept 67 | { 68 | return !(*this == other); 69 | } 70 | /** 71 | * Implicit conversion to tuple. 72 | */ 73 | [[nodiscard]] constexpr operator std::tuple<mockturtle::node<Ntk>, mockturtle::node<Ntk>, int64_t>() const noexcept 74 | { 75 | return {source, target, weight}; 76 | } 77 | /** 78 | * Source node of the edge. 79 | */ 80 | mockturtle::node<Ntk> source{}; 81 | /** 82 | * Target node of the edge. 83 | */ 84 | mockturtle::node<Ntk> target{}; 85 | /** 86 | * Weight of the edge. 87 | */ 88 | int64_t weight{0}; 89 | }; 90 | /** 91 | * List of edges of a network. 92 | * 93 | * @tparam Ntk Network type. 94 | */ 95 | template <typename Ntk> 96 | struct edge_list 97 | { 98 | /** 99 | * Default constructor. 100 | */ 101 | edge_list() = default; 102 | /** 103 | * Constructor. 104 | * 105 | * @param network Network. 106 | */ 107 | explicit edge_list(const Ntk& network) : ntk{network} {}; 108 | /** 109 | * Constructor. 110 | * 111 | * @param network Network. 112 | * @param es Edges of the network. 113 | */ 114 | edge_list(const Ntk& network, const std::vector<edge<Ntk>>& es) : ntk{network}, edges{es} {}; 115 | /** 116 | * Implicit conversion to vector. 117 | * 118 | * @return Edges of the network. 119 | */ 120 | [[nodiscard]] operator std::vector<edge<Ntk>>() const noexcept 121 | { 122 | return edges; 123 | } 124 | /** 125 | * Network. 126 | */ 127 | [[maybe_unused]] Ntk ntk; 128 | /** 129 | * Edges of the network. 130 | */ 131 | std::vector<edge<Ntk>> edges{}; 132 | }; 133 | template <typename Ntk> 134 | [[nodiscard]] edge_list<typename Ntk::base_type> to_edge_list(const Ntk& ntk, const int64_t regular_weight = 0, 135 | const int64_t inverted_weight = 1) noexcept 136 | { 137 | auto el = edge_list<typename Ntk::base_type>(ntk); 138 | 139 | // constants, primary inputs, and regular nodes 140 | ntk.foreach_node( 141 | [&ntk, regular_weight, inverted_weight, &el](const auto& n) 142 | { 143 | ntk.foreach_fanin(n, 144 | [&ntk, regular_weight, inverted_weight, &el, &n](const auto& f) 145 | { 146 | el.edges.emplace_back(ntk.node_to_index(ntk.get_node(f)), ntk.node_to_index(n), 147 | ntk.is_complemented(f) ? inverted_weight : regular_weight); 148 | }); 149 | }); 150 | 151 | // primary outputs 152 | ntk.foreach_po( 153 | [&ntk, regular_weight, inverted_weight, &el](const auto& po) 154 | { 155 | el.edges.emplace_back(ntk.node_to_index(ntk.get_node(po)), ntk.size() + ntk.po_index(po), 156 | ntk.is_complemented(po) ? inverted_weight : regular_weight); 157 | }); 158 | 159 | // register connections (RI to RO) 160 | if constexpr (mockturtle::has_foreach_ri_v<Ntk> && mockturtle::has_ri_to_ro_v<Ntk>) 161 | { 162 | ntk.foreach_ri( 163 | [&ntk, regular_weight, inverted_weight, &el](const auto& ri) 164 | { 165 | // add the feedback loop edge from the driving node to the register output 166 | el.edges.emplace_back(ntk.node_to_index(ntk.get_node(ri)), ntk.node_to_index(ntk.ri_to_ro(ri)), 167 | ntk.is_complemented(ri) ? inverted_weight : regular_weight); 168 | }); 169 | } 170 | 171 | return el; 172 | } 173 | 174 | namespace detail 175 | { 176 | 177 | // Forward declaration of binding template. 178 | template <typename Ntk> 179 | void ntk_edge_list(pybind11::module_& m, const std::string& network_name); 180 | 181 | // Explicit instantiation declaration for AIG. 182 | extern template void ntk_edge_list<aigverse::aig>(pybind11::module_& m, const std::string& network_name); 183 | 184 | } // namespace detail 185 | 186 | // Wrapper declaration (implemented in .cpp) 187 | void bind_to_edge_list(pybind11::module_& m); 188 | 189 | } // namespace aigverse 190 | 191 | namespace fmt 192 | { 193 | 194 | // make edge compatible with fmt::format 195 | template <typename Ntk> 196 | struct formatter<aigverse::edge<Ntk>> 197 | { 198 | template <typename ParseContext> 199 | constexpr auto parse(ParseContext& ctx) 200 | { 201 | return ctx.begin(); 202 | } 203 | 204 | template <typename FormatContext> 205 | auto format(const aigverse::edge<Ntk>& e, FormatContext& ctx) const 206 | { 207 | return format_to(ctx.out(), "Edge(s:{},t:{},w:{})", e.source, e.target, e.weight); 208 | } 209 | }; 210 | 211 | // make edge_list compatible with fmt::format 212 | template <typename Ntk> 213 | struct formatter<aigverse::edge_list<Ntk>> 214 | { 215 | template <typename ParseContext> 216 | constexpr auto parse(ParseContext& ctx) 217 | { 218 | return ctx.begin(); 219 | } 220 | 221 | template <typename FormatContext> 222 | auto format(const aigverse::edge_list<Ntk>& el, FormatContext& ctx) const 223 | { 224 | return format_to(ctx.out(), "{}", el.edges); 225 | } 226 | }; 227 | 228 | } // namespace fmt 229 | -------------------------------------------------------------------------------- /docs/algorithms.md: -------------------------------------------------------------------------------- 1 | --- 2 | file_format: mystnb 3 | kernelspec: 4 | name: python3 5 | mystnb: 6 | number_source_lines: true 7 | --- 8 | 9 | ```{code-cell} ipython3 10 | :tags: [remove-cell] 11 | %config InlineBackend.figure_formats = ['svg'] 12 | ``` 13 | 14 | # Algorithms 15 | 16 | This section covers the various algorithms available in aigverse for working with And-Inverter Graphs (AIGs) and other logic representations. These algorithms enable simulation, optimization, and verification of logic networks. 17 | 18 | ## Simulation 19 | 20 | Simulation algorithms allow you to evaluate the outputs of a logic network for all possible input combinations, effectively generating truth tables for the network's outputs and internal nodes. 21 | 22 | ### Functional Simulation 23 | 24 | For simulating AIGs with truth tables, the {py:func}`~aigverse.simulate` and {py:func}`~aigverse.simulate_nodes` functions allow you to obtain truth tables for outputs and internal nodes of an AIG. 25 | 26 | ```{code-cell} ipython3 27 | from aigverse import Aig, simulate, simulate_nodes 28 | 29 | # Create a sample AIG 30 | aig = Aig() 31 | a = aig.create_pi() 32 | b = aig.create_pi() 33 | f_and = aig.create_and(a, b) 34 | f_or = aig.create_or(a, b) 35 | aig.create_po(f_and) 36 | aig.create_po(f_or) 37 | 38 | # Simulate the outputs 39 | output_tts = simulate(aig) 40 | 41 | # Print the truth tables 42 | print("Truth tables of outputs:") 43 | for i, tt in enumerate(output_tts): 44 | print(f" Output {i}: {tt.to_binary()}") 45 | 46 | # Simulate all nodes 47 | node_tts = simulate_nodes(aig) 48 | 49 | # Print the truth table of each node 50 | print("\nTruth tables of nodes:") 51 | for node, tt in node_tts.items(): 52 | print(f" Node {node}: {tt.to_binary()}") 53 | ``` 54 | 55 | ## Optimization 56 | 57 | AIG optimization aims to reduce the number of AND gates and inverters in a circuit while maintaining its logical functionality. Different optimization techniques target various aspects of the AIG structure. 58 | 59 | ### Basic Optimization Workflow 60 | 61 | The typical optimization workflow involves: 62 | 63 | 1. Creating or loading an AIG 64 | 2. Applying one or more optimization algorithms 65 | 3. Verifying correctness through equivalence checking 66 | 67 | ```{code-cell} ipython3 68 | from aigverse import read_aiger_into_aig 69 | 70 | # Load the i10 benchmark circuit - a real-world example 71 | aig = read_aiger_into_aig("examples/i10.aig") 72 | 73 | # Print statistics about the loaded circuit 74 | print(f"i10 benchmark:") 75 | print(f" I/O: {aig.num_pis()}/{aig.num_pos()}") 76 | print(f" AND gates: {aig.num_gates()}") 77 | ``` 78 | 79 | ### Resubstitution 80 | 81 | Resubstitution identifies portions of logic that can be expressed using existing signals in the network. This technique is particularly effective at identifying and eliminating redundant logic. 82 | 83 | ```{code-cell} ipython3 84 | from aigverse import aig_resubstitution 85 | 86 | # Clone the AIG for comparison 87 | aig_resub = aig.clone() 88 | 89 | # Apply resubstitution 90 | aig_resubstitution(aig_resub, window_size=12) 91 | 92 | print(f"Original AND gates: {aig.num_gates()}") 93 | print(f"After resubstitution: {aig_resub.num_gates()} AND gates") 94 | print(f"Reduction: {aig.num_gates() - aig_resub.num_gates()} gates ({(aig.num_gates() - aig_resub.num_gates()) / aig.num_gates() * 100:.2f}%)") 95 | ``` 96 | 97 | ### Sum-of-Products Refactoring 98 | 99 | SOP (Sum of Products) refactoring collapses parts of the AIG into truth tables, then re-synthesizes those portions using Sum-of-Products representations. This can find more efficient implementations for complex logic functions. 100 | 101 | ```{code-cell} ipython3 102 | from aigverse import sop_refactoring 103 | 104 | # Clone the AIG for comparison 105 | aig_refactor = aig.clone() 106 | 107 | # Apply SOP refactoring 108 | sop_refactoring(aig_refactor, use_reconvergence_cut=True) 109 | 110 | print(f"Original AND gates: {aig.num_gates()}") 111 | print(f"After SOP refactoring: {aig_refactor.num_gates()} AND gates") 112 | print(f"Reduction: {aig.num_gates() - aig_refactor.num_gates()} gates ({(aig.num_gates() - aig_refactor.num_gates()) / aig.num_gates() * 100:.2f}%)") 113 | ``` 114 | 115 | ### Cut Rewriting 116 | 117 | Cut rewriting identifies small subgraphs (cuts) in the AIG and replaces them with pre-computed optimal implementations from a library. This technique leverages NPN-equivalence classes to find the best possible implementation for each cut. 118 | 119 | ```{code-cell} ipython3 120 | from aigverse import aig_cut_rewriting 121 | 122 | # Clone the AIG for comparison 123 | aig_rewrite = aig.clone() 124 | 125 | # Apply cut rewriting 126 | aig_cut_rewriting(aig_rewrite, cut_size=4) 127 | 128 | print(f"Original AND gates: {aig.num_gates()}") 129 | print(f"After cut rewriting: {aig_rewrite.num_gates()} AND gates") 130 | print(f"Reduction: {aig.num_gates() - aig_rewrite.num_gates()} gates ({(aig.num_gates() - aig_rewrite.num_gates()) / aig.num_gates() * 100:.2f}%)") 131 | ``` 132 | 133 | ### Balancing 134 | 135 | Balancing performs (E)SOP factoring to minimize the number of levels in the AIG. 136 | 137 | ```{code-cell} ipython3 138 | from aigverse import balancing, DepthAig 139 | 140 | # Clone the AIG for comparison 141 | aig_balance = aig.clone() 142 | 143 | # Apply balancing 144 | balancing(aig_balance, rebalance_function="sop") 145 | 146 | # Compute depth 147 | original_depth = DepthAig(aig).num_levels() 148 | balanced_depth = DepthAig(aig_balance).num_levels() 149 | 150 | print(f"Original depth: {original_depth} levels") 151 | print(f"After balancing: {balanced_depth} levels") 152 | print(f"Reduction in depth: {original_depth - balanced_depth} levels ({(original_depth - balanced_depth) / original_depth * 100:.2f}%)") 153 | ``` 154 | 155 | ### Combining Optimization Techniques 156 | 157 | For best results, optimization techniques are typically applied in combination, often in multiple passes. The order of application can significantly impact the final result. 158 | 159 | ```{code-cell} ipython3 160 | # Apply optimization techniques in sequence 161 | aig_opt = aig.clone() 162 | 163 | # First pass 164 | aig_resubstitution(aig_opt) 165 | sop_refactoring(aig_opt) 166 | aig_cut_rewriting(aig_opt) 167 | 168 | # Second pass 169 | aig_resubstitution(aig_opt) 170 | sop_refactoring(aig_opt) 171 | 172 | print(f"\nTotal optimization results:") 173 | print(f"- Original: {aig.num_gates()} AND gates") 174 | print(f"- Optimized: {aig_opt.num_gates()} AND gates") 175 | print(f"- Total reduction: {aig.num_gates() - aig_opt.num_gates()} gates ({(aig.num_gates() - aig_opt.num_gates()) / aig.num_gates() * 100:.2f}%)") 176 | ``` 177 | 178 | ## Equivalence Checking 179 | 180 | Equivalence checking algorithms verify that two logic networks implement the same function, which is especially important after performing optimizations. 181 | 182 | ```{code-cell} ipython3 183 | from aigverse import equivalence_checking 184 | 185 | # Verify that our optimized circuit from the previous section maintains functional equivalence 186 | are_equivalent = equivalence_checking(aig, aig_opt) 187 | print(f"\nOriginal and optimized benchmark circuits are equivalent: {are_equivalent}") 188 | print(f"This confirms our optimization preserved the circuit's functionality while reducing") 189 | print(f"the gate count from {aig.num_gates()} to {aig_opt.num_gates()} AND gates.") 190 | ``` 191 | --------------------------------------------------------------------------------