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

Zephyr layout #190

Merged
merged 1 commit into from
Nov 3, 2021
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
2 changes: 1 addition & 1 deletion minorminer/layout/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ def _parse_layout_parameter(S, T, layout, layout_kwargs):
T_layout = t_layout
else:
# Use the dnx_layout if possible
if T.graph.get("family") in ("chimera", "pegasus"):
if T.graph.get("family") in ("chimera", "pegasus", "zephyr"):
Copy link
Member

Choose a reason for hiding this comment

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

Perhaps create an Enum for known families? Should prevent typos.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I don't disagree, but to do that properly, we'd probably want to define that enum in dwave_networkx. How important do you think this is?

Copy link
Member

Choose a reason for hiding this comment

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

Makes sense. I don't think it's critical, as in a blocker of this PR. I'll open an issue.

Copy link

Choose a reason for hiding this comment

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

Slightly related; leaving this here so it doesn't get lost.

Chatting with Kelly we were considering the following unified scheme

topology = dnx.Topology("zephyr")
g = topology.generator(16)
topology.plotter(g)

to avoid tedious if elif logic that tends to appear everywhere:

if topology == 'pegasus':
    generator = pegasus_graph
    plotter = draw_pegasus
elif topology == 'zephyr':
    generator = zephyr_graph
    plotter = draw_zephyr

g = generator(5)
plotter(g)

T_layout = Layout(T, layout=dnx_layout, **layout_kwargs)
# Assumes t_layout a callable or implements a mapping interface
else:
Expand Down
10 changes: 7 additions & 3 deletions minorminer/layout/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -284,9 +284,10 @@ def dnx_layout(G, dim=None, center=None, scale=None, **kwargs):
graph_data = G.graph

family = graph_data.get("family")
if family not in ("chimera", "pegasus"):
if G.graph.get("family") not in ("chimera", "pegasus", "zephyr"):
raise ValueError(
"Only dnx.chimera_graph() and dnx.pegasus_graph() are supported.")
"This strategy is only implemented for Chimera, Pegasus"
" and Zephyr graphs constructed by dwave_networkx`.")

dim, center = _set_dim_and_center(dim, center)

Expand All @@ -302,6 +303,9 @@ def dnx_layout(G, dim=None, center=None, scale=None, **kwargs):
elif family == "pegasus":
dnx_layout = dnx.pegasus_layout(
G, dim=dim, center=dnx_center, scale=dnx_scale)
elif family == "zephyr":
dnx_layout = dnx.zephyr_layout(
G, dim=dim, center=dnx_center, scale=dnx_scale)

layout = Layout(G, dnx_layout)
return layout.layout
Expand Down Expand Up @@ -391,7 +395,7 @@ def __init__(
_call_layout = False
# If passed in, save or compute the layout
if layout is None:
if self.G.graph.get('family') in ('pegasus', 'chimera'):
if self.G.graph.get('family') in ('pegasus', 'chimera', 'zephyr'):
self.layout = dnx_layout(self.G, **kwargs)
else:
_call_layout = True
Expand Down
110 changes: 73 additions & 37 deletions minorminer/layout/placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from collections import Counter, abc, defaultdict

import networkx as nx
import dwave_networkx as dnx
import numpy as np
from scipy import spatial

Expand Down Expand Up @@ -70,10 +71,10 @@ def intersection(S_layout, T_layout, **kwargs):
# Extract the target graph
T = T_layout.G

# Currently only implemented for 2d chimera
if T.graph.get("family") not in ("chimera", "pegasus"):
if T.graph.get("family") not in ("chimera", "pegasus", "zephyr"):
raise NotImplementedError(
"This strategy is currently only implemented for Chimera.")
"This strategy is currently only implemented for Chimera, Pegasus"
" and Zephyr graphs constructed by dwave_networkx`.")

# Bin vertices of S and T into a grid graph G
G = _intersection_binning(S_layout, T)
Expand Down Expand Up @@ -104,6 +105,10 @@ def _intersection_binning(S_layout, T):
"""
# Scale the layout so that for each unit-cell edge, we have an integer point.
m, n, t = T.graph["rows"], T.graph["columns"], T.graph["tile"]
if T.graph['family'] == 'zephyr':
m = 2*m+1
n = 2*n+1
t = 2*t

# --- Make the "intersection graph" of the dnx_graph
# Grid points correspond to intersection rows and columns of the dnx_graph
Expand Down Expand Up @@ -147,54 +152,70 @@ def _lookup_intersection_coordinates(G):
"""For a dwave_networkx graph G, this returns a dictionary mapping the
lattice points to sets of vertices of G.

