Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug fix/documentation #223

Merged
merged 15 commits into from
Sep 20, 2022
Merged
Show file tree
Hide file tree
Changes from 4 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
48 changes: 39 additions & 9 deletions dwave_networkx/generators/chimera.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

from itertools import product

from .common import _add_compatible_edges

__all__ = ['chimera_graph',
'chimera_coordinates',
'find_chimera_indices',
Expand All @@ -35,7 +37,8 @@
]


def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_list=None, data=True, coordinates=False):
def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_list=None,
data=True, coordinates=False, check_node_list=False, check_edge_list=False):
"""Creates a Chimera lattice of size (m, n, t).

Parameters
Expand All @@ -50,13 +53,18 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
If provided, this graph is cleared of nodes and edges and filled
with the new graph. Usually used to set the type of the graph.
node_list : iterable (optional, default None)
Iterable of nodes in the graph. If None, calculated
from (m, n, t). Note that this list is used to remove nodes,
so any nodes specified not in ``range(m * n * 2 * t)`` are not added.
Iterable of nodes in the graph. If None, calculated from (``m``, ``n``, ``t``)
and ``coordinates``. The nodes should be compatible with the requested
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
coordinate system and topology bounds; by default integer-labeled
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
in :code:`range(m * n * t * 2)`. Nodes incompatible
with the requested topology are accepted by default.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
edge_list : iterable (optional, default None)
Iterable of edges in the graph. If None, edges are
generated as described below. The nodes in each edge must be
integer-labeled in ``range(m * n * t * 2)``.
Iterable of edges in the graph. If None, calculated
from the ``node_list`` as described below.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
Edges should be 2-tuples of nodes that match the
requested coordinate system and topology bounds. Edges are accepted by
default, provided component nodes are contained in the ``node_list``,
otherwise they are ignored.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
data : bool (optional, default True)
If True, each node has a
`chimera_index attribute`. The attribute is a 4-tuple Chimera index
Expand All @@ -65,6 +73,18 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
If True, node labels are 4-tuples, equivalent to the chimera_index
attribute as below. In this case, the `data` parameter controls the
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
existence of a `linear_index attribute`, which is an int.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
check_node_list : bool (optional, default False)
If True, the node_list elements are checked for compatibility with
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
the graph topology and node labeling conventions, an error is thrown
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
if any node is incompatible or duplicates exist.
In other words, only node_lists that specify subgraphs of the default
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
(full yield) graph are permitted.
check_edge_list : bool (optional, default False)
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
If True, the edge_list elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
the graph topology and node labeling conventions, an error is thrown
the graph topology and node labeling conventions, and an error is thrown

if any edge is incompatible or duplicates exist.
In other words, only edge_lists that specify subgraphs of the default
(full yield) graph are permitted.

Returns
-------
Expand Down Expand Up @@ -139,7 +159,7 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis

max_size = m * n * 2 * t # max number of nodes G can have

if edge_list is None:
if edge_list is None or check_edge_list is True:
if coordinates:
# tile edges
G.add_edges_from(((i, j, 0, k0), (i, j, 1, k1))
Expand Down Expand Up @@ -180,13 +200,23 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
for i in range(t)
for j in range(i, ni, hoff)
for k in range(j, mi - voff, voff))
if edge_list is not None:
_add_compatible_edges(G, edge_list)

else:
G.add_edges_from(edge_list)

if node_list is not None:
nodes = set(node_list)
G.remove_nodes_from(set(G) - nodes)
G.add_nodes_from(nodes) # for singleton nodes
if check_node_list:
if G.number_of_nodes() != len(node_list):
raise ValueError("node_list contains nodes incompatible with "
"the specified topology and node-labeling "
"convention.")

else:
G.add_nodes_from(nodes) # for singleton nodes

if data:
if coordinates:
Expand Down
13 changes: 13 additions & 0 deletions dwave_networkx/generators/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@

def _add_compatible_edges(G, edge_list):
# Check edge_list defines a subgraph of G and create subgraph.
# Slow when edge_list is large, but clear (non-defaulted behaviour, so fine):
if edge_list is not None:
if not all(G.has_edge(*e) for e in edge_list):
raise ValueError("edge_list contains edges incompatible with a "
"fully yielded graph of the requested topology")
# Hard to check edge_list consistency owing to directedness, etc. Brute force
G.remove_edges_from(list(G.edges))
G.add_edges_from(edge_list)
if G.number_of_edges() < len(edge_list):
raise ValueError('edge_list contains duplicates.')
51 changes: 39 additions & 12 deletions dwave_networkx/generators/pegasus.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,16 @@

from itertools import product
from .chimera import _chimera_coordinates_cache
from .common import _add_compatible_edges

__all__ = ['pegasus_graph',
'pegasus_coordinates',
'pegasus_sublattice_mappings',
]


def pegasus_graph(m, create_using=None, node_list=None, edge_list=None, data=True,
offset_lists=None, offsets_index=None, coordinates=False, fabric_only=True,
nice_coordinates=False):
nice_coordinates=False, check_node_list=False, check_edge_list=False):
"""
Creates a Pegasus graph with size parameter `m`.

