Skip to content

Commit 11a2def

Browse files
committed
added sensible size defaults to Chimera, Pegasus and Zephyr plots
1 parent f866ff6 commit 11a2def

File tree

4 files changed

+125
-47
lines changed

4 files changed

+125
-47
lines changed

dwave_networkx/drawing/chimera_layout.py

+28-11
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,14 @@
2020
import networkx as nx
2121
from networkx import draw
2222

23-
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield
23+
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect
2424
from dwave_networkx.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates
2525

2626

2727
__all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield']
2828

2929

30-
def chimera_layout(G, scale=1., center=None, dim=2):
30+
def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None):
3131
"""Positions the nodes of graph G in a Chimera cross topology.
3232
3333
NumPy (https://scipy.org) is required for this function.
@@ -51,6 +51,11 @@ def chimera_layout(G, scale=1., center=None, dim=2):
5151
Number of dimensions. When dim > 2, all extra dimensions are
5252
set to 0.
5353
54+
normalize_kwargs : None or dict (default None)
55+
A dict of keyword arguments to be used in a plotting function. If not
56+
None, we will populate the "ax" keyword with matplotlib axes, and the
57+
"node_size" and "width" keywords with defaults if they are not set.
58+
5459
Returns
5560
-------
5661
pos : dict
@@ -75,9 +80,9 @@ def chimera_layout(G, scale=1., center=None, dim=2):
7580
n = G.graph['columns']
7681
t = G.graph['tile']
7782
# get a node placement function
78-
xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim)
83+
xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs)
7984

80-
if G.graph.get('labels') == 'coordinate':
85+
if G.graph['labels'] == 'coordinate':
8186
pos = {v: xy_coords(*v) for v in G.nodes()}
8287
elif G.graph.get('data'):
8388
pos = {v: xy_coords(*dat['chimera_index']) for v, dat in G.nodes(data=True)}
@@ -97,15 +102,15 @@ def chimera_layout(G, scale=1., center=None, dim=2):
97102
m = max(idx[0] for idx in chimera_indices.values()) + 1
98103
n = max(idx[1] for idx in chimera_indices.values()) + 1
99104
t = max(idx[3] for idx in chimera_indices.values()) + 1
100-
xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim)
105+
xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs)
101106

102107
# compute our coordinates
103108
pos = {v: xy_coords(i, j, u, k) for v, (i, j, u, k) in chimera_indices.items()}
104109

105110
return pos
106111

107112

108-
def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2):
113+
def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwargs = None):
109114
"""Generates a function that converts Chimera indices to x, y
110115
coordinates for a plot.
111116
@@ -130,6 +135,11 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2):
130135
dim : int (default 2)
131136
Number of dimensions. When dim > 2, all extra dimensions are
132137
set to 0.
138+
139+
normalize_kwargs : None or dict (default None)
140+
A dict of keyword arguments to be used in a plotting function. If not
141+
None, we will populate the "ax" keyword with matplotlib axes, and the
142+
"node_size" and "width" keywords with defaults if they are not set.
133143
134144
Returns
135145
-------
@@ -144,8 +154,13 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2):
144154
center_pad = 1
145155
tile_center = t // 2
146156
tile_length = t + 2 + center_pad # 2 for spacing between tiles
157+
147158
# want the enter plot to fill in [0, 1] when scale=1
148-
scale /= max(m, n) * tile_length - 2 - center_pad
159+
fabric_scale = max(m, n) * tile_length - 2 - center_pad
160+
scale /= fabric_scale
161+
162+
if normalize_kwargs is not None:
163+
normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs)
149164

150165
grid_offsets = {}
151166

