From 96f586aa7f0839e197ab7148b2f2be95e0b40f35 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Mon, 6 Dec 2021 20:08:40 -0800 Subject: [PATCH] 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 ab300926..e69d2959 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 09ba544b..cd63cbba 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 networkx.draw_networkx() for a description of optional keywords, with the exception of the ``pos`` parameter, which is not used by this @@ -198,9 +219,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. @@ -244,13 +264,22 @@ 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 networkx.draw_networkx() for a description of optional keywords, with the exception of the ``pos`` parameter, which is not used by this function. If ``linear_biases`` or ``quadratic_biases`` 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. @@ -278,6 +307,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 networkx.draw_networkx() for a description of optional keywords, with the exception of the `pos` parameter which is not used by this @@ -293,7 +330,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)