Expand All @@ -44,14 +44,20 @@ def pegasus_graph(m, create_using=None, node_list=None, edge_list=None, data=Tru
create_using : Graph, optional (default None)
If provided, this graph is cleared of nodes and edges and filled
with the new graph. Usually used to set the type of the graph.
node_list : iterable, optional (default None)
Iterable of nodes in the graph. If None, calculated from `m`.
Note that this list is used to remove nodes, so any nodes specified
not in ``range(24 * m * (m-1))`` are not added.
edge_list : iterable, optional (default None)
Iterable of edges in the graph. If None, edges are generated as
described below. The nodes in each edge must be integer-labeled in
``range(24 * m * (m-1))``.
node_list : iterable (optional, default None)
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
Iterable of nodes in the graph. If None, calculated
from ``m``, ``fabric_only``, ``nice_coordinates`` and
``coordinates`` as described below. The nodes should be compatible
with the requested coordinate system and topology bounds; by
default integer-labeled in :code:`range(m * (m-1) * 24)`. Nodes
incompatible with the requested topology are accepted by default.
edge_list : iterable (optional, default None)
Iterable of edges in the graph. If None, calculated from the
``node_list`` as described below.
Edges should be 2-tuples of nodes that match the
requested coordinate system and topology bounds. Edges are accepted by
default, provided component nodes are contained in the ``node_list``,
otherwise they are ignored.
data : bool, optional (default True)
If True, each node has a pegasus_index attribute. The attribute
is a 4-tuple Pegasus index as defined below. If the `coordinates` parameter
Expand Down Expand Up @@ -83,6 +89,18 @@ def pegasus_graph(m, create_using=None, node_list=None, edge_list=None, data=Tru
For any given :math:`0 <= t0 < 3`, the subgraph of nodes with :math:`t = t0`
has the structure of `chimera(M-1, M-1, 4)` with the addition of odd couplers.
Supercedes both the `fabric_only` and `coordinates` parameters.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Supercedes both the `fabric_only` and `coordinates` parameters.
Supercedes both the ``fabric_only`` and ``coordinates`` parameters.

check_node_list : bool (optional, default False)
If True, the node_list elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
the graph topology and node labeling conventions, an error is thrown
the graph topology and node labeling conventions, and an error is thrown

if any node is incompatible or duplicates exist.
In other words, only node_lists that specify subgraphs of the default
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
In other words, only node_lists that specify subgraphs of the default
In other words, ``node_list`` must specify a subgraph of the default

(full yield) graph are permitted.
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
(full yield) graph are permitted.
(full yield) graph described below.

check_edge_list : bool (optional, default False)
If True, the edge_list elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
if any edge is incompatible or duplicates exist.
In other words, only edge_lists that specify subgraphs of the default
(full yield) graph are permitted.
Comment on lines +103 to +104
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
In other words, only edge_lists that specify subgraphs of the default
(full yield) graph are permitted.
In other words, ``edge_list`` must specify a subgraph of the default
(full yield) graph described below.


Returns
-------
Expand Down Expand Up @@ -216,7 +234,7 @@ def label(u, w, k, z):

max_size = m * (m - 1) * 24 # max number of nodes G can have

if edge_list is None:
if edge_list is None or check_edge_list is True:
if nice_coordinates:
fabric_start = 4,8
fabric_end = 8, 4
Expand Down Expand Up @@ -252,13 +270,22 @@ def efilter(e): return qfilter(*e[0]) and qfilter(*e[1])
for z in range(m1))
G.add_edges_from((label(*e[0]), label(*e[1])) for e in internal_couplers if efilter(e))

if edge_list is not None:
_add_compatible_edges(G, edge_list)
else:
G.add_edges_from(edge_list)

if node_list is not None:
nodes = set(node_list)
G.remove_nodes_from(set(G) - nodes)
G.add_nodes_from(nodes) # for singleton nodes
if check_node_list:
if G.number_of_nodes() != len(node_list):
raise ValueError("node_list contains nodes incompatible with "
"the specified topology and node-labeling "
"convention, or duplicates")

else:
G.add_nodes_from(nodes) # for singleton nodes

if data:
v = 0
Expand Down
54 changes: 40 additions & 14 deletions dwave_networkx/generators/zephyr.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,15 @@

from .chimera import _chimera_coordinates_cache

from .common import _add_compatible_edges

__all__ = ['zephyr_graph',
'zephyr_coordinates',
'zephyr_sublattice_mappings',
]


def zephyr_graph(m, t=4, create_using=None, node_list=None, edge_list=None,
data=True, coordinates=False):
data=True, coordinates=False, check_node_list=False, check_edge_list=False):
"""
Creates a Zephyr graph with grid parameter ``m`` and tile parameter ``t``.

Expand All @@ -47,22 +48,38 @@ def zephyr_graph(m, t=4, create_using=None, node_list=None, edge_list=None,
create_using : Graph, optional (default None)
If provided, this graph is cleared of nodes and edges and filled
with the new graph. Usually used to set the type of the graph.
node_list : iterable, optional (default None)
Iterable of nodes in the graph. If None, calculated from ``m``.
Note that this list is used to remove nodes, so only specified nodes
that belong to the base node set (described in the ``coordinates``
parameter) are added.
edge_list : iterable, optional (default None)
Iterable of edges in the graph. If None, edges are generated as
described below. The nodes in each edge must be labeled according to the
``coordinates`` parameter.
node_list : iterable (optional, default None)
Iterable of nodes in the graph. If None, calculated from (``m``, ``t``)
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
and ``coordinates``. The nodes should be compatible with the requested
coordinate system and topology bounds; by default integer-labeled
in :code:`range(4 * t * m * (2 * m + 1))`. Nodes incompatible with
the requested topology are accepted by default.
edge_list : iterable (optional, default None)
Iterable of edges in the graph. If None, calculated
from the ``node_list`` as described below.
Edges should be 2-tuples of nodes that match the
requested coordinate system and topology bounds. Edges are accepted by
default, provided component nodes are contained in the ``node_list``,
otherwise they are ignored.
data : bool, optional (default True)
If True, adds to each node an attribute with a format that depends on
the ``coordinates`` parameter: a 5-tuple ``'zephyr_index'`` if
``coordinates`` is False and an integer ``'linear_index'`` if ``coordinates``
is True.
coordinates : bool, optional (default False)
If True, node labels are 5-tuple Zephyr indices.
check_node_list : bool (optional, default False)
If True, the node_list elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
if any node is incompatible or duplicates exist.
In other words, only node_lists that specify subgraphs of the default
(full yield) graph are permitted.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
check_edge_list : bool (optional, default False)
If True, the edge_list elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
if any edge is incompatible or duplicates exist.
In other words, only edge_lists that specify subgraphs of the default
(full yield) graph are permitted.
jackraymond marked this conversation as resolved.
Show resolved Hide resolved

Returns
-------
Expand Down Expand Up @@ -163,7 +180,7 @@ def label(u, w, k, j, z):

G.graph.update(construction)

if edge_list is None:
if edge_list is None or check_edge_list is True:
#external edges
G.add_edges_from((label(u, w, k, j, z), label(u, w, k, j, z + 1))
for u, w, k, j, z in product(
Expand All @@ -182,14 +199,23 @@ def label(u, w, k, j, z):
for w, z, h, k, i, j, a, b in product(
range(m), range(m), range(t), range(t), (0, 1), (0, 1), (0, 1), (0, 1)
))

if edge_list is not None:
_add_compatible_edges(G, edge_list)
else:
G.add_edges_from(edge_list)

if node_list is not None:
nodes = set(node_list)
G.remove_nodes_from(set(G) - nodes)
G.add_nodes_from(nodes) # for singleton nodes
if check_node_list:
if G.number_of_nodes() != len(node_list):
raise ValueError("node_list contains nodes incompatible with "
"the specified topology and node-labeling "
"convention, or duplicates")


else:
G.add_nodes_from(nodes) # for singleton nodes

if data:
if coordinates:
Expand Down
69 changes: 69 additions & 0 deletions tests/test_generator_chimera.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,3 +282,72 @@ def check_subgraph_mapping(f, g, h):
covered.update(map(f, source))
self.assertEqual(covered, set(target))


def test_node_list(self):
m = 4
n = 3
t = 2
N = m*n*t*2
G = dnx.chimera_graph(m,n,t)
#Valid (full) node_list
node_list = list(G.nodes)
G = dnx.chimera_graph(m, n, t, node_list=node_list,
check_node_list=True)
self.assertEqual(G.number_of_nodes(), len(node_list))
#Valid node_list in coordinate system
node_list = [(0,0,0,0)]
G = dnx.chimera_graph(m, n, t, node_list=node_list,
check_node_list=True, coordinates=True)
self.assertEqual(G.number_of_nodes(), len(node_list))
with self.assertRaises(ValueError):
#Invalid node_list
node_list = [0, N]
G = dnx.chimera_graph(m, n, t, node_list=node_list,
check_node_list=True)
with self.assertRaises(ValueError):
# Invalid node_list due to duplicates
node_list = [0, 0]
G = dnx.chimera_graph(m, node_list=node_list,
check_node_list=True)

with self.assertRaises(ValueError):
#node is valid, but not in the requested coordinate system
node_list = [0]
G = dnx.chimera_graph(m, n, t, node_list=node_list,
check_node_list=True, coordinates=True)


def test_edge_list(self):
m = 2
n = 3
t = 4
G = dnx.chimera_graph(m, n, t)
edge_list = list(G.edges)
# Valid (full) edge_list
G = dnx.chimera_graph(m, n, t, edge_list=edge_list,
check_edge_list=True)
self.assertEqual(G.number_of_edges(),len(edge_list))
# Valid edge_list in coordinate system
edge_list = [((0,0,0,0),(0,0,1,0))]
G = dnx.chimera_graph(m, n, t, edge_list=edge_list,
check_edge_list=True, coordinates=True)
self.assertEqual(G.number_of_edges(),len(edge_list))

# Valid edge, but absent from node_list, hence dropped:
edge_list = [(0,t)]
node_list = list(range(t))
G = dnx.chimera_graph(m, n, t, edge_list=edge_list, node_list = node_list,
check_edge_list=True)
self.assertEqual(G.number_of_edges(),0)

with self.assertRaises(ValueError):
# Invalid edge_list (0,1) is a vertical-vertical coupler.
edge_list = [(0,t),(0,1)]
G = dnx.chimera_graph(m, n, t, edge_list=edge_list,
check_edge_list=True)

with self.assertRaises(ValueError):
# Edge list has duplicates
edge_list = [(0, t), (0, t)]
G = dnx.chimera_graph(m, edge_list=edge_list,
check_edge_list=True)
Loading