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

Add support for torus QPU graph variations #225

Merged
merged 15 commits into from
Dec 7, 2022
Merged
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
16 changes: 11 additions & 5 deletions docs/bibliography.rst
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
Bibliography
============

.. [NX] Aric A. Hagberg, Daniel A. Schult and Pieter J. Swart, “Exploring network structure, dynamics, and function using NetworkX”, in Proceedings of the 7th Python in Science Conference (SciPy2008), Gäel Varoquaux, Travis Vaught, and Jarrod Millman (Eds), (Pasadena, CA USA), pp. 11–15, Aug 2008
.. [NX] A. A. Hagberg, D. A. Schult and P. J. Swart, “Exploring network structure, dynamics, and function using NetworkX”, in Proceedings of the 7th Python in Science Conference (SciPy2008), Gäel Varoquaux, Travis Vaught, and Jarrod Millman (Eds), (Pasadena, CA USA), pp. 11–15, Aug 2008

.. [GD] Gogate & Dechter, "A Complete Anytime Algorithm for Treewidth", https://arxiv.org/abs/1207.4109
.. [GD] V. Gogate and R. Dechter, "A Complete Anytime Algorithm for Treewidth", https://arxiv.org/abs/1207.4109

.. [AL] Lucas, A. (2014). Ising formulations of many NP problems. Frontiers in Physics, Volume 2, Article 5.
.. [AL] A. Lucas (2014). Ising formulations of many NP problems. Frontiers in Physics, Volume 2, Article 5.

.. [FIA] Facchetti, G., Iacono G., and Altafini C. (2011). Computing global structural balance in large-scale signed social networks. PNAS, 108, no. 52, 20953-20958
.. [FIA] G. Facchetti, G. Iacono and C. Altafini (2011). Computing global structural balance in large-scale signed social networks. PNAS, 108, no. 52, 20953-20958

.. [DWMP] Dahl, E., "Programming the D-Wave: Map Coloring Problem", https://www.dwavesys.com/media/htfgw5bk/map-coloring-wp2.pdf
.. [DWMP] E. Dahl, "Programming the D-Wave: Map Coloring Problem", https://www.dwavesys.com/media/htfgw5bk/map-coloring-wp2.pdf

.. [BBRR] K. Boothby, P. Bunyk, J. Raymond and A. Roy (2019). Next-Generation Topology of D-Wave Quantum Processors. https://arxiv.org/abs/2003.00133

.. [BRK] K. Boothby, J. Raymond and A. D. King (2021). Zephyr Topology of D-Wave Quantum Processors. https://dwavesys.com/media/fawfas04/14-1056a-a_zephyr_topology_of_d-wave_quantum_processors.pdf

.. [RH] J. Raymond, R. Stevanovic, W. Bernoudy, K. Boothby, C. C. McGeoch, A. J. Berkley, P. Farré and A. D. King (2021). Hybrid quantum annealing for larger-than-QPU lattice-structured problems. https://arxiv.org/abs/2202.03044
12 changes: 12 additions & 0 deletions docs/reference/generators.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ D-Wave Systems
pegasus_graph
zephyr_graph



Example
~~~~~~~

Expand Down Expand Up @@ -48,6 +50,16 @@ the `find_chimera()` function to determine the Chimera indices.

Indices of a Chimera unit cell found by creating a lattice of size (1, 1, 4).

Toruses
-------

.. autosummary::
:toctree: generated/

chimera_torus
randomir marked this conversation as resolved.
Show resolved Hide resolved
pegasus_torus
zephyr_torus

Other Graphs
------------