- Chimera: Each lattice point corresponds to the 2 qubits intersecting at
that point.
- Pegasus: Not Implemented

For Chimera, Pegasus and Zephyr, each lattice point corresponds to the 2
qubits intersecting at that point.
"""
graph_data = G.graph
family = graph_data.get("family")

data_key = None
intersection_points = defaultdict(set)
if family == "chimera":
t = graph_data.get("tile")
intersection_points = defaultdict(set)
shore = graph_data.get("tile")
collect_intersection_points = _chimera_all_intersection_points
if graph_data["labels"] == "coordinate":
for v in G:
_chimera_all_intersection_points(intersection_points, v, t, *v)

def get_coords(v):
return v
elif graph_data["data"]:
for v, d in G.nodes(data=True):
_chimera_all_intersection_points(
intersection_points, v, t, *d["chimera_index"])

data_key = "chimera_index"
else:
raise NotImplementedError("Please pass in a Chimera graph created"
" with an optional parameter 'data=True' or 'coordinates=True'")

return intersection_points

coords = dnx.chimera_coordinates(
graph_data['rows'],
n=graph_data['columns'],
t=shore
)
get_coords = coords.linear_to_chimera
elif family == "pegasus":
offsets = [graph_data['vertical_offsets'],
graph_data['horizontal_offsets']]

intersection_points = defaultdict(set)
shore = [graph_data['vertical_offsets'],
graph_data['horizontal_offsets']]
collect_intersection_points = _pegasus_all_intersection_points
if graph_data["labels"] == "coordinate":
for v in G:
_pegasus_all_intersection_points(intersection_points, offsets,
v, *v)
def get_coords(v):
return v
elif graph_data["data"]:
for v, d in G.nodes(data=True):
_pegasus_all_intersection_points(intersection_points, offsets,
v, *d["pegasus_index"])
data_key = "pegasus_index"
else:
raise NotImplementedError("Please pass in a Pegasus graph created"
" with an optional parameter 'data=True' or 'coordinates=True'")

return intersection_points
coords = dnx.pegasus_coordinates(graph_data['rows'])
if graph_data['labels'] == 'int':
get_coords = coords.linear_to_pegasus
elif graph_data['labels'] == 'nice':
get_coords = coords.nice_to_pegasus
elif family == "zephyr":
shore = graph_data.get("tile")
collect_intersection_points = _zephyr_all_intersection_points
if graph_data["labels"] == "coordinate":
def get_coords(v):
return v
elif graph_data["data"]:
data_key = "zephyr_index"
else:
coords = dnx.zephyr_coordinates(
graph_data['rows'],
t=shore
)
get_coords = coords.linear_to_zephyr

if data_key is None:
for v in G:
collect_intersection_points(
intersection_points, shore, v, *get_coords(v))
else:
for v, d in G.nodes(data=True):
collect_intersection_points(
intersection_points, shore, v, *d[data_key])

return intersection_points

def _chimera_all_intersection_points(intersection_points, v, t, i, j, u, k):
def _chimera_all_intersection_points(intersection_points, t, v, i, j, u, k):
"""Given a coordinate vertex, v = (i, j, u, k), of a Chimera with tile, t,
get all intersection points it is in.
"""
Expand Down Expand Up @@ -230,6 +251,21 @@ def _pegasus_all_intersection_points(intersection_points, offsets, v, u, w, k, z
for kk in range(12):
intersection_points[(col, row_0 + kk)].add(v)

def _zephyr_all_intersection_points(intersection_points, t, v, u, w, k, j, z):
"""Given a coordinate vertex, v = (u, w, k, j, z), of a Zephyr graph with tile
`t`, get all intersection points it is in.
"""
if u == 0:
row = 2*(t*w + k) + j
col_0 = (4*z + 2*j)*t
for kk in range(4*t):
intersection_points[(col_0 + kk, row)].add(v)

elif u == 1:
col = 2*(t*w + k) + j
row_0 = (4*z + 2*j)*t
for kk in range(4*t):
intersection_points[(col, row_0 + kk)].add(v)

def closest(S_layout, T_layout, subset_size=(1, 1), num_neighbors=1, **kwargs):
"""Maps vertices of S to the closest vertices of T as given by ``S_layout``
Expand Down
6 changes: 6 additions & 0 deletions minorminer/layout/tests/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ def __init__(self, *args, **kwargs):
self.P_coord = dnx.pegasus_graph(4, coordinates=True)
self.P_nice = dnx.pegasus_graph(4, nice_coordinates=True)
self.P_blank = dnx.pegasus_graph(4, data=False)
self.Z = dnx.zephyr_graph(4)
self.Z_coord = dnx.zephyr_graph(4, coordinates=True)
self.Z_blank = dnx.pegasus_graph(4, data=False)