@@ -228,8 +243,8 @@ def draw_chimera(G, **kwargs):
228243
>>> plt.show() # doctest: +SKIP
229244
230245
"""
231-
draw_qubit_graph(G, chimera_layout(G), **kwargs)
232-
246+
layout = chimera_layout(G, normalize_kwargs = kwargs)
247+
draw_qubit_graph(G, layout, **kwargs)
233248

234249
def draw_chimera_embedding(G, *args, **kwargs):
235250
"""Draws an embedding onto the chimera graph G, according to layout.
@@ -282,7 +297,8 @@ def draw_chimera_embedding(G, *args, **kwargs):
282297
function. If `linear_biases` or `quadratic_biases` are provided,
283298
any provided `node_color` or `edge_color` arguments are ignored.
284299
"""
285-
draw_embedding(G, chimera_layout(G), *args, **kwargs)
300+
layout = chimera_layout(G, normalize_kwargs = kwargs)
301+
draw_embedding(G, layout, *args, **kwargs)
286302

287303

288304
def draw_chimera_yield(G, **kwargs):
@@ -325,5 +341,6 @@ def draw_chimera_yield(G, **kwargs):
325341
tile, and label attributes to be able to identify faulty qubits.")
326342

327343
perfect_graph = chimera_graph(m,n,t, coordinates=coordinates)
344+
layout = chimera_layout(perfect_graph, normalize_kwargs = kwargs)
345+
draw_yield(G, layout, perfect_graph, **kwargs)
328346

329-
draw_yield(G, chimera_layout(perfect_graph), perfect_graph, **kwargs)

dwave_networkx/drawing/pegasus_layout.py

+39-27
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import networkx as nx
2020
from networkx import draw
2121

22-
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield
22+
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect
2323
from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates
2424
from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d
2525

@@ -31,7 +31,7 @@
3131
]
3232

3333

34-
def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False):
34+
def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None):
3535
"""Positions the nodes of graph G in a Pegasus topology.
3636
3737
`NumPy <https://scipy.org>`_ is required for this function.
@@ -58,6 +58,11 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False):
5858
rather than L configuration. Ignored if G is defined with
5959
``nice_coordinates=True``.
6060
61+
normalize_kwargs : None or dict (default None)
62+
A dict of keyword arguments to be used in a plotting function. If not
63+
None, we will populate the "ax" keyword with matplotlib axes, and the
64+
"node_size" and "width" keywords with defaults if they are not set.
65+
6166
Returns
6267
-------
6368
pos : dict
@@ -75,28 +80,27 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False):
7580
if not isinstance(G, nx.Graph) or G.graph.get("family") != "pegasus":
7681
raise ValueError("G must be generated by dwave_networkx.pegasus_graph")
7782

78-
if G.graph.get('labels') == 'nice':
79-
m = 3*(G.graph['rows']-1)
80-
c_coords = chimera_node_placer_2d(m, m, 4, scale=scale, center=center, dim=dim)
81-
def xy_coords(t, y, x, u, k):
82-
return c_coords(3*y+2-t, 3*x+t, u, k)
83+
labels = G.graph.get('labels')
84+
xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses=(crosses or labels == 'nice_coordinates'), normalize_kwargs = normalize_kwargs)
85+
86+
if labels == 'coordinate':
8387
pos = {v: xy_coords(*v) for v in G.nodes()}
88+
elif G.graph['data']:
89+
pos = {v: xy_coords(*dat['pegasus_index']) for v, dat in G.nodes(data=True)}
8490
else:
85-
xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses=crosses)
86-
87-
if G.graph.get('labels') == 'coordinate':
88-
pos = {v: xy_coords(*v) for v in G.nodes()}
89-
elif G.graph.get('data'):
90-
pos = {v: xy_coords(*dat['pegasus_index']) for v, dat in G.nodes(data=True)}
91-
else:
92-
m = G.graph.get('rows')
93-
coord = pegasus_coordinates(m)
91+
m = G.graph['rows']
92+
coord = pegasus_coordinates(m)
93+
if labels == 'int':
9494
pos = {v: xy_coords(*coord.linear_to_pegasus(v)) for v in G.nodes()}
95+
elif labels == 'nice':
96+
pos = {v: xy_coords(*coord.nice_to_pegasus(v)) for v in G.nodes()}
97+
else:
98+
raise ValueError(f"Pegasus labeling {labels} not recognized")
9599

96100
return pos
97101

98102

