From c57d80a26177ec1426312a81d47aa9f8ffcf515d Mon Sep 17 00:00:00 2001 From: Kevin Chern Date: Thu, 16 Nov 2023 13:37:15 -0800 Subject: [PATCH 1/4] Fix undefined variables for color maps --- dwave_networkx/drawing/qubit_layout.py | 43 ++++++++++---------------- 1 file changed, 16 insertions(+), 27 deletions(-) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index d71f73d6..df1c28c8 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -102,24 +102,6 @@ def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, if edgelist is None: edgelist = G.edges() - # since we're applying the colormap here, matplotlib throws warnings if - # we provide these arguments and it doesn't use them. - if linear_biases: - cmap = kwargs.pop('cmap', None) - vmin = kwargs.pop('vmin', None) - vmax = kwargs.pop('vmax', None) - - if quadratic_biases: - edge_cmap = kwargs.pop('edge_cmap', None) - edge_vmin = kwargs.pop('edge_vmin', None) - edge_vmax = kwargs.pop('edge_vmax', None) - - if cmap is None: - cmap = plt.get_cmap('coolwarm') - - if edge_cmap is None: - edge_cmap = plt.get_cmap('coolwarm') - # any edges or nodes with an unspecified bias default to 0 def edge_color(u, v): c = 0. @@ -143,17 +125,16 @@ def node_color(v): # the range of the color map is shared for nodes/edges and is symmetric # around 0. vmag = max(max(abs(c) for c in node_color), max(abs(c) for c in edge_color)) - if vmin is None: - vmin = -1 * vmag - - if vmax is None: - vmax = vmag - if edge_vmin is None: - edge_vmin = -1 * vmag + # since we're applying the colormap here, matplotlib throws warnings if + # we provide these arguments and it doesn't use them. + cmap = kwargs.pop('cmap', plt.get_cmap('coolwarm')) + vmin = kwargs.pop('vmin', -1 * vmag) + vmax = kwargs.pop('vmax', vmag) - if edge_vmax is None: - edge_vmax = vmag + edge_cmap = kwargs.pop('edge_cmap', plt.get_cmap('coolwarm')) + edge_vmin = kwargs.pop('edge_vmin', -1 * vmag) + edge_vmax = kwargs.pop('edge_vmax', vmag) if linear_biases and quadratic_biases: global_vmin = min(edge_vmin, vmin) @@ -189,6 +170,14 @@ def node_color(v): if ax is None: ax = fig.add_axes([0.01, 0.01, 0.98, 0.98]) + if linear_biases and not quadratic_biases: + kwargs['edge_vmin'] = edge_vmin + kwargs['edge_vmax'] = edge_vmax + kwargs['edge_cmap'] = edge_cmap + if quadratic_biases and not linear_biases: + kwargs['vmin'] = vmin + kwargs['vmax'] = vmax + kwargs['cmap'] = cmap draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, **kwargs) From 7acafe34703faa7f4117bc4b26d8e014084f2159 Mon Sep 17 00:00:00 2001 From: Kevin Chern Date: Tue, 21 Nov 2023 09:35:05 -0800 Subject: [PATCH 2/4] Fix mutable default args --- dwave_networkx/drawing/qubit_layout.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index df1c28c8..ec20fc1b 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -27,7 +27,7 @@ __all__ = ['draw_qubit_graph'] -def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, +def draw_qubit_graph(G, layout, linear_biases=None, quadratic_biases=None, nodelist=None, edgelist=None, midpoint=None, **kwargs): """Draws graph G according to layout. @@ -45,11 +45,11 @@ def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, be of the form {node: coordinate, ...}. Coordinates will be treated as vectors, and should all have the same length. - linear_biases : dict (optional, default {}) + linear_biases : dict (optional, None) A dict of biases associated with each node in G. Should be of form {node: bias, ...}. Each bias should be numeric. - quadratic_biases : dict (optional, default {}) + quadratic_biases : dict (optional, None) A dict of biases associated with each edge in G. Should be of form {edge: bias, ...}. Each bias should be numeric. Self-loop edges (i.e., :math:`i=j`) are treated as linear biases. @@ -79,6 +79,9 @@ def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={}, else: _mpl_toolkit_found = True + linear_biases = linear_biases or dict() + quadratic_biases = quadratic_biases or dict() + fig = plt.gcf() ax = kwargs.pop('ax', None) cax = kwargs.pop('cax', None) From 4fe8ffc765256b70129029a1a32a5baedaff552c Mon Sep 17 00:00:00 2001 From: Kevin Chern Date: Tue, 21 Nov 2023 12:37:11 -0800 Subject: [PATCH 3/4] Fix kwargs edge case, clean code, and add tests --- dwave_networkx/drawing/qubit_layout.py | 76 ++++++++++---------------- tests/test_qubit_layout.py | 59 ++++++++++++++++++++ 2 files changed, 88 insertions(+), 47 deletions(-) create mode 100644 tests/test_qubit_layout.py diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index ec20fc1b..6caeaca4 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -131,61 +131,44 @@ def node_color(v): # since we're applying the colormap here, matplotlib throws warnings if # we provide these arguments and it doesn't use them. - cmap = kwargs.pop('cmap', plt.get_cmap('coolwarm')) - vmin = kwargs.pop('vmin', -1 * vmag) - vmax = kwargs.pop('vmax', vmag) + cmap = kwargs.pop('cmap', None) or plt.get_cmap('coolwarm') + vmin = kwargs.pop('vmin', None) or -1 * vmag + vmax = kwargs.pop('vmax', None) or vmag - edge_cmap = kwargs.pop('edge_cmap', plt.get_cmap('coolwarm')) - edge_vmin = kwargs.pop('edge_vmin', -1 * vmag) - edge_vmax = kwargs.pop('edge_vmax', vmag) + edge_cmap = kwargs.pop('edge_cmap', None) or plt.get_cmap('coolwarm') + edge_vmin = kwargs.pop('edge_vmin', None) or -1 * vmag + edge_vmax = kwargs.pop('edge_vmax', None) or vmag if linear_biases and quadratic_biases: - global_vmin = min(edge_vmin, vmin) - global_vmax = max(edge_vmax, vmax) + final_vmin = min(edge_vmin, vmin) + final_vmax = max(edge_vmax, vmax) + mapper = cmap - if midpoint is None: - midpoint = (global_vmax + global_vmin) / 2.0 - norm_map = mpl.colors.TwoSlopeNorm(midpoint, vmin=global_vmin, vmax=global_vmax) - - node_color = [cmap(norm_map(node)) for node in node_color] - edge_color = [cmap(norm_map(edge)) for edge in edge_color] - mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm_map, orientation='vertical') - - # if the biases are provided, then add a legend explaining the color map elif linear_biases: - if midpoint is None: - midpoint = (vmax + vmin) / 2.0 - norm_map = mpl.colors.TwoSlopeNorm(midpoint, vmin=vmin, vmax=vmax) - node_color = [cmap(norm_map(node)) for node in node_color] - mpl.colorbar.ColorbarBase(cax, cmap=cmap, norm=norm_map, orientation='vertical') + final_vmin = vmin + final_vmax = vmax + mapper = cmap elif quadratic_biases: - if midpoint is None: - midpoint = (edge_vmax + edge_vmin) / 2.0 - norm_map = mpl.colors.TwoSlopeNorm(midpoint, vmin=edge_vmin, vmax=edge_vmax) - edge_color = [edge_cmap(norm_map(edge)) for edge in edge_color] - mpl.colorbar.ColorbarBase(cax, cmap=edge_cmap, norm=norm_map, orientation='vertical') + final_vmin = edge_vmin + final_vmax = edge_vmax + mapper = edge_cmap - kwargs['edge_color'] = edge_color - kwargs['node_color'] = node_color + midpoint = midpoint or (final_vmax + final_vmin) / 2.0 + norm_map = mpl.colors.TwoSlopeNorm(midpoint, vmin=final_vmin, vmax=final_vmax) + mpl.colorbar.ColorbarBase(cax, cmap=mapper, norm=norm_map, orientation='vertical') + kwargs['node_color'] = [mapper(norm_map(node)) for node in node_color] + kwargs['edge_color'] = [mapper(norm_map(edge)) for edge in edge_color] else: if ax is None: ax = fig.add_axes([0.01, 0.01, 0.98, 0.98]) - if linear_biases and not quadratic_biases: - kwargs['edge_vmin'] = edge_vmin - kwargs['edge_vmax'] = edge_vmax - kwargs['edge_cmap'] = edge_cmap - if quadratic_biases and not linear_biases: - kwargs['vmin'] = vmin - kwargs['vmax'] = vmax - kwargs['cmap'] = cmap draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, **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, + chain_color=None, unused_color=(0.9, 0.9, 0.9, 1.0), cmap=None, show_labels=False, overlapped_embedding=False, **kwargs): """Draws an embedding onto the graph G, according to layout. @@ -423,10 +406,9 @@ def unoverlapped_embedding(G, emb, interaction_edges): return new_G, new_emb, new_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='x', - fault_style='dashed', **kwargs): - +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='x', + fault_style='dashed', **kwargs): """Draws the given graph G with highlighted faults, according to layout. Parameters @@ -482,9 +464,9 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), # Draw faults with different style and shape draw(perfect_graph, layout, nodelist=faults_nodelist, edgelist=faults_edgelist, - node_color=faults_node_color, edge_color=faults_edge_color, - style=fault_style, node_shape=fault_shape, - **kwargs ) + node_color=faults_node_color, edge_color=faults_edge_color, + style=fault_style, node_shape=fault_shape, + **kwargs) # Draw rest of graph if unused_color is not None: @@ -497,5 +479,5 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0), unused_edge_color = [unused_color for v in edgelist] draw(perfect_graph, layout, nodelist=nodelist, edgelist=edgelist, - node_color=unused_node_color, edge_color=unused_edge_color, - **kwargs) + node_color=unused_node_color, edge_color=unused_edge_color, + **kwargs) diff --git a/tests/test_qubit_layout.py b/tests/test_qubit_layout.py new file mode 100644 index 00000000..21dccc72 --- /dev/null +++ b/tests/test_qubit_layout.py @@ -0,0 +1,59 @@ +# Copyright 2018 D-Wave Systems Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import os +import unittest + +import dwave_networkx as dnx + +try: + import matplotlib.pyplot as plt +except ImportError: + plt = False + +try: + import numpy as np +except ImportError: + np = False + +_display = os.environ.get('DISPLAY', '') != '' + + +@unittest.skipUnless(np and plt, "No numpy or matplotlib") +class TestDrawing(unittest.TestCase): + @unittest.skipUnless(_display, " No display found") + def test_draw_qubit_graph_kwargs(self): + G = dnx.chimera_graph(2, 2, 4) + pos = dnx.chimera_layout(G) + linear_biases = {v: -v / max(G) if v % 2 == 0 else v / max(G) for v in G} + quadratic_biases = {(u, v): (u - v) / max(abs(u), abs(v)) for u, v in G.edges} + cm = plt.get_cmap("spring_r") + + # Don't supply biases + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos) + + # Supply both biases + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, quadratic_biases) + + # Supply linear but not quadratic biases + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=None) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=cm) + + # Supply quadratic but not linear biases + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, {}, quadratic_biases) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=None) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=cm) From ab7c3c96027a9e73ef9afc03ba240b7ef8380de7 Mon Sep 17 00:00:00 2001 From: Kevin Chern Date: Wed, 22 Nov 2023 11:56:59 -0800 Subject: [PATCH 4/4] Handle edge cases for default kwargs --- dwave_networkx/drawing/qubit_layout.py | 20 +++++++++++++------- tests/test_qubit_layout.py | 6 ++++++ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index 6caeaca4..cb379b5d 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -131,13 +131,19 @@ def node_color(v): # since we're applying the colormap here, matplotlib throws warnings if # we provide these arguments and it doesn't use them. - cmap = kwargs.pop('cmap', None) or plt.get_cmap('coolwarm') - vmin = kwargs.pop('vmin', None) or -1 * vmag - vmax = kwargs.pop('vmax', None) or vmag - - edge_cmap = kwargs.pop('edge_cmap', None) or plt.get_cmap('coolwarm') - edge_vmin = kwargs.pop('edge_vmin', None) or -1 * vmag - edge_vmax = kwargs.pop('edge_vmax', None) or vmag + cmap = kwargs.pop('cmap', None) + cmap = plt.get_cmap('coolwarm') if cmap is None else cmap + vmin = kwargs.pop('vmin', None) + vmin = -vmag if vmin is None else vmin + vmax = kwargs.pop('vmax', None) + vmax = vmag if vmax is None else vmax + + edge_cmap = kwargs.pop('edge_cmap', None) + edge_cmap = plt.get_cmap('coolwarm') if edge_cmap is None else edge_cmap + edge_vmin = kwargs.pop('edge_vmin', None) + edge_vmin = -vmag if edge_vmin is None else edge_vmin + edge_vmax = kwargs.pop('edge_vmax', None) + edge_vmax = vmag if edge_vmax is None else edge_vmax if linear_biases and quadratic_biases: final_vmin = min(edge_vmin, vmin) diff --git a/tests/test_qubit_layout.py b/tests/test_qubit_layout.py index 21dccc72..25be0bcd 100644 --- a/tests/test_qubit_layout.py +++ b/tests/test_qubit_layout.py @@ -51,9 +51,15 @@ def test_draw_qubit_graph_kwargs(self): dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None) dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=None) dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, cmap=cm) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, vmin=-0.1, vmax=0) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, linear_biases, None, vmin=0.0, vmax=10) # Supply quadratic but not linear biases dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, {}, quadratic_biases) dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases) dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=None) dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, edge_cmap=cm) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, + edge_vmin=-0.1, edge_vmax=0) + dnx.drawing.qubit_layout.draw_qubit_graph(G, pos, None, quadratic_biases, + edge_vmin=0.0, edge_vmax=10)