Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions docs/aigs.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,60 @@ for node in fanout_aig.fanouts(aig.get_node(n4)):
print(f" Node {node}")
```

### Cut Views

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
### Cut Views
### Cuts


The {py:class}`~aigverse.networks.Cut` class provides an isolated view on a single cut in a network. A cut has a single output (root) and a set of leaves (inputs). This is useful for analyzing subgraphs or extracting portions of a larger network.

```{code-cell} ipython3
from aigverse.networks import Aig, Cut

# Create a sample AIG
aig = Aig()
x1 = aig.create_pi()
x2 = aig.create_pi()
x3 = aig.create_pi()

# Create logic
f1 = aig.create_and(x1, x2)
f2 = aig.create_and(f1, x3)
aig.create_po(f2)

# Create a small cut view with x1, x2 as leaves and f1 as root
# This is valid because f1 only depends on x1 and x2
cut = Cut(aig, [x1, x2], f1)

print(f"Small cut has {cut.num_pis} PIs (leaves)")
print(f"Small cut has {cut.num_pos} POs (roots)")
print(f"Small cut has {cut.num_gates} gates")

# Iterate over leaves (PIs in the cut)
print("\nLeaf nodes in small cut:")
for leaf in cut.pis():
print(f" Leaf: {leaf}")

# Iterate over gates in the cut
print("\nGates in small cut:")
for gate in cut.gates():
print(f" Gate: {gate}")

# Create a larger cut with all PIs as leaves and f2 as root
# This cut includes both f1 and f2 gates
cut2 = Cut(aig, [x1, x2, x3], f2)

print(f"\nLarger cut has {cut2.num_pis} PIs (leaves)")
print(f"Larger cut has {cut2.num_pos} POs (roots)")
print(f"Larger cut has {cut2.num_gates} gates")