99-
def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False):
103+
def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None):
100104
"""Generates a function to convert Pegasus indices to plottable coordinates.
101105
102106
Parameters
@@ -138,7 +142,11 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False):
138142
cross_shift = 2 if crosses else 0
139143

140144
# want the enter plot to fill in [0, 1] when scale=1
141-
scale /= m*tile_width - 2*odd_k_wobble - 1
145+
fabric_scale = m*tile_width - 2*odd_k_wobble - 1
146+
scale /= fabric_scale
147+
148+
if normalize_kwargs is not None:
149+
normalize_size_and_aspect(fabric_scale, 150, normalize_kwargs)
142150

143151
if center is None:
144152
center = np.zeros(dim)
@@ -219,11 +227,10 @@ def draw_pegasus(G, crosses=False, **kwargs):
219227
>>> plt.show() # doctest: +SKIP
220228
221229
"""
230+
layout = pegasus_layout(G, normalize_kwargs = kwargs)
231+
draw_qubit_graph(G, layout, **kwargs)
222232

223-
draw_qubit_graph(G, pegasus_layout(G, crosses=crosses), **kwargs)
224-
225-
226-
def draw_pegasus_embedding(G, *args, **kwargs):
233+
def draw_pegasus_embedding(G, *args, crosses=False, **kwargs):
227234
"""Draws an embedding onto Pegasus graph G.
228235
229236
Parameters
@@ -276,10 +283,10 @@ def draw_pegasus_embedding(G, *args, **kwargs):
276283
function. If ``linear_biases`` or ``quadratic_biases`` are provided,
277284
any provided ``node_color`` or ``edge_color`` arguments are ignored.
278285
"""
279-
crosses = kwargs.pop("crosses", False)
280-
draw_embedding(G, pegasus_layout(G, crosses=crosses), *args, **kwargs)
286+
layout = pegasus_layout(G, crosses=crosses, normalize_kwargs=kwargs)
287+
draw_embedding(G, layout, *args, **kwargs)
281288

282-
def draw_pegasus_yield(G, **kwargs):
289+
def draw_pegasus_yield(G, crosses=False, **kwargs):
283290
"""Draws the given graph G with highlighted faults, according to layout.
284291
285292
Parameters
@@ -302,6 +309,11 @@ def draw_pegasus_yield(G, **kwargs):
302309
fault_style : string, optional (default='dashed')
303310
Edge fault line style (solid|dashed|dotted|dashdot)
304311
312+
crosses: boolean (optional, default False)
313+
If True, :math:`K_{4,4}` subgraphs are shown in a cross
314+
rather than L configuration. Ignored if G is defined with
315+
``nice_coordinates=True``.
316+
305317
kwargs : optional keywords
306318
See networkx.draw_networkx() for a description of optional keywords,
307319
with the exception of the `pos` parameter which is not used by this
@@ -321,5 +333,5 @@ def draw_pegasus_yield(G, **kwargs):
321333

322334

323335
perfect_graph = pegasus_graph(m, offset_lists=offset_lists, coordinates=coordinates, nice_coordinates=nice)
324-
325-
draw_yield(G, pegasus_layout(perfect_graph), perfect_graph, **kwargs)
336+
layout = pegasus_layout(perfect_graph, crosses=crosses, normalize_kwargs=kwargs)
337+
draw_yield(G, layout, perfect_graph, **kwargs)

dwave_networkx/drawing/qubit_layout.py

+34
Original file line numberDiff line numberDiff line change
@@ -499,3 +499,37 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0),
499499
draw(perfect_graph, layout, nodelist=nodelist, edgelist=edgelist,
500500
node_color=unused_node_color, edge_color=unused_edge_color,
501501
**kwargs)
502+
503+
def normalize_size_and_aspect(scale, node_scale, kwargs):
504+
ax = kwargs.get('ax')
505+
if ax is None:
506+
try:
507+
import matplotlib.pyplot as plt
508+
except ImportError:
509+
raise ImportError("Matplotlib required for graph drawing")
510+
cf = plt.gcf()
511+
else:
512+
cf = ax.get_figure()
513+
cf.set_facecolor("w")
514+
if ax is None:
515+
if cf._axstack() is None:
516+
ax = cf.add_axes((0, 0, 1, 1))
517+
else:
518+
ax = cf.gca()
519+
kwargs['ax'] = ax
520+
ax.set_aspect(1)
521+
fig_scale = min(cf.get_figheight(), cf.get_figwidth())
522+
ax.set_axis_off()
523+
524+
if kwargs.get('node_size') is None:
525+
if kwargs.get("line_plot"):
526+
kwargs['node_size'] = 50*(fig_scale/scale)**2
527+
else:
528+
kwargs['node_size'] = node_scale*(fig_scale/scale)**2
529+
530+
if kwargs.get('width') is None:
531+
if kwargs.get("line_plot"):
532+
kwargs['width'] = 5*(fig_scale / scale)
533+
else:
534+
kwargs['width'] = 2*(fig_scale / scale)
535+

dwave_networkx/drawing/zephyr_layout.py

+24-9
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import networkx as nx
2020
from networkx import draw
2121

22-
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield
22+
from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect
2323
from dwave_networkx.generators.zephyr import zephyr_graph, zephyr_coordinates
2424

2525

@@ -30,7 +30,7 @@
3030
]
3131

3232

33-
def zephyr_layout(G, scale=1., center=None, dim=2):
33+
def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None):
3434
"""Positions the nodes of graph G in a Zephyr topology.
3535
3636
`NumPy <https://scipy.org>`_ is required for this function.
@@ -52,6 +52,11 @@ def zephyr_layout(G, scale=1., center=None, dim=2):
5252
Number of dimensions. When dim > 2, all extra dimensions are
5353
set to 0.
5454
55+
normalize_kwargs : None or dict (default None)
56+
A dict of keyword arguments to be used in a plotting function. If not
57+
None, we will populate the "ax" keyword with matplotlib axes, and the
58+
"node_size" and "width" keywords with defaults if they are not set.
59+
5560
Returns
5661
-------
5762
pos : dict
@@ -69,22 +74,22 @@ def zephyr_layout(G, scale=1., center=None, dim=2):
6974
if not isinstance(G, nx.Graph) or G.graph.get("family") != "zephyr":
7075
raise ValueError("G must be generated by dwave_networkx.zephyr_graph")
7176