Expand Down
3 changes: 2 additions & 1 deletion dwave_networkx/algorithms/coloring.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,8 @@ def _chromatic_number_upper_bound(G):
# be the largest eigenvalue of A. Then chi <= theta_1 + 1 with
# equality iff G is complete or an odd cycle.
# this is strictly better than brooks theorem
bound = math.ceil(max(np.linalg.eigvals(nx.to_numpy_array(G))))
# G is real symmetric, use eigvalsh for real valued output.
bound = math.ceil(max(np.linalg.eigvalsh(nx.to_numpy_array(G))))
else:
# we know it's connected
bound = n_nodes
Expand Down
20 changes: 3 additions & 17 deletions dwave_networkx/algorithms/elimination_ordering.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,7 @@ def treewidth_branch_and_bound(G, elimination_order=None, treewidth_upperbound=N

References
----------
.. [GD] Gogate & Dechter, "A Complete Anytime Algorithm for Treewidth",
https://arxiv.org/abs/1207.4109

Based on the algorithm presented in [GD]_
jackraymond marked this conversation as resolved.
Show resolved Hide resolved
"""
# empty graphs have treewidth 0 and the nodes can be eliminated in
# any order
Expand Down Expand Up @@ -875,7 +873,7 @@ def pegasus_elimination_order(n, coordinates=False):
"""Provides a variable elimination order for the Pegasus graph.

The treewidth of a Pegasus graph ``pegasus_graph(n)`` is lower-bounded by
:math:`12n-11` and upper bounded by :math:`12n-4` [bbrr]_ .
:math:`12n-11` and upper bounded by :math:`12n-4` [BBRR]_ .

Simple pegasus variable elimination order rules:

Expand All @@ -897,11 +895,6 @@ def pegasus_elimination_order(n, coordinates=False):
order : list
An elimination order that provides an upper bound on the treewidth.


.. [bbrr] Boothby, K., P. Bunky, J. Raymond, A. Roy. Next-Generation Topology
of D-Wave Quantum Processors. Technical Report, Februrary 2019.
https://www.dwavesys.com/resources/publications?type=white

"""
m = n
l = 12
Expand Down Expand Up @@ -932,7 +925,7 @@ def zephyr_elimination_order(m, t=4, coordinates=False):
"""Provides a variable elimination order for the zephyr graph.

The treewidth of a Zephyr graph ``zephyr_graph(m,t)`` is upper-bounded by
:math:`4tm+2t` and lower-bounded by :math:`4tm` [brk]_ .
:math:`4tm+2t` and lower-bounded by :math:`4tm` [BRK]_ .

Simple zephyr variable elimination rules:
- eliminate vertical qubits, one column at a time
Expand All @@ -953,13 +946,6 @@ def zephyr_elimination_order(m, t=4, coordinates=False):
order : list
An elimination order that achieves an upper bound on the treewidth.


References
----------
.. [brk] Boothby, Raymond, King, Zephyr Topology of D-Wave Quantum
Processors, October 2021.
https://dwavesys.com/media/fawfas04/14-1056a-a_zephyr_topology_of_d-wave_quantum_processors.pdf

"""
order = ([(0,w,k,j,z) for w in range(2*m+1) for k in range(t) for z in range(m) for j in range(2)]
+ [(1,w,k,j,z) for z in range(m) for j in range(2) for w in range(2*m+1) for k in range(t)])
Expand Down
145 changes: 124 additions & 21 deletions dwave_networkx/generators/chimera.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,14 +26,15 @@

from itertools import product

from .common import _add_compatible_edges
from .common import _add_compatible_nodes, _add_compatible_edges, _add_compatible_terms

__all__ = ['chimera_graph',
'chimera_coordinates',
'find_chimera_indices',
'chimera_to_linear',
'linear_to_chimera',
'chimera_sublattice_mappings',
'chimera_torus',
]


Expand Down Expand Up @@ -78,7 +79,8 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
the graph topology and node labeling conventions, and an error is thrown
if any node is incompatible or duplicates exist.
In other words, the ``node_list`` must specify a subgraph of the
full-yield graph described below.
full-yield graph described below. An exception is allowed if
``check_edge_list=False``, in which case any node in ``edge_list`` is treated as valid.
check_edge_list : bool (optional, default :code:`False`)
If :code:`True`, the ``edge_list`` elements are checked for compatibility with
the graph topology and node labeling conventions, an error is thrown
Expand All @@ -95,14 +97,14 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
A Chimera lattice is an m-by-n grid of Chimera tiles. Each Chimera
tile is itself a bipartite graph with shores of size t. The
connection in a Chimera lattice can be expressed using a node-indexing
notation (i,j,u,k) for each node.
notation (i, j, u, k) for each node.

* (i,j) indexes the (row, column) of the Chimera tile. i must be
between 0 and m-1, inclusive, and j must be between 0 and
n-1, inclusive.
* (i, j) indexes the (row, column) of the Chimera tile. i must be
between 0 and m - 1, inclusive, and j must be between 0 and
n - 1, inclusive.
* u=0 indicates the left-hand nodes in the tile, and u=1 indicates
the right-hand nodes.
* k=0,1,...,t-1 indexes nodes within either the left- or
* k=0, 1, ..., t - 1 indexes nodes within either the left- or
right-hand shores of a tile.

In this notation, two nodes (i, j, u, k) and (i', j', u', k') are
Expand Down Expand Up @@ -159,6 +161,11 @@ 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:
check_edge_list = False
if node_list is None:
check_node_list = False

if edge_list is None or check_edge_list is True:
if coordinates:
# tile edges
Expand All @@ -169,13 +176,13 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
for k1 in range(t))

# horizontal edges
G.add_edges_from(((i, j, 1, k), (i, j+1, 1, k))
G.add_edges_from(((i, j, 1, k), (i, j + 1, 1, k))
for i in range(m)
for j in range(n-1)
for j in range(n - 1)
for k in range(t))
# vertical edges
G.add_edges_from(((i, j, 0, k), (i+1, j, 0, k))
for i in range(m-1)
G.add_edges_from(((i, j, 0, k), (i + 1, j, 0, k))
for i in range(m - 1)
for j in range(n)
for k in range(t))
else:
Expand Down Expand Up @@ -204,20 +211,25 @@ def chimera_graph(m, n=None, t=None, create_using=None, node_list=None, edge_lis
_add_compatible_edges(G, edge_list)

else:
if check_node_list or node_list is None:
if coordinates:
G.add_nodes_from((i, j, u, k) for i in range(m)
for j in range(n)
for u in range(2)
for k in range(t))
else:
G.add_nodes_from(i for i in range(m*n*t*2))

G.add_edges_from(edge_list)

if node_list is not None:
nodes = set(node_list)
G.remove_nodes_from(set(G) - 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.")

_add_compatible_nodes(G, node_list)
else:
nodes = set(node_list)
G.remove_nodes_from(set(G) - nodes)
G.add_nodes_from(nodes) # for singleton nodes

if data:
if coordinates:
def checkadd(v, q):
Expand Down Expand Up @@ -634,7 +646,7 @@ def mapping(q):
y, x, u, k = source_to_chimera(q)
return chimera_to_target((y + y_offset, x + x_offset, u, k))

#store the offset in the mapping, so the user can reconstruct it
# store the offset in the mapping, so the user can reconstruct it
mapping.offset = offset

return mapping
Expand All @@ -656,7 +668,7 @@ def chimera_sublattice_mappings(source, target, offset_list=None):
tile parameters, and the mappings produced are not exhaustive. The mappings
take the form

``(y, x, u, k) -> (y+y_offset, x+x_offset, u, k)``
``(y, x, u, k) -> (y + y_offset, x + x_offset, u, k)``

preserving the orientation and tile index of nodes. We use the notation of
Chimera coordinates above, but either or both of the target graph may have
Expand Down Expand Up @@ -726,3 +738,94 @@ def chimera_to_target(q):
for offset in offset_list:
yield _chimera_sublattice_mapping(source_to_chimera, chimera_to_target, offset)

def chimera_torus(m, n=None, t=None, node_list=None, edge_list=None):
"""Creates a defect-free Chimera lattice of size :math:`(m, n, t)` subject to periodic boundary conditions.


Parameters
----------
m : int
Number of rows in the Chimera torus lattice.
If :math:`m<3` translational invariance already applies in the rows. If
:math:`m>=3` additional external couplers are added, reestablishing
translational invariance.
Connectivity of all horizontal qubits is :math:`min(m - 1, 2) + 2t`.
n : int (optional, default m)
Number of columns in the Chimera torus lattice.
If :math:`n<3` translational invariance already applies in the columns. If
:math:`n>=3` additional external couplers are added, reestablishing
translational invariance.
Connectivity of all vertical qubits is :math:`min(n - 1, 2) + 2t`.
t : int (optional, default 4)
Size of the shore within each Chimera tile.
node_list : iterable (optional, default None)
Iterable of nodes in the graph. If None, nodes are generated
for an undiluted torus calculated from ``m``, ``n`` and ``t``
as described below. The node list must describe a subset
of the torus nodes to be maintained in the graph
using the coordinate node labeling scheme.
edge_list : iterable (optional, default None)
Iterable of edges in the graph. If None, edges are generated
for an undiluted torus calculated from ``m``, ``n`` and ``t``
as described below. The edge list must describe
a subgraph of the torus, using the coordinate node labeling scheme.

Returns
-------
G : NetworkX Graph
A Chimera torus with shape (m, n, t), with Chimera coordinate node labels.


A Chimera torus is a generalization of the standard chimera graph
whereby degree-six connectivity is maintained, but the boundary
condition is modified to enforce an additional translational-invariance
symmetry [RH]_. Local connectivity in the Chimera torus
is identical to connectivity for chimera graph nodes away from the boundary.
The graph has :code:`V=8*m*n` nodes, and :code:`min(6, 4 + m)V//2 +
min(6, 4 + n)V/2` edges. With the standard :math:`K_{t, t}` Chimera tile definition,
any tile displacement :math:`(x, y)` modulo :math:`(m, n)`, rows and columns respectively,
that is, :code:`(i, j, u, k)` -> :code:`((i + x)%m, (i + y)%n, u, k)`,
defines an automorphism.

See :func:`.chimera_graph` for additional information.

Examples
========
>>> G = dnx.chimera_torus(3, 3, 4) # a 3x3 tile chimera graph (connectivity 6)
>>> len(G)
72
>>> any([len(list(G.neighbors(n))) != 6 for n in G.nodes])
False

"""
# Graph properties are by and large inherited from chimera_graph
G = chimera_graph(m=m, n=n, t=t, node_list=None, edge_list=None, data=True, coordinates=True)
if n is None:
n = G.graph['columns']
if t is None:
t = G.graph['tile']


# With modification of the boundary condition
if m>2:
# Wrapped around row external-coupler edges:
additional_edges = [((m - 1, j, 0, k), (0, j, 0, k))
for j in range(n)
for k in range(t)]
else:
additional_edges = []

if n>2:
# Wrapped around columns external-coupler edges:
additional_edges += [((i, n - 1, 1, k), (i, 0, 1, k))
for i in range(m)
for k in range(t)]

if len(additional_edges)>0:
G.add_edges_from(additional_edges)

_add_compatible_terms(G, node_list, edge_list)

G.graph['boundary_condition'] = 'torus'

return G
20 changes: 18 additions & 2 deletions dwave_networkx/generators/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,26 @@ def _add_compatible_edges(G, edge_list):
# 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")
raise ValueError("edge_list contains edges incompatible with G")
# 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.')

def _add_compatible_nodes(G, node_list):
if node_list is not None:
if not all(G.has_node(n) for n in node_list):
raise ValueError("node_list contains nodes incompatible with G")
nodes = set(node_list)
remove_nodes = set(G) - nodes
G.remove_nodes_from(remove_nodes)
if G.number_of_nodes() < len(node_list):
raise ValueError('node_list contains duplicates.')

def _add_compatible_terms(G, node_list, edge_list):
_add_compatible_edges(G, edge_list)
_add_compatible_nodes(G, node_list)
#Check node deletion hasn't caused edge deletion:
if edge_list is not None and len(edge_list) != G.number_of_edges():
raise ValueError('The edge_list contains nodes absent from the node_list')
Loading