# The gates include both f1 and f2
print("\nGates in larger cut:")
for gate in cut2.gates():
print(f" Gate: {gate}")
```

:::{note}
A cut is only valid if all dependencies of the root node are either included in the leaves or can be reached through nodes within the cut. The cut view clears all nodes' visited flags before construction to ensure the cut is constructed correctly.
:::

### Sequential AIGs

{py:class}`~aigverse.networks.SequentialAig`s extend standard AIGs to include registers, which allow modeling sequential circuits
Expand Down
90 changes: 90 additions & 0 deletions python/aigverse/networks.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,96 @@ class FanoutAig(Aig):
def fanouts(self, n: int) -> list[int]:
"""Returns fanout nodes of node ``n``."""

class Cut:
"""Implements an isolated view on a single cut in a network.

This view creates a network from a single cut with a single output `root`
and a set of `leaves`. This is a standalone structural descriptor; it does
not inherit from the network type and only exposes read-only inspection
methods.

Note:
This view clears all nodes' visited flags before construction to ensure
the cut is constructed correctly. The view guarantees that all nodes in
the view will have a 0 visited flag after construction.
"""

@overload
def __init__(self, ntk: Aig, leaves: Sequence[int], root: AigSignal) -> None:
"""Creates a cut view from a network, leaf nodes, and root signal.

Args:
ntk: The base network.
leaves: Vector of leaf nodes (boundary of the cut).
root: The root signal (output) of the cut.
"""

@overload
def __init__(self, ntk: Aig, leaves: Sequence[AigSignal], root: AigSignal) -> None:
"""Creates a cut view from a network, leaf signals, and root signal.

Args:
ntk: The base network.
leaves: Vector of leaf signals (boundary of the cut).
root: The root signal (output) of the cut.
"""

def clone(self) -> Cut:
"""Creates a structural copy of the cut view."""

def __copy__(self) -> Cut:
"""Returns a shallow copy of the cut view."""

def __deepcopy__(self, memo: dict) -> Cut:
"""Returns a deep copy of the cut view."""

def nodes(self) -> list[int]:
"""Returns a list of all nodes in the cut view."""

def gates(self) -> list[int]:
"""Returns a list of all gate nodes in the cut view."""

def pis(self) -> list[int]:
"""Returns a list of all primary input (leaf) nodes in the cut view."""

def pos(self) -> list[AigSignal]:
"""Returns a list containing the root signal of the cut view."""

def is_pi(self, n: int) -> bool:
"""Returns whether ``n`` is a primary input (leaf) in the cut view."""

@property
def size(self) -> int:
"""Number of nodes in the cut view."""

@property
def num_pis(self) -> int:
"""Number of primary inputs (leaves) in the cut view."""

@property
def num_pos(self) -> int:
"""Number of primary outputs (always 1 for cut view)."""

@property
def num_gates(self) -> int:
"""Number of logic gates in the cut view."""

def node_to_index(self, n: int) -> int:
"""Returns the integer index of a node."""

def index_to_node(self, index: int) -> int:
"""Returns the node for an index."""

def to_index_list(self) -> AigIndexList:
"""Converts the cut view to an index-list encoding.

Only the cut's restricted node set is encoded. The resulting index list
can be decoded into a standalone Aig via ``AigIndexList.to_aig()``.

Returns:
The corresponding index-list representation.
"""

class AigRegister:
"""Represents metadata for one sequential register."""

Expand Down
127 changes: 127 additions & 0 deletions src/aigverse/networks/logic_networks.cpp

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we're exposing cuts, we should also expose cut enumeration algorithms: https://mockturtle.readthedocs.io/en/latest/algorithms/cut_enumeration.html

I haven't put much thought into how exactly the architecture would look best for aigverse. Would you mind making an initial proposal?

Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include <fmt/format.h>
#include <mockturtle/networks/sequential.hpp>
#include <mockturtle/traits.hpp>
#include <mockturtle/views/cut_view.hpp>
#include <mockturtle/views/depth_view.hpp>
#include <mockturtle/views/fanout_view.hpp>
#include <mockturtle/views/names_view.hpp>
Expand All @@ -22,6 +23,7 @@

#include <algorithm>
#include <cctype>
#include <cstddef>
#include <cstdint>
#include <exception>
#include <functional>
Expand Down Expand Up @@ -529,6 +531,131 @@ Preserves only combinational structure and does not capture augmented view metad
},
nb::arg("n"), R"pb(Returns fanout nodes of node ``n``.)pb");

using CutNtk = mockturtle::cut_view<Ntk>;

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using CutNtk = mockturtle::cut_view<Ntk>;
using NtkCut = mockturtle::cut_view<Ntk>;


auto clear_visited = [](const Ntk& ntk) { ntk.foreach_node([&ntk](const auto& n) { ntk.set_visited(n, 0); }); };

nb::class_<CutNtk>(m, "Cut",
R"pb(Implements an isolated view on a single cut in a network.
Comment on lines +538 to +539

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know I proposed renaming this to Cut, but we will have naming collisions if we keep it this way, once new network types get added. Two options:

  1. Move this entire block out of the function template so that it registers only once. But that won't work with all network node types.
  2. Introduce one cut type per network type, i.e., renaming it to AigCut, indicating it is a cut in an AIG, rather than an AIG with cuts in it. I prefer this solution.

Long story short, we won't get around parameterizing this type with the network name to avoid collisions; we just do it slightly differently than before. And we keep the non-inheritance.

Suggested change
nb::class_<CutNtk>(m, "Cut",
R"pb(Implements an isolated view on a single cut in a network.
nb::class_<CutNtk>(m, fmt::format("{}Cut", ...).c_str(),
R"pb(Implements an isolated view on a single cut in a network.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The documentation and tests will have to be adjusted accordingly.


This view creates a network from a single cut with a single output `root`
and a set of `leaves`. This is a standalone structural descriptor; it does
not inherit from the network type and only exposes read-only inspection
methods.

Note:
This view clears all nodes' visited flags before construction to ensure
the cut is constructed correctly. The view guarantees that all nodes in
the view will have a 0 visited flag after construction.)pb")
Comment on lines +534 to +549

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is exactly what I had in mind. Thanks for changing the architecture! Feels much cleaner to me. What do you think?

A few things we must still adjust, though, or we will run into issues once we add more network types.

.def(
"__init__",
[clear_visited](CutNtk* self, const Ntk& ntk, const std::vector<Node>& leaves, const Signal& root)
{
clear_visited(ntk);
new (self) CutNtk(ntk, leaves, root);
},
nb::arg("ntk"), nb::arg("leaves"), nb::arg("root"),
R"pb(Creates a cut view from a network, leaf nodes, and root signal.

Args:
ntk: The base network.
leaves: Vector of leaf nodes (boundary of the cut).
root: The root signal (output) of the cut.)pb")
.def(
"__init__",
[clear_visited](CutNtk* self, const Ntk& ntk, const std::vector<Signal>& leaves, const Signal& root)
{
clear_visited(ntk);
new (self) CutNtk(ntk, leaves, root);
},
nb::arg("ntk"), nb::arg("leaves"), nb::arg("root"),
R"pb(Creates a cut view from a network, leaf signals, and root signal.

Args:
ntk: The base network.
leaves: Vector of leaf signals (boundary of the cut).
root: The root signal (output) of the cut.)pb")
.def(
"clone", [](const CutNtk& ntk) { return CutNtk{ntk}; }, R"pb(Creates a structural copy of the cut view.)pb")
.def(
"__copy__", [](const CutNtk& ntk) { return CutNtk{ntk}; }, R"pb(Returns a shallow copy of the cut view.)pb")
.def(
"__deepcopy__", [](const CutNtk& ntk, const nb::dict&) { return CutNtk{ntk}; }, nb::arg("memo"),
R"pb(Returns a deep copy of the cut view.)pb")
.def(
"nodes", [](const CutNtk& ntk) { return collect_nodes(ntk); },
R"pb(Returns a list of all nodes in the cut view.)pb")
.def(
"gates",
[](const CutNtk& ntk)
{
std::vector<Node> gates{};
gates.reserve(static_cast<size_t>(ntk.num_gates()));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
gates.reserve(static_cast<size_t>(ntk.num_gates()));
gates.reserve(static_cast<std::size_t>(ntk.num_gates()));

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use this consistently.

ntk.foreach_gate([&gates](const auto& g) { gates.push_back(g); });
return gates;
},
R"pb(Returns a list of all gate nodes in the cut view.)pb")
.def(
"pis",
[](const CutNtk& ntk)
{
std::vector<Node> pis{};
pis.reserve(static_cast<size_t>(ntk.num_pis()));
ntk.foreach_pi([&pis](const auto& pi) { pis.push_back(pi); });
return pis;
},
R"pb(Returns a list of all primary input (leaf) nodes in the cut view.)pb")
.def(
"pos",
[](const CutNtk& ntk)
{
std::vector<Signal> pos{};
pos.reserve(static_cast<size_t>(ntk.num_pos()));
ntk.foreach_po([&pos](const auto& po) { pos.push_back(po); });
return pos;
},
R"pb(Returns a list containing the root signal of the cut view.)pb")
.def(
"is_pi", [](const CutNtk& ntk, const Node& n) { return ntk.is_pi(n); }, nb::arg("n"),
R"pb(Returns whether ``n`` is a primary input (leaf) in the cut view.)pb")
.def_prop_ro(
"size", [](const CutNtk& ntk) { return ntk.size(); }, R"pb(Number of nodes in the cut view.)pb")
.def_prop_ro(
"num_pis", [](const CutNtk& ntk) { return ntk.num_pis(); },
R"pb(Number of primary inputs (leaves) in the cut view.)pb")
.def_prop_ro(
"num_pos", [](const CutNtk& ntk) { return ntk.num_pos(); },
R"pb(Number of primary outputs (always 1 for cut view).)pb")
.def_prop_ro(
"num_gates", [](const CutNtk& ntk) { return ntk.num_gates(); },
R"pb(Number of logic gates in the cut view.)pb")
.def(
"node_to_index", [](const CutNtk& ntk, const Node& n) { return ntk.node_to_index(n); }, nb::arg("n"),
R"pb(Returns the integer index of a node.)pb")
.def(
"index_to_node", [](const CutNtk& ntk, const uint32_t index) { return ntk.index_to_node(index); },
nb::arg("index"), R"pb(Returns the node for an index.)pb")
.def(
"to_index_list",
[](const CutNtk& ntk)
{
aigverse::aig_index_list il{};
mockturtle::encode(il, ntk);
return il;
},
R"pb(Converts the cut view to an index-list encoding.

Only the cut's restricted node set is encoded. The resulting index list
can be decoded into a standalone Aig via ``AigIndexList.to_aig()``.

Returns:
The corresponding index-list representation.)pb",
nb::rv_policy::move)
.def(
"__repr__", [](const CutNtk& ntk)
{ return fmt::format("Cut(leaves={}, gates={}, size={})", ntk.num_pis(), ntk.num_gates(), ntk.size()); },
R"pb(Returns a developer-friendly string representation.)pb");

using Register = mockturtle::register_t; // NOLINT(readability-identifier-naming)
nb::class_<Register>(m, fmt::format("{}Register", network_name).c_str(),
R"pb(Represents metadata for one sequential register.)pb")
Expand Down
Loading
Loading