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 598f0332..b0b09278 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, draw_lineplot 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, plot_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,13 @@ def chimera_layout(G, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :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. + Returns ------- pos : dict @@ -75,9 +82,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, plot_kwargs=plot_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 +104,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, 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()} @@ -105,7 +112,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, plot_kwargs=None): """Generates a function that converts Chimera indices to x, y coordinates for a plot. @@ -131,6 +138,13 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :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. + Returns ------- xy_coords : function @@ -141,10 +155,20 @@ def chimera_node_placer_2d(m, n, t, scale=1., center=None, dim=2): """ import numpy as np + 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 + 3 # 1 for middle of cross, 2 for spacing between tiles + tile_length = t + border_pad + center_pad + # want the enter plot to fill in [0, 1] when scale=1 - scale /= max(m, n) * tile_length - 3 + fabric_scale = max(m, n) * tile_length - border_pad - center_pad + scale /= fabric_scale + + if plot_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 200, plot_kwargs) grid_offsets = {} @@ -153,10 +177,12 @@ 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") + # pad the dimensions beyond the second with zeros + paddims = np.zeros(dim - 2, dtype='float') + if len(center) != dim: raise ValueError("length of center coordinates must match dimension of layout") @@ -167,26 +193,37 @@ 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 _xy_coords + return np.hstack((xy * scale, paddims)) + center + + 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): @@ -209,6 +246,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 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, with the exception of the `pos` parameter which is not used by this @@ -226,8 +271,8 @@ def draw_chimera(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - draw_qubit_graph(G, chimera_layout(G), **kwargs) - + layout = chimera_layout(G, plot_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. @@ -274,13 +319,22 @@ 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 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, 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, chimera_layout(G), *args, **kwargs) + layout = chimera_layout(G, plot_kwargs=kwargs) + draw_embedding(G, layout, *args, **kwargs) def draw_chimera_yield(G, **kwargs): @@ -296,16 +350,27 @@ 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^>v`_ is required for this function. @@ -58,6 +58,13 @@ 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``. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :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. + Returns ------- pos : dict @@ -75,28 +82,30 @@ 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') + 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()} + 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, plot_kwargs=None): """Generates a function to convert Pegasus indices to plottable coordinates. Parameters @@ -120,6 +129,13 @@ def pegasus_node_placer_2d(G, scale=1., center=None, dim=2, crosses=False): 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.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. + Returns ------- xy_coords : function @@ -129,50 +145,72 @@ 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") + line_plot = False if plot_kwargs is None else plot_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"] + 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 + fabric_scale = m*tile_width - 2*odd_k_wobble - 1 + scale /= fabric_scale + + if plot_kwargs is not None: + normalize_size_and_aspect(fabric_scale, 150, plot_kwargs) 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") + # pad the dimensions beyond the second with zeros + 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 _xy_coords + return np.hstack((xy * scale, paddims)) + center + + + 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): @@ -199,7 +237,15 @@ 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 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, @@ -219,11 +265,10 @@ def draw_pegasus(G, crosses=False, **kwargs): >>> plt.show() # doctest: +SKIP """ + layout = pegasus_layout(G, plot_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 @@ -270,16 +315,24 @@ def draw_pegasus_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 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, 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. """ - crosses = kwargs.pop("crosses", False) - draw_embedding(G, pegasus_layout(G, crosses=crosses), *args, **kwargs) + layout = pegasus_layout(G, crosses=crosses, plot_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 @@ -292,16 +345,32 @@ def draw_pegasus_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^>vv 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 7f22d6eb..5262b1b8 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, draw_lineplot 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, plot_kwargs=None): """Positions the nodes of graph G in a Zephyr topology. `NumPy `_ is required for this function. @@ -52,6 +52,13 @@ def zephyr_layout(G, scale=1., center=None, dim=2): Number of dimensions. When dim > 2, all extra dimensions are set to 0. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :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:`~dwave_networkx.drawing.qubit_layout.normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. + Returns ------- pos : dict @@ -69,22 +76,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, plot_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, plot_kwargs=None): """Generates a function to convert Zephyr indices to plottable coordinates. Parameters @@ -104,6 +111,13 @@ 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. + plot_kwargs : None or dict (default None) + A dict of keyword arguments to be used in a plotting function (see + :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:`~dwave_networkx.drawing.qubit_layout.normalize_size_and_aspect` + function. Do nothing if ``plot_kwargs`` is None. + Returns ------- xy_coords : function @@ -113,39 +127,54 @@ 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") + line_plot = False if plot_kwargs is None else plot_kwargs.get('line_plot') - # want the enter plot to fill in [0, 1] when scale=1 - scale /= m * tile_width + m = G.graph['rows'] + tile_width = 2*G.graph["tile"] + + 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 _xy_coords + return np.hstack((xy * scale, paddims)) + center + # want the enter plot to fill in [0, 1] when scale=1 + fabric_scale = max(map(abs, _xy_coords(0, 2*m, G.graph["tile"]-1, 1, m-1))) + scale /= fabric_scale + + 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 + 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. @@ -168,6 +197,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 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 optional keywords, with the exception of the ``pos`` parameter, which is @@ -187,9 +224,8 @@ def draw_zephyr(G, **kwargs): >>> plt.show() # doctest: +SKIP """ - - draw_qubit_graph(G, zephyr_layout(G), **kwargs) - + layout = zephyr_layout(G, plot_kwargs=kwargs) + draw_qubit_graph(G, layout, **kwargs) def draw_zephyr_embedding(G, *args, **kwargs): """Draws an embedding onto Zephyr graph G. @@ -233,6 +269,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 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 optional keywords, with the exception of the ``pos`` parameter, which is @@ -240,7 +284,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, plot_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. @@ -255,7 +300,10 @@ def draw_zephyr_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.5,0.5,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') @@ -267,6 +315,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 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 optional keywords, with the exception of the ``pos`` parameter, which is @@ -283,7 +339,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, plot_kwargs=kwargs) + draw_yield(G, layout, perfect_graph, **kwargs) 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)