From 5a2317fbc4996c2886a19b29475fd2ac1397932e Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 6 Dec 2021 18:26:54 -0800 Subject: [PATCH 1/7] normalized Chimera, Pegasus and Zephyr layouts to unit square --- dwave_networkx/drawing/chimera_layout.py | 20 +++++++------ dwave_networkx/drawing/pegasus_layout.py | 36 ++++++++++++------------ dwave_networkx/drawing/zephyr_layout.py | 29 +++++++++---------- tests/test_chimera_layout.py | 12 ++++++++ tests/test_pegasus_layout.py | 12 ++++++++ tests/test_zephyr_layout.py | 12 ++++++++ 6 files changed, 78 insertions(+), 43 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 598f0332..78c52f06 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -141,10 +141,11 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): """ import numpy as np + center_pad = 1 tile_center = t // 2 - tile_length = t + 3 # 1 for middle of cross, 2 for spacing between tiles + tile_length = t + 2 + center_pad # 2 for spacing between tiles # want the enter plot to fill in [0, 1] when scale=1 - scale /= max(m, n) * tile_length - 3 + scale /= max(m, n) * tile_length - 2 - center_pad grid_offsets = {} @@ -153,10 +154,11 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): else: center = np.asarray(center) - paddims = dim - 2 - if paddims < 0: + if dim < 2: raise ValueError("layout must have at least two dimensions") + paddims = np.zeros(dim - 2, dtype='float') + if len(center) != dim: raise ValueError("length of center coordinates must match dimension of layout") @@ -167,24 +169,24 @@ def _xy_coords(i, j, u, k): if k < tile_center: p = k else: - p = k + 1 + p = k + center_pad if u: - xy = np.array([tile_center, -1 * p]) + xy = np.array([tile_center, -1 * p], dtype='float') else: - xy = np.array([p, -1 * tile_center]) + xy = np.array([p, -1 * tile_center], dtype='float') # next offset the corrdinates based on the which tile if i > 0 or j > 0: if (i, j) in grid_offsets: xy += grid_offsets[(i, j)] else: - off = np.array([j * tile_length, -1 * i * tile_length]) + off = np.array([j * tile_length, -1 * i * tile_length], dtype='float') xy += off grid_offsets[(i, j)] = off # convention for Chimera-lattice pictures is to invert the y-axis - return np.hstack((xy * scale, np.zeros(paddims))) + center + return np.hstack((xy * scale, paddims)) + center return _xy_coords diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index e540b4d4..66a2b160 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -129,48 +129,48 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False): """ import numpy as np - m = G.graph.get('rows') - h_offsets = G.graph.get("horizontal_offsets") - v_offsets = G.graph.get("vertical_offsets") - tile_width = G.graph.get("tile") + m = G.graph['rows'] + h_offsets = G.graph["horizontal_offsets"] + v_offsets = G.graph["vertical_offsets"] + tile_width = G.graph["tile"] + odd_k_wobble = .05 tile_center = tile_width / 2 - .5 + cross_shift = 2 if crosses else 0 # want the enter plot to fill in [0, 1] when scale=1 - scale /= m * tile_width + scale /= m*tile_width - 2*odd_k_wobble - 1 if center is None: center = np.zeros(dim) else: center = np.asarray(center) - paddims = dim - 2 - if paddims < 0: + center[0] -= (cross_shift + odd_k_wobble)*scale + center[1] -= (cross_shift - odd_k_wobble)*scale + + if dim < 0: raise ValueError("layout must have at least two dimensions") + paddims = np.zeros(dim - 2) + if len(center) != dim: raise ValueError("length of center coordinates must match dimension of layout") - if crosses: - # adjustment for crosses - cross_shift = 2. - else: - cross_shift = 0. - def _xy_coords(u, w, k, z): # orientation, major perpendicular offset, minor perpendicular offset, parallel offset if k % 2: - p = -.1 + p = -odd_k_wobble else: - p = .1 + p = odd_k_wobble if u: - xy = np.array([z*tile_width+h_offsets[k] + tile_center, -tile_width*w-k-p+cross_shift]) + xy = np.array([z*tile_width+h_offsets[k] + tile_center, -tile_width*w-k-p+cross_shift], dtype='float') else: - xy = np.array([tile_width*w+k+p+cross_shift, -z*tile_width-v_offsets[k]-tile_center]) + xy = np.array([tile_width*w+k+p+cross_shift, -z*tile_width-v_offsets[k]-tile_center], dtype='float') # convention for Pegasus-lattice pictures is to invert the y-axis - return np.hstack((xy * scale, np.zeros(paddims))) + center + return np.hstack((xy * scale, paddims)) + center return _xy_coords diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index 7f22d6eb..9cab3569 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -113,40 +113,37 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2): """ import numpy as np - m = G.graph.get('rows') - tile_width = G.graph.get("tile") + m = G.graph['rows'] + tile_width = 2*G.graph["tile"] - # want the enter plot to fill in [0, 1] when scale=1 - scale /= m * tile_width + if dim < 2: + raise ValueError("layout must have at least two dimensions") + + paddims = np.zeros(dim - 2) if center is None: center = np.zeros(dim) else: center = np.asarray(center) - paddims = dim - 2 - if paddims < 0: - raise ValueError("layout must have at least two dimensions") - if len(center) != dim: raise ValueError("length of center coordinates must match dimension of layout") def _xy_coords(u, w, k, j, z): # orientation, major perpendicular offset, secondary perpendicular offset, minor perpendicular offset, parallel offset - W = 2*tile_width*w + 2*k + .625*j + .125 - Z = (2*z+j+1)*2*tile_width - .5 - + W = tile_width*w + 2*k + .625*j + Z = (2*z+j+1)*tile_width - .6875 if u: - xy = np.array([Z, -W]) + xy = np.array([Z, -W], dtype='float') else: - xy = np.array([W, -Z]) - + xy = np.array([W, -Z], dtype='float') - return np.hstack((xy * scale, np.zeros(paddims))) + center + return np.hstack((xy * scale, paddims)) + center + # want the enter plot to fill in [0, 1] when scale=1 + scale /= max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1))) return _xy_coords - def draw_zephyr(G, **kwargs): """Draws graph G in a Zephyr topology. diff --git a/tests/test_chimera_layout.py b/tests/test_chimera_layout.py index ef5544cd..4561ad10 100644 --- a/tests/test_chimera_layout.py +++ b/tests/test_chimera_layout.py @@ -116,3 +116,15 @@ def test_draw_overlapped_chimera_embedding(self): emb = {0: [1, 5], 1: [5, 9, 13], 2: [25, 29], 3: [17, 21]} dnx.draw_chimera_embedding(C, emb, overlapped_embedding=True) dnx.draw_chimera_embedding(C, emb, overlapped_embedding=True, show_labels=True) + + def test_layout_bounds(self): + for C in [dnx.chimera_graph(1, 1, 4), dnx.chimera_graph(2, 2, 2), dnx.chimera_graph(4, 4, 4), dnx.chimera_graph(2, 2, 8)]: + pos = dnx.chimera_layout(C) + minx = min(x for x, y in pos.values()) + miny = min(y for x, y in pos.values()) + maxx = max(x for x, y in pos.values()) + maxy = max(y for x, y in pos.values()) + self.assertAlmostEqual(minx, 0) + self.assertAlmostEqual(maxx, 1) + self.assertAlmostEqual(miny, -1) + self.assertAlmostEqual(maxy, 0) diff --git a/tests/test_pegasus_layout.py b/tests/test_pegasus_layout.py index c344b91d..e9507db2 100644 --- a/tests/test_pegasus_layout.py +++ b/tests/test_pegasus_layout.py @@ -102,3 +102,15 @@ def test_draw_overlapped_chimera_embedding(self): emb = {0: [12, 35], 1: [12, 31], 2: [32], 3: [14]} dnx.draw_pegasus_embedding(C, emb, overlapped_embedding=True) dnx.draw_pegasus_embedding(C, emb, overlapped_embedding=True, show_labels=True) + + def test_layout_bounds(self): + for P in [dnx.pegasus_graph(2, fabric_only=False), dnx.pegasus_graph(3, fabric_only=False), dnx.pegasus_graph(4, fabric_only=False)]: + pos = dnx.pegasus_layout(P) + minx = min(x for x, y in pos.values()) + miny = min(y for x, y in pos.values()) + maxx = max(x for x, y in pos.values()) + maxy = max(y for x, y in pos.values()) + self.assertAlmostEqual(minx, 0) + self.assertAlmostEqual(maxx, 1) + self.assertAlmostEqual(miny, -1) + self.assertAlmostEqual(maxy, 0) diff --git a/tests/test_zephyr_layout.py b/tests/test_zephyr_layout.py index f588800b..e7cbd64b 100644 --- a/tests/test_zephyr_layout.py +++ b/tests/test_zephyr_layout.py @@ -109,3 +109,15 @@ def test_draw_overlapped_zephyr_embedding(self): emb = {0: [1, 3], 1: [3, 128, 15], 2: [25, 140], 3: [17, 122]} dnx.draw_zephyr_embedding(C, emb, overlapped_embedding=True) dnx.draw_zephyr_embedding(C, emb, overlapped_embedding=True, show_labels=True) + + def test_layout_bounds(self): + for Z in [dnx.zephyr_graph(2), dnx.zephyr_graph(3), dnx.zephyr_graph(4), dnx.zephyr_graph(2, 8)]: + pos = dnx.zephyr_layout(Z) + minx = min(x for x, y in pos.values()) + miny = min(y for x, y in pos.values()) + maxx = max(x for x, y in pos.values()) + maxy = max(y for x, y in pos.values()) + self.assertAlmostEqual(minx, 0) + self.assertAlmostEqual(maxx, 1) + self.assertAlmostEqual(miny, -1) + self.assertAlmostEqual(maxy, 0) From d3afdb3c4350c9946dec2ae86a2a1da04b9363b8 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 6 Dec 2021 18:46:09 -0800 Subject: [PATCH 2/7] added sensible size defaults to Chimera, Pegasus and Zephyr plots --- dwave_networkx/drawing/chimera_layout.py | 39 ++++++++++---- dwave_networkx/drawing/pegasus_layout.py | 66 ++++++++++++++---------- dwave_networkx/drawing/qubit_layout.py | 34 ++++++++++++ dwave_networkx/drawing/zephyr_layout.py | 33 ++++++++---- 4 files changed, 125 insertions(+), 47 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 78c52f06..2e50c7a0 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -20,14 +20,14 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect from dwave_networkx.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates __all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield'] -def chimera_layout(G, scale=1., center=None, dim=2): +def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): """Positions the nodes of graph G in a Chimera cross topology. NumPy (https://scipy.org) is required for this function. @@ -51,6 +51,11 @@ def chimera_layout(G, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + normalize_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function. If not + None, we will populate the "ax" keyword with matplotlib axes, and the + "node_size" and "width" keywords with defaults if they are not set. + Returns ------- pos : dict @@ -75,9 +80,9 @@ def chimera_layout(G, scale=1., center=None, dim=2): n = G.graph['columns'] t = G.graph['tile'] # get a node placement function - xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim) + xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs) - if G.graph.get('labels') == 'coordinate': + if G.graph['labels'] == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} elif G.graph.get('data'): pos = {v: xy_coords(*dat['chimera_index']) for v, dat in G.nodes(data=True)} @@ -97,7 +102,7 @@ def chimera_layout(G, scale=1., center=None, dim=2): m = max(idx[0] for idx in chimera_indices.values()) + 1 n = max(idx[1] for idx in chimera_indices.values()) + 1 t = max(idx[3] for idx in chimera_indices.values()) + 1 - xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim) + xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs) # compute our coordinates pos = {v: xy_coords(i, j, u, k) for v, (i, j, u, k) in chimera_indices.items()} @@ -105,7 +110,7 @@ def chimera_layout(G, scale=1., center=None, dim=2): return pos -def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): +def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwargs = None): """Generates a function that converts Chimera indices to x, y coordinates for a plot. @@ -130,6 +135,11 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): dim : int (default 2) Number of dimensions. When dim > 2, all extra dimensions are set to 0. + + normalize_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function. If not + None, we will populate the "ax" keyword with matplotlib axes, and the + "node_size" and "width" keywords with defaults if they are not set. Returns ------- @@ -144,8 +154,13 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): center_pad = 1 tile_center = t // 2 tile_length = t + 2 + center_pad # 2 for spacing between tiles + # want the enter plot to fill in [0, 1] when scale=1 - scale /= max(m, n) * tile_length - 2 - center_pad + fabric_scale = max(m, n) * tile_length - 2 - center_pad + scale /= fabric_scale + + if normalize_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs) grid_offsets = {} @@ -228,8 +243,8 @@ def draw_chimera(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - draw_qubit_graph(G, chimera_layout(G), **kwargs) - + layout = chimera_layout(G, normalize_kwargs = kwargs) + draw_qubit_graph(G, layout, **kwargs) def draw_chimera_embedding(G, *args, **kwargs): """Draws an embedding onto the chimera graph G, according to layout. @@ -282,7 +297,8 @@ def draw_chimera_embedding(G, *args, **kwargs): function. If `linear_biases` or `quadratic_biases` are provided, any provided `node_color` or `edge_color` arguments are ignored. """ - draw_embedding(G, chimera_layout(G), *args, **kwargs) + layout = chimera_layout(G, normalize_kwargs = kwargs) + draw_embedding(G, layout, *args, **kwargs) def draw_chimera_yield(G, **kwargs): @@ -325,5 +341,6 @@ def draw_chimera_yield(G, **kwargs): tile, and label attributes to be able to identify faulty qubits.") perfect_graph = chimera_graph(m,n,t, coordinates=coordinates) + layout = chimera_layout(perfect_graph, normalize_kwargs = kwargs) + draw_yield(G, layout, perfect_graph, **kwargs) - draw_yield(G, chimera_layout(perfect_graph), perfect_graph, **kwargs) diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index 66a2b160..d3c82681 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -19,7 +19,7 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d @@ -31,7 +31,7 @@ ] -def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): +def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None): """Positions the nodes of graph G in a Pegasus topology. `NumPy `_ is required for this function. @@ -58,6 +58,11 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): rather than L configuration. Ignored if G is defined with ``nice_coordinates=True``. + normalize_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function. If not + None, we will populate the "ax" keyword with matplotlib axes, and the + "node_size" and "width" keywords with defaults if they are not set. + Returns ------- pos : dict @@ -75,28 +80,27 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): if not isinstance(G, nx.Graph) or G.graph.get("family") != "pegasus": raise ValueError("G must be generated by dwave_networkx.pegasus_graph") - if G.graph.get('labels') == 'nice': - m = 3*(G.graph['rows']-1) - c_coords = chimera_node_placer_2d(m, m, 4, scale=scale, center=center, dim=dim) - def xy_coords(t, y, x, u, k): - return c_coords(3*y+2-t, 3*x+t, u, k) + labels = G.graph.get('labels') + xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses=(crosses or labels == 'nice_coordinates'), normalize_kwargs = normalize_kwargs) + + if labels == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} + elif G.graph['data']: + pos = {v: xy_coords(*dat['pegasus_index']) for v, dat in G.nodes(data=True)} else: - xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses=crosses) - - if G.graph.get('labels') == 'coordinate': - pos = {v: xy_coords(*v) for v in G.nodes()} - elif G.graph.get('data'): - pos = {v: xy_coords(*dat['pegasus_index']) for v, dat in G.nodes(data=True)} - else: - m = G.graph.get('rows') - coord = pegasus_coordinates(m) + m = G.graph['rows'] + coord = pegasus_coordinates(m) + if labels == 'int': pos = {v: xy_coords(*coord.linear_to_pegasus(v)) for v in G.nodes()} + elif labels == 'nice': + pos = {v: xy_coords(*coord.nice_to_pegasus(v)) for v in G.nodes()} + else: + raise ValueError(f"Pegasus labeling {labels} not recognized") return pos -def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False): +def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None): """Generates a function to convert Pegasus indices to plottable coordinates. Parameters @@ -138,7 +142,11 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False): cross_shift = 2 if crosses else 0 # want the enter plot to fill in [0, 1] when scale=1 - scale /= m*tile_width - 2*odd_k_wobble - 1 + fabric_scale = m*tile_width - 2*odd_k_wobble - 1 + scale /= fabric_scale + + if normalize_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 150, normalize_kwargs) if center is None: center = np.zeros(dim) @@ -219,11 +227,10 @@ def draw_pegasus(G, crosses=False, **kwargs): >>> plt.show() # doctest: +SKIP """ + layout = pegasus_layout(G, normalize_kwargs = kwargs) + draw_qubit_graph(G, layout, **kwargs) - draw_qubit_graph(G, pegasus_layout(G, crosses=crosses), **kwargs) - - -def draw_pegasus_embedding(G, *args, **kwargs): +def draw_pegasus_embedding(G, *args, crosses=False, **kwargs): """Draws an embedding onto Pegasus graph G. Parameters @@ -276,10 +283,10 @@ def draw_pegasus_embedding(G, *args, **kwargs): function. If ``linear_biases`` or ``quadratic_biases`` are provided, any provided ``node_color`` or ``edge_color`` arguments are ignored. """ - crosses = kwargs.pop("crosses", False) - draw_embedding(G, pegasus_layout(G, crosses=crosses), *args, **kwargs) + layout = pegasus_layout(G, crosses=crosses, normalize_kwargs=kwargs) + draw_embedding(G, layout, *args, **kwargs) -def draw_pegasus_yield(G, **kwargs): +def draw_pegasus_yield(G, crosses=False, **kwargs): """Draws the given graph G with highlighted faults, according to layout. Parameters @@ -302,6 +309,11 @@ def draw_pegasus_yield(G, **kwargs): fault_style : string, optional (default='dashed') Edge fault line style (solid|dashed|dotted|dashdot) + crosses: boolean (optional, default False) + If True, :math:`K_{4,4}` subgraphs are shown in a cross + rather than L configuration. Ignored if G is defined with + ``nice_coordinates=True``. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -321,5 +333,5 @@ def draw_pegasus_yield(G, **kwargs): perfect_graph = pegasus_graph(m, offset_lists=offset_lists, coordinates=coordinates, nice_coordinates=nice) - - draw_yield(G, pegasus_layout(perfect_graph), perfect_graph, **kwargs) + layout = pegasus_layout(perfect_graph, crosses=crosses, normalize_kwargs=kwargs) + draw_yield(G, layout, perfect_graph, **kwargs) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index 7dffb996..acb8814d 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -499,3 +499,37 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), draw(perfect_graph, layout, nodelist=nodelist, edgelist=edgelist, node_color=unused_node_color, edge_color=unused_edge_color, **kwargs) + +def normalize_size_and_aspect(scale, node_scale, kwargs): + ax = kwargs.get('ax') + if ax is None: + try: + import matplotlib.pyplot as plt + except ImportError: + raise ImportError("Matplotlib required for graph drawing") + cf = plt.gcf() + else: + cf = ax.get_figure() + cf.set_facecolor("w") + if ax is None: + if cf._axstack() is None: + ax = cf.add_axes((0, 0, 1, 1)) + else: + ax = cf.gca() + kwargs['ax'] = ax + ax.set_aspect(1) + fig_scale = min(cf.get_figheight(), cf.get_figwidth()) + ax.set_axis_off() + + if kwargs.get('node_size') is None: + if kwargs.get("line_plot"): + kwargs['node_size'] = 50*(fig_scale/scale)**2 + else: + kwargs['node_size'] = node_scale*(fig_scale/scale)**2 + + if kwargs.get('width') is None: + if kwargs.get("line_plot"): + kwargs['width'] = 5*(fig_scale / scale) + else: + kwargs['width'] = 2*(fig_scale / scale) + diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index 9cab3569..7bce7b25 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -19,7 +19,7 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect from dwave_networkx.generators.zephyr import zephyr_graph, zephyr_coordinates @@ -30,7 +30,7 @@ ] -def zephyr_layout(G, scale=1., center=None, dim=2): +def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None): """Positions the nodes of graph G in a Zephyr topology. `NumPy `_ is required for this function. @@ -52,6 +52,11 @@ def zephyr_layout(G, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + normalize_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function. If not + None, we will populate the "ax" keyword with matplotlib axes, and the + "node_size" and "width" keywords with defaults if they are not set. + Returns ------- pos : dict @@ -69,22 +74,22 @@ def zephyr_layout(G, scale=1., center=None, dim=2): if not isinstance(G, nx.Graph) or G.graph.get("family") != "zephyr": raise ValueError("G must be generated by dwave_networkx.zephyr_graph") - xy_coords = zephyr_node_placer_2d(G, scale, center, dim) + xy_coords = zephyr_node_placer_2d(G, scale, center, dim, normalize_kwargs) - if G.graph.get('labels') == 'coordinate': + if G.graph['labels'] == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} - elif G.graph.get('data'): + elif G.graph['data']: pos = {v: xy_coords(*dat['zephyr_index']) for v, dat in G.nodes(data=True)} else: - m = G.graph.get('rows') - t = G.graph.get('tile') + m = G.graph['rows'] + t = G.graph['tile'] coord = zephyr_coordinates(m, t) pos = {v: xy_coords(*coord.linear_to_zephyr(v)) for v in G.nodes()} return pos -def zephyr_node_placer_2d(G, scale=1., center=None, dim=2): +def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = None): """Generates a function to convert Zephyr indices to plottable coordinates. Parameters @@ -104,6 +109,11 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + normalize_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function. If not + None, we will populate the "ax" keyword with matplotlib axes, and the + "node_size" and "width" keywords with defaults if they are not set. + Returns ------- xy_coords : function @@ -141,7 +151,12 @@ def _xy_coords(u, w, k, j, z): return np.hstack((xy * scale, paddims)) + center # want the enter plot to fill in [0, 1] when scale=1 - scale /= max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1))) + fabric_scale = max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1))) + scale /= fabric_scale + + if normalize_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs) + return _xy_coords def draw_zephyr(G, **kwargs): From 52e2903878abe327cd1875ab7dbb4787eadc0448 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 6 Dec 2021 20:03:29 -0800 Subject: [PATCH 3/7] reworked yield_plot to be more readable --- dwave_networkx/drawing/chimera_layout.py | 8 ++-- dwave_networkx/drawing/pegasus_layout.py | 7 +++- dwave_networkx/drawing/qubit_layout.py | 53 ++++++++++++------------ dwave_networkx/drawing/zephyr_layout.py | 5 ++- 4 files changed, 40 insertions(+), 33 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 2e50c7a0..62619d29 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -314,10 +314,13 @@ def draw_chimera_yield(G, **kwargs): If unused_color is None, these nodes and edges will not be shown at all. fault_color : tuple or color string (optional, default (1.0,0.0,0.0,1.0)) - A color to represent nodes absent from the graph G. Colors should be + A color to represent nodes absent from the graph G. + + incident_fault_color : tuple or color string (optional, default (1.0,0.8,0.8,1.0)) + A color to represent edges incident to faulty nodes. Colors should be length-4 tuples of floats between 0 and 1 inclusive. - fault_shape : string, optional (default='x') + fault_shape : string, optional (default='o') The shape of the fault nodes. Specification is as matplotlib.scatter marker, one of 'so^>vvv Date: Mon, 6 Dec 2021 20:08:40 -0800 Subject: [PATCH 4/7] added line_plot option --- dwave_networkx/drawing/chimera_layout.py | 44 +++- dwave_networkx/drawing/pegasus_layout.py | 52 ++++- dwave_networkx/drawing/qubit_layout.py | 286 ++++++++++++++++++++--- dwave_networkx/drawing/zephyr_layout.py | 54 ++++- 4 files changed, 391 insertions(+), 45 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 62619d29..985b22e7 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -20,7 +20,7 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect, draw_lineplot from dwave_networkx.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates @@ -151,7 +151,10 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwar """ import numpy as np - center_pad = 1 + line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + + center_pad = 0 if line_plot else 1 + tile_center = t // 2 tile_length = t + 2 + center_pad # 2 for spacing between tiles @@ -203,7 +206,18 @@ def _xy_coords(i, j, u, k): # convention for Chimera-lattice pictures is to invert the y-axis return np.hstack((xy * scale, paddims)) + center - return _xy_coords + if line_plot: + qubit_dx = np.hstack(([(t + 1)/2, 0], paddims)) * scale + qubit_dy = np.hstack(([0, (t + 1)/2], paddims)) * scale + def _line_coords(i, j, u, k): + xy = _xy_coords(i, j, u, k) + if u: + return np.vstack((xy - qubit_dx, xy + qubit_dx)) + else: + return np.vstack((xy - qubit_dy, xy + qubit_dy)) + return _line_coords + else: + return _xy_coords def draw_chimera(G, **kwargs): @@ -226,6 +240,14 @@ def draw_chimera(G, **kwargs): form {edge: bias, ...}. Each bias should be numeric. Self-loop edges (i.e., :math:`i=j`) are treated as linear biases. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -291,6 +313,14 @@ def draw_chimera_embedding(G, *args, **kwargs): the same vertices in G), and the drawing will display these overlaps as concentric circles. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -327,6 +357,14 @@ def draw_chimera_yield(G, **kwargs): fault_style : string, optional (default='dashed') Edge fault line style (solid|dashed|dotted|dashdot) + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index 33c1d82c..c2bc70fe 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -19,7 +19,7 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect, draw_lineplot from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d @@ -133,6 +133,11 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, norma """ import numpy as np + line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + + if line_plot: + crosses = False + m = G.graph['rows'] h_offsets = G.graph["horizontal_offsets"] v_offsets = G.graph["vertical_offsets"] @@ -180,7 +185,19 @@ def _xy_coords(u, w, k, z): # convention for Pegasus-lattice pictures is to invert the y-axis return np.hstack((xy * scale, paddims)) + center - return _xy_coords + + if line_plot: + qubit_dx = np.hstack(([5.75, 0], paddims)) * scale + qubit_dy = np.hstack(([0, 5.75], paddims)) * scale + def _line_coords(u, w, k, z): + xy = _xy_coords(u, w, k, z) + if u: + return np.vstack((xy - qubit_dx, xy + qubit_dx)) + else: + return np.vstack((xy - qubit_dy, xy + qubit_dy)) + return _line_coords + else: + return _xy_coords def draw_pegasus(G, crosses=False, **kwargs): @@ -207,7 +224,16 @@ def draw_pegasus(G, crosses=False, **kwargs): crosses: boolean (optional, default False) If True, :math:`K_{4,4}` subgraphs are shown in a cross rather than L configuration. Ignored if G is defined with - ``nice_coordinates=True``. + ``nice_coordinates=True`` or ``line_plot=True``. + + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -277,6 +303,15 @@ def draw_pegasus_embedding(G, *args, crosses=False, **kwargs): If True, chains in ``emb`` may overlap (contain the same vertices in G), and these overlaps are displayed as concentric circles. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the ``pos`` parameter, which is not used by this @@ -315,7 +350,16 @@ def draw_pegasus_yield(G, crosses=False, **kwargs): crosses: boolean (optional, default False) If True, :math:`K_{4,4}` subgraphs are shown in a cross rather than L configuration. Ignored if G is defined with - ``nice_coordinates=True``. + ``nice_coordinates=True`` or ``line_plot=True``. + + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index e23f1b12..fbf17c20 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -23,13 +23,16 @@ from networkx import draw from dwave_networkx.drawing.distinguishable_colors import distinguishable_color_map +from itertools import repeat, chain + +from numbers import Number __all__ = ['draw_qubit_graph'] def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, nodelist=None, edgelist=None, cmap=None, edge_cmap=None, vmin=None, vmax=None, - edge_vmin=None, edge_vmax=None, midpoint=None, + edge_vmin=None, edge_vmax=None, midpoint=None, line_plot=False, **kwargs): """Draws graph G according to layout. @@ -60,6 +63,16 @@ def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, be. If not provided, the colormap will default to the middle of min/max values provided. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. Qubit line segments are given + twice the width of edges. Layout should be a dict of the form + {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes + are associated with endpoints of n-dimensional line segments. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -178,15 +191,21 @@ def node_color(v): if ax is None: ax = fig.add_axes([0.01, 0.01, 0.98, 0.98]) - draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, - cmap=cmap, edge_cmap=edge_cmap, vmin=vmin, vmax=vmax, edge_vmin=edge_vmin, - edge_vmax=edge_vmax, - **kwargs) + if line_plot: + draw_lineplot(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, + cmap=cmap, edge_cmap=edge_cmap, vmin=vmin, vmax=vmax, edge_vmin=edge_vmin, + edge_vmax=edge_vmax, + **kwargs) + else: + draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, + cmap=cmap, edge_cmap=edge_cmap, vmin=vmin, vmax=vmax, edge_vmin=edge_vmin, + edge_vmax=edge_vmax, + **kwargs) def draw_embedding(G, layout, emb, embedded_graph=None, interaction_edges=None, chain_color=None, unused_color=(0.9,0.9,0.9,1.0), cmap=None, - show_labels=False, overlapped_embedding=False, **kwargs): + show_labels=False, overlapped_embedding=False, line_plot=False, **kwargs): """Draws an embedding onto the graph G, according to layout. If interaction_edges is not None, then only display the couplers in that @@ -240,6 +259,16 @@ def draw_embedding(G, layout, emb, embedded_graph=None, interaction_edges=None, the same vertices in G), and the drawing will display these overlaps as concentric circles. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. Qubit line segments are given + twice the width of edges. Layout should be a dict of the form + {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes + are associated with endpoints of n-dimensional line segments. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -252,6 +281,12 @@ def draw_embedding(G, layout, emb, embedded_graph=None, interaction_edges=None, except ImportError: raise ImportError("Matplotlib and numpy required for draw_chimera()") + if line_plot and show_labels: + raise NotImplementedError("line_plot style drawings do not currently support node labels") + + if line_plot and overlapped_embedding: + raise NotImplementedError("line_plot style drawings do not currently support overlapped embeddings") + if nx.utils.is_string_like(unused_color): from matplotlib.colors import colorConverter alpha = kwargs.get('alpha', 1.0) @@ -371,15 +406,25 @@ def show(p, q, u, v): return True c = emb[v] labels[list(c)[0]] = str(v) - # draw the background (unused) graph first - if unused_color is not None: - draw(G, layout, nodelist=nodelist, edgelist=background_edgelist, - node_color=node_color, edge_color=background_edge_color, - **kwargs) + if line_plot: + if unused_color is not None: + draw_lineplot(G, layout, nodelist=nodelist, edgelist=background_edgelist, + node_color=node_color, edge_color=background_edge_color, + **kwargs) - draw(G, layout, nodelist=nodelist, edgelist=edgelist, - node_color=node_color, edge_color=edge_color, labels=labels, - **kwargs) + draw_lineplot(G, layout, nodelist=nodelist, edgelist=edgelist, + node_color=node_color, edge_color=edge_color, z_offset = 10, + **kwargs) + else: + # draw the background (unused) graph first + if unused_color is not None: + draw(G, layout, nodelist=nodelist, edgelist=background_edgelist, + node_color=node_color, edge_color=background_edge_color, + **kwargs) + + draw(G, layout, nodelist=nodelist, edgelist=edgelist, + node_color=node_color, edge_color=edge_color, labels=labels, + **kwargs) def compute_bags(C, emb): @@ -426,7 +471,7 @@ def unoverlapped_embedding(G, emb, interaction_edges): def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), fault_color=(1.0,0.0,0.0,1.0), fault_shape='o', fault_style='dashed', incident_fault_color=(1.0,0.8,0.8,1.0), - **kwargs): + line_plot = False, **kwargs): """Draws the given graph G with highlighted faults, according to layout. Parameters @@ -461,6 +506,16 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), fault_style : string, optional (default='dashed') Edge fault line style (solid|dashed|dotted,dashdot) + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. Qubit line segments are given + twice the width of edges. Layout should be a dict of the form + {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes + are associated with endpoints of n-dimensional line segments. + kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -481,22 +536,36 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), incident_edgelist = edgeset(perfect_graph.edges) - edgeset(perfect_graph.subgraph(nodelist).edges) faults_edgelist = edgeset(perfect_graph.subgraph(nodelist).edges) - edgeset(G.edges) - faults_node_color = [fault_color for v in faults_nodelist] - faults_edge_color = [fault_color for e in faults_edgelist] - incident_edge_color = [incident_fault_color for e in incident_edgelist] + if line_plot: + if unused_color is not None: + node_color = [fault_color if v in faults_nodelist else unused_color for v in perfect_graph] + long_edgelist = list(edgeset(perfect_graph.edges) - incident_edgelist - faults_edgelist) + else: + node_color = [] + long_edgelist = [] + edge_color = [unused_color]*len(long_edgelist) + long_edgelist.extend(incident_edgelist) + edge_color.extend([incident_fault_color]*len(incident_edgelist)) + long_edgelist.extend(faults_edgelist) + edge_color.extend([fault_color]*len(faults_edgelist)) + draw_lineplot(perfect_graph, layout, edgelist=long_edgelist, node_color=node_color, edge_color=edge_color, **kwargs) + else: + faults_node_color = [fault_color for v in faults_nodelist] + faults_edge_color = [fault_color for e in faults_edgelist] + incident_edge_color = [incident_fault_color for e in incident_edgelist] - # Draw edges first, in the order (unused, incident, faults) - if unused_color is not None: - unused_edge_color = [unused_color for e in G.edges()] - nx.draw_networkx_edges(G, layout, edge_color=unused_edge_color, **kwargs) - nx.draw_networkx_edges(perfect_graph, layout, incident_edgelist, style=fault_style, edge_color=incident_edge_color, **kwargs) - nx.draw_networkx_edges(perfect_graph, layout, faults_edgelist, style=fault_style, edge_color=faults_edge_color, **kwargs) + # Draw edges first, in the order (unused, incident, faults) + if unused_color is not None: + unused_edge_color = [unused_color for e in G.edges()] + nx.draw_networkx_edges(G, layout, edge_color=unused_edge_color, **kwargs) + nx.draw_networkx_edges(perfect_graph, layout, incident_edgelist, style=fault_style, edge_color=incident_edge_color, **kwargs) + nx.draw_networkx_edges(perfect_graph, layout, faults_edgelist, style=fault_style, edge_color=faults_edge_color, **kwargs) - # Draw nodes second, in the order (unused, faults) - if unused_color is not None: - unused_node_color = [unused_color for e in G] - nx.draw_networkx_nodes(G, layout, node_color = unused_node_color, **kwargs) - nx.draw_networkx_nodes(perfect_graph, layout, faults_nodelist, node_shape=fault_shape, node_color=faults_node_color, **kwargs) + # Draw nodes second, in the order (unused, faults) + if unused_color is not None: + unused_node_color = [unused_color for e in G] + nx.draw_networkx_nodes(G, layout, node_color = unused_node_color, **kwargs) + nx.draw_networkx_nodes(perfect_graph, layout, faults_nodelist, node_shape=fault_shape, node_color=faults_node_color, **kwargs) def normalize_size_and_aspect(scale, node_scale, kwargs): @@ -532,3 +601,162 @@ def normalize_size_and_aspect(scale, node_scale, kwargs): else: kwargs['width'] = 2*(fig_scale / scale) + +def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, + edgelist=None, node_color='blue', edge_color='black', + cmap=None, vmin=None, vmax=None, edge_cmap=None, + edge_vmin=None, edge_vmax=None, z_offset=0): + """Draws the graph G with line segments representing nodes + + This function is meant to be a drop-in replacement for :func:`networkx.draw` + where nodes are associated with line segments (specified as 2x2 matrices + [[x0, y0], [x1, y1]]). This function makes significant assumptions about + the edges of the graph G, that hold when G is a Chimera, Pegasus, or Zephyr + graph and the line segments are provided by :func:`chimera_layout`, + :func:`pegasus_layout` and :func:`zephyr_layout` respectively. These graphs + have three classes of edges: + + * internal edges between qubits whose line segments are perpendicular + and intersect at a point, which we draw with a circle located at the + point of intersection, + * external edges between qubits whose line segments are colinear, which + we draw as a line segment between the nearest endpoints, and + * odd edges between parallel qubits whose line segments are parallel and + overlap in a perpendicular projection, which we draw as a line segment + between the midpoints of the respective perpendicular projections + + Parameters + ---------- + + G : networkx.Graph + A graph constructed by :func:`chimera_layout`, :func:`pegasus_layout, or + :func:`zephyr_layout` + + pos : dict + A dictionary with nodes as keys and 2x2 matrices [[x0, y0], [x1, y1]] + representing the line segments of nodes. + + ax : matplotlib.Axis + The matplotlib Axis object to draw the graph on. + + node_size : float + The size (in area) of the circles used to depict internal edges. + + width : float + The width of line segments associated with edges, and half the width of + line segments associated with nodes. + + nodelist : iterable or None (default=None) + The set of nodes to draw. If None, all nodes from G are drawn. + + edgelist : iterable or None (default=None) + The set of edges to draw. If both nodelist and edgelist are None, all + edges of G are drawn. If edgelist is None, all edges from the subgraph + ``G.subgraph(nodelist)`` are drawn. + + node_color : iterable or string (default='blue') + The sequence of colors to use in drawing nodes of G. If node_color is + not a string, the colors are taken in the same order as nodelist, and + each color is either a float, a 3-tuple or 4-tuple of floats. + + edge_color : iterable or string (default='black') + The sequence of colors to use in drawing edges of G. If edge_color is + not a string, the colors are taken in the same order as edgelist, and + each color is either a float, a 3-tuple or 4-tuple of floats. + + cmap : string or matplotlib.ColorMap or None (default=None) + A colormap to color nodes with. Presumes that node_color is a sequence + of floats. + + vmin : float or None (default=None) + Minimum value to use to use when normalizing node colors through cmap. + + vmax : float or None (default=None) + Maximum value to use to use when normalizing node colors through cmap. + + edge_cmap : string or matplotlib.ColorMap or None (default=None) + A colormap to color edges with. Presumes that edge_color is a sequence + of floats. + + edge_vmin : float or None (default=None) + Minimum value to use to use when normalizing edge colors through + edge_cmap + + edge_vmax : float or None (default=None) + Maximum value to use to use when normalizing edge colors through + edge_cmap + + z_offset : int (default=0) + An offset to the zorder that various elements are drawn in. Edge lines + are drawn with zorder=z_offset; horizontal node lines are drawn with + zorder=zoffset+1; vertical node lines are drawn with zorder=zoffset+2, + and edge circles are drawn with zorder=zoffset+3. This parameter can be + used to layer line plots over or under eachother. + """ + + from networkx.drawing.nx_pylab import apply_alpha + import numpy as np + from matplotlib.collections import LineCollection, CircleCollection + + if not isinstance(node_size, Number) or not isinstance(width, Number): + raise NotImplementedError("Varying node size and edge width per element in line plots is not implemented") + + if edgelist is None: + if nodelist is not None: + edgelist = G.subgraph(nodelist).edges + else: + edgelist = G.edges + if nodelist is None: + nodelist = G + + node_color = apply_alpha(node_color, 1, nodelist, cmap=cmap, vmin=vmin, vmax=vmax) + node_lines = np.array([layout[v] for v in nodelist], dtype='float') + vertical = np.array([abs(x0-x1) < abs(y0-y1) for (x0, y0), (x1, y1) in node_lines], dtype='bool') + if node_color.shape == (1, 4): + vcolor = hcolor = node_color + else: + vcolor = node_color[vertical] + hcolor = node_color[~vertical] + ax.add_collection(LineCollection(node_lines[~vertical], edgecolor=hcolor, linewidths=width, zorder=1+z_offset, capstyle='round')) + ax.add_collection(LineCollection(node_lines[vertical], edgecolor=vcolor, linewidths=width, zorder=2+z_offset, capstyle='round')) + + if edge_color is not None and len(edgelist): + vertical = dict(zip(nodelist, map(int, vertical))) + as_line = np.full(len(edgelist), False, dtype='bool') + edge_data = np.empty((len(edgelist), 2, 2), dtype='float') + for i, (u, v) in enumerate(edgelist): + u0, u1 = layout[u] + v0, v1 = layout[v] + orientation = vertical[u] + if orientation == vertical[v]: + as_line[i] = True + if u0[orientation] > v1[orientation]: + # external; v1 < u0 + edge_data[i] = v1, u0 + elif v0[orientation] > u1[orientation]: + # external; u1 < v0 + edge_data[i] = u1, v0 + elif orientation: + # odd, vertical + ymean = (u0[1] + u1[1] + v0[1] + v1[1])/4 + edge_data[i] = (u0[0], ymean), (v0[0], ymean) + else: + # odd, horizontal + xmean = (u0[0] + u1[0] + v0[0] + v1[0])/4 + edge_data[i] = (xmean, u0[1]), (xmean, v0[1]) + elif orientation: + # internal, u is vertical and v is horizontal + edge_data[i, 0] = u0[0], v0[1] + else: + # internal, v is vertical and u is horizontal + edge_data[i, 0] = v0[0], u0[1] + + edge_color = apply_alpha(edge_color, 1, edgelist, cmap=edge_cmap, vmin=edge_vmin, vmax=edge_vmax) + if edge_color.shape == (1, 4): + edge_line_color = edge_spot_color = edge_color + else: + edge_line_color = edge_color[as_line] + edge_spot_color = edge_color[~as_line] + ax.add_collection(LineCollection(edge_data[as_line], edgecolor=edge_line_color, linewidths=width/2, zorder=z_offset)) + ax.scatter(*edge_data[~as_line, 0].T, c=edge_spot_color, zorder=3+z_offset, s=node_size) + ax.autoscale_view() diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index 6acd4910..a56c7dfc 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -19,7 +19,7 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, normalize_size_and_aspect, draw_lineplot from dwave_networkx.generators.zephyr import zephyr_graph, zephyr_coordinates @@ -123,6 +123,8 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = No """ import numpy as np + line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + m = G.graph['rows'] tile_width = 2*G.graph["tile"] @@ -157,7 +159,18 @@ def _xy_coords(u, w, k, j, z): if normalize_kwargs is not None: normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs) - return _xy_coords + if line_plot: + qubit_dx = np.hstack(([tile_width - .25, 0], paddims)) * scale + qubit_dy = np.hstack(([0, tile_width - .25], paddims)) * scale + def _line_coords(u, w, k, j, z): + xy = _xy_coords(u, w, k, j, z) + if u: + return np.vstack((xy - qubit_dx, xy + qubit_dx)) + else: + return np.vstack((xy - qubit_dy, xy + qubit_dy)) + return _line_coords + else: + return _xy_coords def draw_zephyr(G, **kwargs): """Draws graph G in a Zephyr topology. @@ -180,6 +193,14 @@ def draw_zephyr(G, **kwargs): edges in G and biases are numeric. Self-loop edges (i.e., :math:`i=j`) are treated as linear biases. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of optional keywords, with the exception of the ``pos`` parameter, which is @@ -199,9 +220,8 @@ def draw_zephyr(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - - draw_qubit_graph(G, zephyr_layout(G), **kwargs) - + layout = zephyr_layout(G, normalize_kwargs = kwargs) + draw_qubit_graph(G, layout, **kwargs) def draw_zephyr_embedding(G, *args, **kwargs): """Draws an embedding onto Zephyr graph G. @@ -245,6 +265,14 @@ def draw_zephyr_embedding(G, *args, **kwargs): If True, chains in ``emb`` may overlap (contain the same vertices in G), and these overlaps are displayed as concentric circles. + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of optional keywords, with the exception of the ``pos`` parameter, which is @@ -252,7 +280,8 @@ def draw_zephyr_embedding(G, *args, **kwargs): are provided, any provided ``node_color`` or ``edge_color`` arguments are ignored. """ - draw_embedding(G, zephyr_layout(G), *args, **kwargs) + layout = zephyr_layout(G, normalize_kwargs = kwargs) + draw_embedding(G, layout, *args, **kwargs) def draw_zephyr_yield(G, **kwargs): """Draws the given graph G with highlighted faults, according to layout. @@ -282,6 +311,14 @@ def draw_zephyr_yield(G, **kwargs): fault_style : string, optional (default='dashed') Edge fault line style (solid|dashed|dotted|dashdot) + line_plot : boolean (optional, default False) + If line_plot is True, then qubits are drawn as line segments, and edges + are drawn either as line segments between qubits, or as circles where + two qubits overlap. In this drawing style, the interpretation the width + and node_size parameters (provided in kwargs) determines the area of the + circles, and line widths, respectively. For more information, see + :func:`dwave_networkx.qubit_layout.draw_lineplot`. + kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of optional keywords, with the exception of the ``pos`` parameter, which is @@ -298,7 +335,6 @@ def draw_zephyr_yield(G, **kwargs): raise ValueError("Target zephyr graph needs to have columns, rows, \ tile, and label attributes to be able to identify faulty qubits.") - perfect_graph = zephyr_graph(m, t, coordinates=coordinates) - - draw_yield(G, zephyr_layout(perfect_graph), perfect_graph, **kwargs) + layout = zephyr_layout(perfect_graph, normalize_kwargs = kwargs) + draw_yield(G, layout, perfect_graph, **kwargs) From 49a186297e25527cd26c53a249e30bb22a0431d9 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 13 Dec 2021 16:23:18 -0800 Subject: [PATCH 5/7] review --- dwave_networkx/drawing/chimera_layout.py | 84 ++++++++++++---------- dwave_networkx/drawing/pegasus_layout.py | 78 +++++++++++--------- dwave_networkx/drawing/qubit_layout.py | 91 ++++++++++++++++-------- dwave_networkx/drawing/zephyr_layout.py | 74 ++++++++++--------- 4 files changed, 190 insertions(+), 137 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 985b22e7..206d1756 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -27,7 +27,7 @@ __all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield'] -def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): +def chimera_layout(G, scale=1., center=None, dim=2, plot_kwargs=None): """Positions the nodes of graph G in a Chimera cross topology. NumPy (https://scipy.org) is required for this function. @@ -51,10 +51,12 @@ def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): Number of dimensions. When dim > 2, all extra dimensions are set to 0. - normalize_kwargs : None or dict (default None) - A dict of keyword arguments to be used in a plotting function. If not - None, we will populate the "ax" keyword with matplotlib axes, and the - "node_size" and "width" keywords with defaults if they are not set. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. Returns ------- @@ -80,7 +82,7 @@ def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): n = G.graph['columns'] t = G.graph['tile'] # get a node placement function - xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs) + xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, plot_kwargs=plot_kwargs) if G.graph['labels'] == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} @@ -102,7 +104,7 @@ def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): m = max(idx[0] for idx in chimera_indices.values()) + 1 n = max(idx[1] for idx in chimera_indices.values()) + 1 t = max(idx[3] for idx in chimera_indices.values()) + 1 - xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, normalize_kwargs = normalize_kwargs) + xy_coords = chimera_node_placer_2d(m, n, t, scale, center, dim, plot_kwargs=plot_kwargs) # compute our coordinates pos = {v: xy_coords(i, j, u, k) for v, (i, j, u, k) in chimera_indices.items()} @@ -110,7 +112,7 @@ def chimera_layout(G, scale=1., center=None, dim=2, normalize_kwargs = None): return pos -def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwargs = None): +def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, plot_kwargs=None): """Generates a function that converts Chimera indices to x, y coordinates for a plot. @@ -135,11 +137,13 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwar dim : int (default 2) Number of dimensions. When dim > 2, all extra dimensions are set to 0. - - normalize_kwargs : None or dict (default None) - A dict of keyword arguments to be used in a plotting function. If not - None, we will populate the "ax" keyword with matplotlib axes, and the - "node_size" and "width" keywords with defaults if they are not set. + + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. Returns ------- @@ -151,19 +155,20 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwar """ import numpy as np - line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + line_plot = False if plot_kwargs is None else plot_kwargs.get('line_plot') center_pad = 0 if line_plot else 1 + border_pad = 2 tile_center = t // 2 - tile_length = t + 2 + center_pad # 2 for spacing between tiles + tile_length = t + border_pad + center_pad # want the enter plot to fill in [0, 1] when scale=1 - fabric_scale = max(m, n) * tile_length - 2 - center_pad + fabric_scale = max(m, n) * tile_length - border_pad - center_pad scale /= fabric_scale - if normalize_kwargs is not None: - normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs) + if plot_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 200, plot_kwargs) grid_offsets = {} @@ -175,6 +180,7 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, normalize_kwar if dim < 2: raise ValueError("layout must have at least two dimensions") + # pad the dimensions beyond the second with zeros paddims = np.zeros(dim - 2, dtype='float') if len(center) != dim: @@ -241,12 +247,12 @@ def draw_chimera(G, **kwargs): edges (i.e., :math:`i=j`) are treated as linear biases. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -265,7 +271,7 @@ def draw_chimera(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - layout = chimera_layout(G, normalize_kwargs = kwargs) + layout = chimera_layout(G, plot_kwargs=kwargs) draw_qubit_graph(G, layout, **kwargs) def draw_chimera_embedding(G, *args, **kwargs): @@ -314,12 +320,12 @@ def draw_chimera_embedding(G, *args, **kwargs): concentric circles. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -327,7 +333,7 @@ def draw_chimera_embedding(G, *args, **kwargs): function. If `linear_biases` or `quadratic_biases` are provided, any provided `node_color` or `edge_color` arguments are ignored. """ - layout = chimera_layout(G, normalize_kwargs = kwargs) + layout = chimera_layout(G, plot_kwargs=kwargs) draw_embedding(G, layout, *args, **kwargs) @@ -358,12 +364,12 @@ def draw_chimera_yield(G, **kwargs): Edge fault line style (solid|dashed|dotted|dashdot) line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -382,5 +388,5 @@ def draw_chimera_yield(G, **kwargs): tile, and label attributes to be able to identify faulty qubits.") perfect_graph = chimera_graph(m,n,t, coordinates=coordinates) - layout = chimera_layout(perfect_graph, normalize_kwargs = kwargs) + layout = chimera_layout(perfect_graph, plot_kwargs=kwargs) draw_yield(G, layout, perfect_graph, **kwargs) diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index c2bc70fe..69b29123 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -31,7 +31,7 @@ ] -def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None): +def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, plot_kwargs=None): """Positions the nodes of graph G in a Pegasus topology. `NumPy `_ is required for this function. @@ -58,10 +58,12 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwa rather than L configuration. Ignored if G is defined with ``nice_coordinates=True``. - normalize_kwargs : None or dict (default None) - A dict of keyword arguments to be used in a plotting function. If not - None, we will populate the "ax" keyword with matplotlib axes, and the - "node_size" and "width" keywords with defaults if they are not set. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. Returns ------- @@ -81,7 +83,10 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwa raise ValueError("G must be generated by dwave_networkx.pegasus_graph") labels = G.graph.get('labels') - xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses=(crosses or labels == 'nice_coordinates'), normalize_kwargs = normalize_kwargs) + if labels == 'coordinate': + crosses = True + + xy_coords = pegasus_node_placer_2d(G, scale, center, dim, crosses, plot_kwargs) if labels == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} @@ -100,7 +105,7 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, normalize_kwa return pos -def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, normalize_kwargs=None): +def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, plot_kwargs=None): """Generates a function to convert Pegasus indices to plottable coordinates. Parameters @@ -124,6 +129,13 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, norma If True, :math:`K_{4,4}` subgraphs are shown in a cross rather than L configuration. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. + Returns ------- xy_coords : function @@ -133,7 +145,7 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, norma """ import numpy as np - line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + line_plot = False if plot_kwargs is None else plot_kwargs.get('line_plot') if line_plot: crosses = False @@ -150,8 +162,8 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, norma fabric_scale = m*tile_width - 2*odd_k_wobble - 1 scale /= fabric_scale - if normalize_kwargs is not None: - normalize_size_and_aspect(fabric_scale, 150, normalize_kwargs) + if plot_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 150, plot_kwargs) if center is None: center = np.zeros(dim) @@ -164,6 +176,7 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, norma if dim < 0: raise ValueError("layout must have at least two dimensions") + # pad the dimensions beyond the second with zeros paddims = np.zeros(dim - 2) if len(center) != dim: @@ -227,13 +240,12 @@ def draw_pegasus(G, crosses=False, **kwargs): ``nice_coordinates=True`` or ``line_plot=True``. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. - + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -253,7 +265,7 @@ def draw_pegasus(G, crosses=False, **kwargs): >>> plt.show() # doctest: +SKIP """ - layout = pegasus_layout(G, normalize_kwargs = kwargs) + layout = pegasus_layout(G, plot_kwargs=kwargs) draw_qubit_graph(G, layout, **kwargs) def draw_pegasus_embedding(G, *args, crosses=False, **kwargs): @@ -304,13 +316,12 @@ def draw_pegasus_embedding(G, *args, crosses=False, **kwargs): in G), and these overlaps are displayed as concentric circles. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. - + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -318,7 +329,7 @@ def draw_pegasus_embedding(G, *args, crosses=False, **kwargs): function. If ``linear_biases`` or ``quadratic_biases`` are provided, any provided ``node_color`` or ``edge_color`` arguments are ignored. """ - layout = pegasus_layout(G, crosses=crosses, normalize_kwargs=kwargs) + layout = pegasus_layout(G, crosses=crosses, plot_kwargs=kwargs) draw_embedding(G, layout, *args, **kwargs) def draw_pegasus_yield(G, crosses=False, **kwargs): @@ -353,13 +364,12 @@ def draw_pegasus_yield(G, crosses=False, **kwargs): ``nice_coordinates=True`` or ``line_plot=True``. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. - + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See networkx.draw_networkx() for a description of optional keywords, @@ -380,5 +390,5 @@ def draw_pegasus_yield(G, crosses=False, **kwargs): perfect_graph = pegasus_graph(m, offset_lists=offset_lists, coordinates=coordinates, nice_coordinates=nice) - layout = pegasus_layout(perfect_graph, crosses=crosses, normalize_kwargs=kwargs) + layout = pegasus_layout(perfect_graph, crosses=crosses, plot_kwargs=kwargs) draw_yield(G, layout, perfect_graph, **kwargs) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index fbf17c20..73752968 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -526,7 +526,7 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), import matplotlib.pyplot as plt import matplotlib as mpl except ImportError: - raise ImportError("Matplotlib and numpy required for draw_yield()") + raise ImportError("Matplotlib required for draw_yield()") edgeset = lambda E: set(map(lambda e: tuple(sorted(e)), E)) nodelist = G.nodes() @@ -569,6 +569,35 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), def normalize_size_and_aspect(scale, node_scale, kwargs): + """Sets default values for the "ax", "node_size" and "width" keys. + + If the `"ax"` is not set, then we create an axis from the current + `matplotlib` figure (:func:`matplotlib.pyplot.gcf()`). Then, the the axis + (either the one we created, or the pre-existing one) is set to have an + aspect ratio of 1 and the drawing of axes is turned off. + + Then, if `"node_size"` and `"width"` are not set, we compute default values + for them based on the scale of the current figure and the scale parameters + as appropriate. + + Note, if `kwargs["line_plot"]` is True, then the interpretation of the + `"width"` and `"node_size"` parameters determine the area of edge-circles + and line widths (used for both nodes and edges) respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. + + Parameters + ---------- + scale : float + A geometric size of a figure to be drawn. This is roughly the number + of qubits in any given row or column of the graph. + + node_scale : float + A magic number used to scale node circles -- ignored and replaced with + the number 50 when `kwargs["line_plot"]` is True. + + kwargs : dict + A dictionary to populate with default values. + """ ax = kwargs.get('ax') if ax is None: try: @@ -606,7 +635,7 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, edgelist=None, node_color='blue', edge_color='black', cmap=None, vmin=None, vmax=None, edge_cmap=None, edge_vmin=None, edge_vmax=None, z_offset=0): - """Draws the graph G with line segments representing nodes + """Draws the graph G with line segments representing nodes. This function is meant to be a drop-in replacement for :func:`networkx.draw` where nodes are associated with line segments (specified as 2x2 matrices @@ -617,22 +646,22 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, have three classes of edges: * internal edges between qubits whose line segments are perpendicular - and intersect at a point, which we draw with a circle located at the - point of intersection, - * external edges between qubits whose line segments are colinear, which - we draw as a line segment between the nearest endpoints, and + and intersect at a point, drawn as a circle located at the point of + intersection, + * external edges between qubits whose line segments are colinear, drawn + as a line segment between the nearest endpoints, and * odd edges between parallel qubits whose line segments are parallel and - overlap in a perpendicular projection, which we draw as a line segment - between the midpoints of the respective perpendicular projections + overlap in a perpendicular projection, drawn as a line segment between + the midpoints of the respective perpendicular projections. Parameters ---------- G : networkx.Graph - A graph constructed by :func:`chimera_layout`, :func:`pegasus_layout, or - :func:`zephyr_layout` + A graph constructed by :func:`chimera_layout`, :func:`pegasus_layout`, + or :func:`zephyr_layout`. - pos : dict + layout : dict A dictionary with nodes as keys and 2x2 matrices [[x0, y0], [x1, y1]] representing the line segments of nodes. @@ -650,9 +679,9 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, The set of nodes to draw. If None, all nodes from G are drawn. edgelist : iterable or None (default=None) - The set of edges to draw. If both nodelist and edgelist are None, all - edges of G are drawn. If edgelist is None, all edges from the subgraph - ``G.subgraph(nodelist)`` are drawn. + The set of edges to draw. If both nodelist and ``edgelist`` are None, + all edges of G are drawn. If ``edgelist`` is None, all edges from the + subgraph ``G.subgraph(nodelist)`` are drawn. node_color : iterable or string (default='blue') The sequence of colors to use in drawing nodes of G. If node_color is @@ -661,18 +690,20 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, edge_color : iterable or string (default='black') The sequence of colors to use in drawing edges of G. If edge_color is - not a string, the colors are taken in the same order as edgelist, and - each color is either a float, a 3-tuple or 4-tuple of floats. + not a string, the colors are taken in the same order as ``edgelist``, + and each color is either a float, a 3-tuple or 4-tuple of floats. cmap : string or matplotlib.ColorMap or None (default=None) A colormap to color nodes with. Presumes that node_color is a sequence of floats. vmin : float or None (default=None) - Minimum value to use to use when normalizing node colors through cmap. + Minimum value to use to use when normalizing node colors through + ``cmap``. vmax : float or None (default=None) - Maximum value to use to use when normalizing node colors through cmap. + Maximum value to use to use when normalizing node colors through + ``cmap``. edge_cmap : string or matplotlib.ColorMap or None (default=None) A colormap to color edges with. Presumes that edge_color is a sequence @@ -680,23 +711,25 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, edge_vmin : float or None (default=None) Minimum value to use to use when normalizing edge colors through - edge_cmap + ``edge_cmap``. edge_vmax : float or None (default=None) Maximum value to use to use when normalizing edge colors through - edge_cmap + ``edge_cmap``. z_offset : int (default=0) - An offset to the zorder that various elements are drawn in. Edge lines - are drawn with zorder=z_offset; horizontal node lines are drawn with - zorder=zoffset+1; vertical node lines are drawn with zorder=zoffset+2, - and edge circles are drawn with zorder=zoffset+3. This parameter can be - used to layer line plots over or under eachother. + An offset to the "zorder: that various elements are drawn in. Edge + lines are drawn with `zorder=z_offset`; horizontal node lines are drawn + with `zorder=zoffset+1`; vertical node lines are drawn with + `zorder=zoffset+2`, and edge circles are drawn with `zorder=zoffset+3`. + This parameter can be used to layer line plots over or under each other. """ - - from networkx.drawing.nx_pylab import apply_alpha - import numpy as np - from matplotlib.collections import LineCollection, CircleCollection + try: + from networkx.drawing.nx_pylab import apply_alpha + import numpy as np + from matplotlib.collections import LineCollection, CircleCollection + except ImportError: + raise ImportError("Matplotlib and NumPy required for draw_lineplot()") if not isinstance(node_size, Number) or not isinstance(width, Number): raise NotImplementedError("Varying node size and edge width per element in line plots is not implemented") diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index a56c7dfc..5f1ce4c1 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -30,7 +30,7 @@ ] -def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None): +def zephyr_layout(G, scale=1., center=None, dim=2, plot_kwargs=None): """Positions the nodes of graph G in a Zephyr topology. `NumPy `_ is required for this function. @@ -52,10 +52,12 @@ def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None): Number of dimensions. When dim > 2, all extra dimensions are set to 0. - normalize_kwargs : None or dict (default None) - A dict of keyword arguments to be used in a plotting function. If not - None, we will populate the "ax" keyword with matplotlib axes, and the - "node_size" and "width" keywords with defaults if they are not set. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. Returns ------- @@ -74,7 +76,7 @@ def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None): if not isinstance(G, nx.Graph) or G.graph.get("family") != "zephyr": raise ValueError("G must be generated by dwave_networkx.zephyr_graph") - xy_coords = zephyr_node_placer_2d(G, scale, center, dim, normalize_kwargs) + xy_coords = zephyr_node_placer_2d(G, scale, center, dim, plot_kwargs) if G.graph['labels'] == 'coordinate': pos = {v: xy_coords(*v) for v in G.nodes()} @@ -89,7 +91,7 @@ def zephyr_layout(G, scale=1., center=None, dim=2, normalize_kwargs=None): return pos -def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = None): +def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, plot_kwargs=None): """Generates a function to convert Zephyr indices to plottable coordinates. Parameters @@ -109,10 +111,12 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = No Number of dimensions. When dim > 2, all extra dimensions are set to 0. - normalize_kwargs : None or dict (default None) - A dict of keyword arguments to be used in a plotting function. If not - None, we will populate the "ax" keyword with matplotlib axes, and the - "node_size" and "width" keywords with defaults if they are not set. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` + are set to default values by the :func:`normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. Returns ------- @@ -123,7 +127,7 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, normalize_kwargs = No """ import numpy as np - line_plot = False if normalize_kwargs is None else normalize_kwargs.get('line_plot') + line_plot = False if plot_kwargs is None else plot_kwargs.get('line_plot') m = G.graph['rows'] tile_width = 2*G.graph["tile"] @@ -156,8 +160,8 @@ def _xy_coords(u, w, k, j, z): fabric_scale = max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1))) scale /= fabric_scale - if normalize_kwargs is not None: - normalize_size_and_aspect(fabric_scale, 200, normalize_kwargs) + if plot_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 200, plot_kwargs) if line_plot: qubit_dx = np.hstack(([tile_width - .25, 0], paddims)) * scale @@ -194,12 +198,12 @@ def draw_zephyr(G, **kwargs): edges (i.e., :math:`i=j`) are treated as linear biases. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of @@ -220,7 +224,7 @@ def draw_zephyr(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - layout = zephyr_layout(G, normalize_kwargs = kwargs) + layout = zephyr_layout(G, plot_kwargs=kwargs) draw_qubit_graph(G, layout, **kwargs) def draw_zephyr_embedding(G, *args, **kwargs): @@ -266,12 +270,12 @@ def draw_zephyr_embedding(G, *args, **kwargs): in G), and these overlaps are displayed as concentric circles. line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of @@ -280,7 +284,7 @@ def draw_zephyr_embedding(G, *args, **kwargs): are provided, any provided ``node_color`` or ``edge_color`` arguments are ignored. """ - layout = zephyr_layout(G, normalize_kwargs = kwargs) + layout = zephyr_layout(G, plot_kwargs=kwargs) draw_embedding(G, layout, *args, **kwargs) def draw_zephyr_yield(G, **kwargs): @@ -312,12 +316,12 @@ def draw_zephyr_yield(G, **kwargs): Edge fault line style (solid|dashed|dotted|dashdot) line_plot : boolean (optional, default False) - If line_plot is True, then qubits are drawn as line segments, and edges - are drawn either as line segments between qubits, or as circles where - two qubits overlap. In this drawing style, the interpretation the width - and node_size parameters (provided in kwargs) determines the area of the - circles, and line widths, respectively. For more information, see - :func:`dwave_networkx.qubit_layout.draw_lineplot`. + If ``line_plot`` is True, then qubits are drawn as line segments, and + edges are drawn either as line segments between qubits, or as circles + where two qubits overlap. In this drawing style, the interpretation of + the ``width`` and ``node_size`` parameters (provided in ``kwargs``) + determine the area of the circles and line widths respectively. See + :func:`dwave_networkx.qubit_layout.draw_lineplot` for more information. kwargs : optional keywords See :func:`~networkx.drawing.nx_pylab.draw_networkx` for a description of @@ -336,5 +340,5 @@ def draw_zephyr_yield(G, **kwargs): tile, and label attributes to be able to identify faulty qubits.") perfect_graph = zephyr_graph(m, t, coordinates=coordinates) - layout = zephyr_layout(perfect_graph, normalize_kwargs = kwargs) + layout = zephyr_layout(perfect_graph, plot_kwargs=kwargs) draw_yield(G, layout, perfect_graph, **kwargs) From 553581ca37ef2a390cff0cadd464a35f0c139548 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Wed, 25 Jan 2023 13:48:41 -0800 Subject: [PATCH 6/7] doc review; improved references and added generic plotting docs --- docs/reference/drawing.rst | 18 ++++++++++++++++-- dwave_networkx/drawing/chimera_layout.py | 4 ++-- dwave_networkx/drawing/pegasus_layout.py | 4 ++-- dwave_networkx/drawing/qubit_layout.py | 4 ++-- dwave_networkx/drawing/zephyr_layout.py | 8 ++++---- 5 files changed, 26 insertions(+), 12 deletions(-) diff --git a/docs/reference/drawing.rst b/docs/reference/drawing.rst index 1434d36a..fc6862a3 100644 --- a/docs/reference/drawing.rst +++ b/docs/reference/drawing.rst @@ -21,6 +21,7 @@ Chimera Graph Functions chimera_layout draw_chimera + chimera_node_placer_2d Example ~~~~~~~ @@ -89,8 +90,6 @@ of nodes of a simple 5-node graph on a small Pegasus lattice. edge_list=[(4, 40), (4, 41), (4, 42), (4, 43)]) >>> # Show graph H on a small Pegasus lattice >>> plt.ion() - >>> # Show graph H on a small Pegasus lattice - >>> plt.ion() >>> dnx.draw_pegasus(G, with_labels=True, crosses=True, node_color="Yellow") >>> dnx.draw_pegasus(H, crosses=True, node_color='b', style='dashed', edge_color='b', width=3) @@ -141,3 +140,18 @@ of a five-node clique on a small Zephyr graph. :alt: Five-node clique embedded in a small Zephyr graph. Five-node clique embedded in a small Zephyr graph. + + +Generic Plotting Functions +-------------------------- + +.. automodule:: dwave_networkx.drawing.qubit_layout + +.. autosummary:: + :toctree: generated/ + + draw_qubit_graph + draw_embedding + draw_yield + normalize_size_and_aspect + draw_lineplot diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 206d1756..b0b09278 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -53,7 +53,7 @@ def chimera_layout(G, scale=1., center=None, dim=2, plot_kwargs=None): plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` are set to default values by the :func:`normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. @@ -140,7 +140,7 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2, plot_kwargs=No plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` are set to default values by the :func:`normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index 69b29123..a2825e48 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -60,7 +60,7 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False, plot_kwargs=N plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` are set to default values by the :func:`normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. @@ -131,7 +131,7 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False, plot_ plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` are set to default values by the :func:`normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index 73752968..da18c22c 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -27,7 +27,7 @@ from numbers import Number -__all__ = ['draw_qubit_graph'] +__all__ = ['draw_qubit_graph', 'draw_embedding', 'draw_yield', 'normalize_size_and_aspect', 'draw_lineplot'] def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, @@ -637,7 +637,7 @@ def draw_lineplot(G, layout, *, ax, node_size, width, nodelist=None, edge_vmin=None, edge_vmax=None, z_offset=0): """Draws the graph G with line segments representing nodes. - This function is meant to be a drop-in replacement for :func:`networkx.draw` + This function is meant to be a drop-in replacement for :func:`networkx.drawing.nx_pylab.draw` where nodes are associated with line segments (specified as 2x2 matrices [[x0, y0], [x1, y1]]). This function makes significant assumptions about the edges of the graph G, that hold when G is a Chimera, Pegasus, or Zephyr diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index 5f1ce4c1..5262b1b8 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -54,9 +54,9 @@ def zephyr_layout(G, scale=1., center=None, dim=2, plot_kwargs=None): plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` - are set to default values by the :func:`normalize_size_and_aspect` + are set to default values by the :func:`~dwave_networkx.drawing.qubit_layout.normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. Returns @@ -113,9 +113,9 @@ def zephyr_node_placer_2d(G, scale=1., center=None, dim=2, plot_kwargs=None): plot_kwargs : None or dict (default None) A dict of keyword arguments to be used in a plotting function (see - :func:`networkx.draw` and :func:`draw_lineplot`), to scale node and edge + :func:`networkx.drawing.nx_pylab.draw` and :func:`draw_lineplot`), to scale node and edge sizes appropriately to the graph `G`. Optional keys in ``plot_kwargs`` - are set to default values by the :func:`normalize_size_and_aspect` + are set to default values by the :func:`~dwave_networkx.drawing.qubit_layout.normalize_size_and_aspect` function. Do nothing if ``plot_kwargs`` is None. Returns From aba5e46ddd109d2233b62401b4674e8853e34099 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 30 Jan 2023 11:42:10 -0800 Subject: [PATCH 7/7] resolved incompatibility with matplotlib 3.6 (see networkx/networkx#5937) --- dwave_networkx/drawing/qubit_layout.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index da18c22c..be9ea4b7 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -609,10 +609,10 @@ def normalize_size_and_aspect(scale, node_scale, kwargs): cf = ax.get_figure() cf.set_facecolor("w") if ax is None: - if cf._axstack() is None: - ax = cf.add_axes((0, 0, 1, 1)) - else: + if cf.axes: ax = cf.gca() + else: + ax = cf.add_axes((0, 0, 1, 1)) kwargs['ax'] = ax ax.set_aspect(1) fig_scale = min(cf.get_figheight(), cf.get_figwidth())