# Compute some layouts
self.S_layout = mml.Layout(self.S)
Expand All @@ -52,6 +55,9 @@ def __init__(self, *args, **kwargs):
self.P_coord_layout = mml.Layout(self.P_coord)
self.P_nice_layout = mml.Layout(self.P_nice)
self.P_blank_layout = mml.Layout(self.P_blank)
self.Z_layout = mml.Layout(self.Z)
self.Z_coord_layout = mml.Layout(self.Z_coord)
self.Z_blank_layout = mml.Layout(self.Z_blank)

def assertArrayEqual(self, a, b):
"""
Expand Down
2 changes: 2 additions & 0 deletions minorminer/layout/tests/test_layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,8 @@ def test_dnx(self):
mml.dnx_layout(self.C)
# Pegasus
mml.dnx_layout(self.P)
# Zephyr
mml.dnx_layout(self.Z)

# Passing in dim
mml.dnx_layout(self.C, dim=dim)
Expand Down
25 changes: 13 additions & 12 deletions minorminer/layout/tests/test_placement.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,26 +35,24 @@ def test_intersection(self):
self.assertIsPlacement(self.S, self.C, placement)

# Test different scale ratios
S_layout = mml.Layout(self.S)
placement_1 = mml.intersection(
S_layout, self.C_layout, scale_ratio=.5)
S_layout = mml.Layout(self.S)
placement_2 = mml.intersection(
S_layout, self.C_layout, scale_ratio=1)
S_layout = mml.Layout(self.S)
placement_3 = mml.intersection(
S_layout, self.C_layout, scale_ratio=1.5)
self.assertIsPlacement(self.S, self.C, placement_1)
self.assertIsPlacement(self.S, self.C, placement_2)
self.assertIsPlacement(self.S, self.C, placement_3)

# Test a coordinate version of chimera
S_layout = mml.Layout(self.S)
# Test a coordinate variations of chimera
placement = mml.intersection(S_layout, self.C_coord_layout)
self.assertIsPlacement(self.S, self.C_coord, placement)

placement = mml.intersection(S_layout, self.C_blank_layout)
self.assertIsPlacement(self.S, self.C_blank, placement)

# Test coordinate variations of pegasus
S_layout = mml.Layout(self.S)
placement = mml.intersection(S_layout, self.P_coord_layout)
self.assertIsPlacement(self.S, self.P_coord, placement)

Expand All @@ -67,15 +65,18 @@ def test_intersection(self):
placement = mml.intersection(S_layout, self.P_nice_layout)
self.assertIsPlacement(self.S, self.P_nice, placement)

placement = mml.intersection(S_layout, self.P_blank_layout)
self.assertIsPlacement(self.S, self.P_blank, placement)

# Chimera must have data or coordinates
self.assertRaises(NotImplementedError, mml.intersection,
self.S_layout, self.C_blank_layout)
# Test coordinate variations of zephyr
placement = mml.intersection(S_layout, self.Z_coord_layout)
self.assertIsPlacement(self.S, self.Z_coord, placement)

# Pegasus must have data or coordinates
self.assertRaises(NotImplementedError, mml.intersection,
self.S_layout, self.P_blank_layout)
placement = mml.intersection(S_layout, self.Z_layout)
self.assertIsPlacement(self.S, self.Z, placement)

placement = mml.intersection(S_layout, self.Z_blank_layout)
self.assertIsPlacement(self.S, self.Z_blank, placement)

def test_closest(self):
"""
Expand Down