72-
xy_coords = zephyr_node_placer_2d(G, scale, center, dim)
77+
xy_coords = zephyr_node_placer_2d(G, scale, center, dim, normalize_kwargs)
7378

74-
if G.graph.get('labels') == 'coordinate':
79+
if G.graph['labels'] == 'coordinate':
7580
pos = {v: xy_coords(*v) for v in G.nodes()}
76-
elif G.graph.get('data'):
81+
elif G.graph['data']:
7782
pos = {v: xy_coords(*dat['zephyr_index']) for v, dat in G.nodes(data=True)}
7883
else:
79-
m = G.graph.get('rows')
80-
t = G.graph.get('tile')
84+
m = G.graph['rows']
85+
t = G.graph['tile']
8186
coord = zephyr_coordinates(m, t)
8287
pos = {v: xy_coords(*coord.linear_to_zephyr(v)) for v in G.nodes()}
8388

8489
return pos
8590

8691

87-
def zephyr_node_placer_2d(G, scale=1., center=None, dim=2):
92+
def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = None):
8893
"""Generates a function to convert Zephyr indices to plottable coordinates.
8994
9095
Parameters
@@ -104,6 +109,11 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2):
104109
Number of dimensions. When dim > 2, all extra dimensions are
105110
set to 0.
106111
112+
normalize_kwargs : None or dict (default None)
113+
A dict of keyword arguments to be used in a plotting function. If not
114+
None, we will populate the "ax" keyword with matplotlib axes, and the
115+
"node_size" and "width" keywords with defaults if they are not set.
116+
107117
Returns
108118
-------
109119
xy_coords : function
@@ -141,7 +151,12 @@ def _xy_coords(u, w, k, j, z):
141151
return np.hstack((xy * scale, paddims)) + center
142152

143153
# want the enter plot to fill in [0, 1] when scale=1
144-
scale /= max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1)))
154+
fabric_scale = max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1)))
155+
scale /= fabric_scale
156+
157+
if normalize_kwargs is not None:
158+
normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs)
159+
145160
return _xy_coords
146161

147162
def draw_zephyr(G, **kwargs):

0 commit comments

Comments
 (0)