From 5e9f9aacba0fb0ab83044daea0f3462cc4a60ea8 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Tue, 24 Dec 2024 15:41:12 -0800 Subject: [PATCH 1/8] add topology enums and dispatch decorator --- dwave_networkx/__future_enum.py | 27 ++++ dwave_networkx/__init__.py | 2 + dwave_networkx/drawing/__init__.py | 7 + dwave_networkx/drawing/chimera_layout.py | 21 +-- dwave_networkx/drawing/pegasus_layout.py | 25 +--- dwave_networkx/drawing/qubit_layout.py | 157 ++++++++++++++++++++++- dwave_networkx/drawing/zephyr_layout.py | 21 +-- dwave_networkx/generators/chimera.py | 19 ++- dwave_networkx/generators/common.py | 21 +++ dwave_networkx/generators/pegasus.py | 29 ++++- dwave_networkx/generators/zephyr.py | 24 +++- dwave_networkx/topology.py | 13 ++ dwave_networkx/utils/decorators.py | 33 ++++- 13 files changed, 332 insertions(+), 67 deletions(-) create mode 100644 dwave_networkx/__future_enum.py create mode 100644 dwave_networkx/topology.py diff --git a/dwave_networkx/__future_enum.py b/dwave_networkx/__future_enum.py new file mode 100644 index 00000000..6efb51b1 --- /dev/null +++ b/dwave_networkx/__future_enum.py @@ -0,0 +1,27 @@ +# delete after Py3.10 is dropped +# The implementation of StrEnum and global_enum are strongly informed by +# those found in Py3.11 but are significantly less general. + +from enum import Enum as _Enum +import sys as _sys + +class StrEnum(str, _Enum): + def __new__(cls, value): + member = str.__new__(cls, value) + member._value_ = value + return member + + @staticmethod + def _generate_next_value_(name, *_): + #make auto() work + return name.lower() + + def __str__(self): + return self._value_ + +def global_enum(cls): + toplevel = cls.__module__.split('.')[-1] + cls.__repr__ = lambda self: f'{toplevel}.{self._name_}' + _sys.modules[cls.__module__].__dict__.update(cls.__members__) + return cls + diff --git a/dwave_networkx/__init__.py b/dwave_networkx/__init__.py index f948c483..f18f96a1 100644 --- a/dwave_networkx/__init__.py +++ b/dwave_networkx/__init__.py @@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +from dwave_networkx.topology import * + import dwave_networkx.generators from dwave_networkx.generators import * diff --git a/dwave_networkx/drawing/__init__.py b/dwave_networkx/drawing/__init__.py index 8466d8f3..1dbe544f 100644 --- a/dwave_networkx/drawing/__init__.py +++ b/dwave_networkx/drawing/__init__.py @@ -15,3 +15,10 @@ from dwave_networkx.drawing.chimera_layout import * from dwave_networkx.drawing.pegasus_layout import * from dwave_networkx.drawing.zephyr_layout import * + +from dwave_networkx.drawing.qubit_layout import ( + draw_qubit_graph_generic as draw_qubit_graph, + draw_embedding_generic as draw_embedding, + draw_yield_generic as draw_yield, + qubit_layout as qubit_layout +) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index 89db7f4d..eb43fb17 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -20,13 +20,13 @@ 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.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates - +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, qubit_layout +from dwave_networkx.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates, defect_free_chimera +from ..topology import CHIMERA __all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield'] - +@qubit_layout.install_dispatch(CHIMERA, pop_kwargs=('scale', 'center', 'dim')) def chimera_layout(G, scale=1., center=None, dim=2): """Positions the nodes of graph ``G`` in a Chimera layout. @@ -312,16 +312,5 @@ def draw_chimera_yield(G, **kwargs): the :func:`~networkx.drawing.nx_pylab.draw_networkx` ``node_color`` or ``edge_color`` parameters are ignored. """ - try: - assert(G.graph["family"] == "chimera") - m = G.graph["rows"] - n = G.graph["columns"] - t = G.graph["tile"] - coordinates = G.graph["labels"] == "coordinate" - except: - raise ValueError("Target chimera graph needs to have columns, rows, \ - tile, and label attributes to be able to identify faulty qubits.") - - perfect_graph = chimera_graph(m,n,t, coordinates=coordinates) - + perfect_graph = defect_free_chimera(G) 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 30ac3d16..753ecd77 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -19,10 +19,10 @@ from networkx import draw import numpy as np -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield -from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, qubit_layout +from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates, defect_free_pegasus from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d - +from ..topology import PEGASUS __all__ = ['pegasus_layout', 'draw_pegasus', @@ -30,7 +30,7 @@ 'draw_pegasus_yield', ] - +@qubit_layout.install_dispatch(PEGASUS, pop_kwargs = ('scale', 'center', 'dim', 'crosses')) def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): """Positions the nodes of graph ``G`` in a Pegasus topology. @@ -322,18 +322,5 @@ def draw_pegasus_yield(G, **kwargs): the :func:`~networkx.drawing.nx_pylab.draw_networkx` ``node_color`` or ``edge_color`` parameters are ignored. """ - try: - assert(G.graph["family"] == "pegasus") - m = G.graph['columns'] - offset_lists = (G.graph['vertical_offsets'], G.graph['horizontal_offsets']) - coordinates = G.graph["labels"] == "coordinate" - nice = G.graph["labels"] == "nice" - # Can't interpret fabric_only from graph attributes - except: - raise ValueError("Target pegasus graph needs to have columns, rows, \ - tile, and label attributes to be able to identify faulty qubits.") - - - perfect_graph = pegasus_graph(m, offset_lists=offset_lists, coordinates=coordinates, nice_coordinates=nice) - - draw_yield(G, pegasus_layout(perfect_graph), perfect_graph, **kwargs) \ No newline at end of file + perfect_graph = defect_free_pegasus(G) + draw_yield(G, pegasus_layout(perfect_graph), perfect_graph, **kwargs) diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index cb379b5d..61621bc5 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -23,8 +23,20 @@ from networkx import draw from dwave_networkx.drawing.distinguishable_colors import distinguishable_color_map +from ..utils.decorators import topology_dispatch -__all__ = ['draw_qubit_graph'] +__all__ = ['draw_qubit_graph_generic', 'draw_embedding_generic', 'draw_yield_generic', 'qubit_layout'] + + +@topology_dispatch +def qubit_layout(G): + raise NotImplementedError(f"no dispatch defined for layouts of {G.graph.get('family')} graphs") + +def _get_layout_and_kwargs(G, layout, kwargs): + if layout is None: + kwargs, layout_args = qubit_layout.pop_kwargs(G, kwargs) + layout = qubit_layout(G, **layout_args) + return layout, kwargs def draw_qubit_graph(G, layout, linear_biases=None, quadratic_biases=None, @@ -172,6 +184,44 @@ def node_color(v): draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, **kwargs) +def draw_qubit_graph_generic(G, linear_biases=None, quadratic_biases=None, + nodelist=None, edgelist=None, midpoint=None, + **kwargs): + """Draws graph G with a layout computed according to its topology family. + + If `linear_biases` and/or `quadratic_biases` are provided, these + are visualized on the plot. + + Parameters + ---------- + G : NetworkX graph + The graph to be drawn. Must be a dwave_networkx qubit topology. + + 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, 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. + + midpoint : float (optional, default None) + A float that specifies where the center of the colormap should + be. If not provided, the colormap will default to the middle of + min/max values provided. + + 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. + + """ + + layout, kwargs = _get_layout_and_kwargs(G, kwargs) + return draw_qubit_graph(G, layout, linear_biases=linear_biases, quadratic_biases=quadratic_biases, + nodelist=nodelist, edgelist=edgelist, midpoint=midpoint, **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, @@ -370,6 +420,70 @@ def show(p, q, u, v): return True node_color=node_color, edge_color=edge_color, labels=labels, **kwargs) +def draw_embedding_generic(G, 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): + """Draws an embedding onto the graph G. + + If interaction_edges is not None, then only display the couplers in that + list. If embedded_graph is not None, the only display the couplers between + chains with intended couplings according to embedded_graph. + + The layout of G is computed according to its topology family. + + Parameters + ---------- + G : NetworkX graph + The graph to be drawn. Must be a dwave_networkx qubit topology. + + emb : dict + A dict of chains associated with each node in G. Should be + of the form {node: chain, ...}. Chains should be iterables + of qubit labels (qubits are nodes in G). + + embedded_graph : NetworkX graph (optional, default None) + A graph which contains all keys of emb as nodes. If specified, + edges of G will be considered interactions if and only if they + exist between two chains of emb if their keys are connected by + an edge in embedded_graph + + interaction_edges : list (optional, default None) + A list of edges which will be used as interactions. + + show_labels: boolean (optional, default False) + If show_labels is True, then each chain in emb is labelled with its key. + + chain_color : dict (optional, default None) + A dict of colors associated with each key in emb. Should be + of the form {node: rgba_color, ...}. Colors should be length-4 + tuples of floats between 0 and 1 inclusive. If chain_color is None, + each chain will be assigned a different color. + + cmap : str or matplotlib colormap (optional, default None) + A matplotlib colormap for coloring of chains. Only used if chain_color + is None. + + unused_color : tuple or color string (optional, default (0.9,0.9,0.9,1.0)) + The color to use for nodes and edges of G which are not involved + in chains, and edges which are neither chain edges nor interactions. + If unused_color is None, these nodes and edges will not be shown at all. + + overlapped_embedding: boolean (optional, default False) + If overlapped_embedding is True, then chains in emb may overlap (contain + the same vertices in G), and the drawing will display these overlaps as + concentric circles. + + 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. + """ + layout, kwargs = _get_layout_and_kwargs(G, kwargs) + return draw_embedding(G, layout, emb=emb, embedded_graph=embedded_graph, interaction_edges=interaction_edges, + chain_color=chain_color, unused_color=unused_color, cmap=cmap, + show_labels=show_labels, overlapped_embedding=overlapped_embedding, **kwargs) + def compute_bags(C, emb): # Given an overlapped embedding, compute the set of source nodes embedded at every target node. @@ -430,7 +544,6 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9, 0.9, 0.9, 1.0), perfect_graph : NetworkX graph The graph to be drawn with highlighted faults - unused_color : tuple or color string (optional, default (0.9,0.9,0.9,1.0)) The color to use for nodes and edges of G which are not faults. If unused_color is None, these nodes and edges will not be shown at all. @@ -487,3 +600,43 @@ 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 draw_yield_generic(G, linear_biases=None, 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 graph G with highlighted faults. + + The graph layout will be computed according to the topology family of G. + + Parameters + ---------- + G : NetworkX graph + The graph to be parsed for faults. Must be a dwave_networkx qubit topology. + + unused_color : tuple or color string (optional, default (0.9,0.9,0.9,1.0)) + The color to use for nodes and edges of G which are not faults. + 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 + length-4 tuples of floats between 0 and 1 inclusive. + + fault_shape : string, optional (default='x') + The shape of the fault nodes. Specification is as matplotlib.scatter + marker, one of 'so^>v Date: Tue, 31 Dec 2024 14:53:04 -0800 Subject: [PATCH 2/8] better approach for decorators and dispatching --- dwave_networkx/__future_enum.py | 27 ---- dwave_networkx/drawing/__init__.py | 7 -- dwave_networkx/drawing/chimera_layout.py | 12 +- dwave_networkx/drawing/pegasus_layout.py | 12 +- dwave_networkx/drawing/qubit_layout.py | 153 +---------------------- dwave_networkx/drawing/zephyr_layout.py | 11 +- dwave_networkx/generators/chimera.py | 10 +- dwave_networkx/generators/common.py | 7 -- dwave_networkx/generators/pegasus.py | 9 +- dwave_networkx/generators/zephyr.py | 10 +- dwave_networkx/topology.py | 89 +++++++++++-- dwave_networkx/utils/decorators.py | 61 +++++---- 12 files changed, 159 insertions(+), 249 deletions(-) delete mode 100644 dwave_networkx/__future_enum.py diff --git a/dwave_networkx/__future_enum.py b/dwave_networkx/__future_enum.py deleted file mode 100644 index 6efb51b1..00000000 --- a/dwave_networkx/__future_enum.py +++ /dev/null @@ -1,27 +0,0 @@ -# delete after Py3.10 is dropped -# The implementation of StrEnum and global_enum are strongly informed by -# those found in Py3.11 but are significantly less general. - -from enum import Enum as _Enum -import sys as _sys - -class StrEnum(str, _Enum): - def __new__(cls, value): - member = str.__new__(cls, value) - member._value_ = value - return member - - @staticmethod - def _generate_next_value_(name, *_): - #make auto() work - return name.lower() - - def __str__(self): - return self._value_ - -def global_enum(cls): - toplevel = cls.__module__.split('.')[-1] - cls.__repr__ = lambda self: f'{toplevel}.{self._name_}' - _sys.modules[cls.__module__].__dict__.update(cls.__members__) - return cls - diff --git a/dwave_networkx/drawing/__init__.py b/dwave_networkx/drawing/__init__.py index 1dbe544f..8466d8f3 100644 --- a/dwave_networkx/drawing/__init__.py +++ b/dwave_networkx/drawing/__init__.py @@ -15,10 +15,3 @@ from dwave_networkx.drawing.chimera_layout import * from dwave_networkx.drawing.pegasus_layout import * from dwave_networkx.drawing.zephyr_layout import * - -from dwave_networkx.drawing.qubit_layout import ( - draw_qubit_graph_generic as draw_qubit_graph, - draw_embedding_generic as draw_embedding, - draw_yield_generic as draw_yield, - qubit_layout as qubit_layout -) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index eb43fb17..a1281063 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -20,13 +20,14 @@ import networkx as nx from networkx import draw -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, qubit_layout -from dwave_networkx.generators.chimera import chimera_graph, find_chimera_indices, chimera_coordinates, defect_free_chimera +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield +from dwave_networkx.generators.chimera import find_chimera_indices, chimera_coordinates, defect_free_chimera from ..topology import CHIMERA __all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield'] -@qubit_layout.install_dispatch(CHIMERA, pop_kwargs=('scale', 'center', 'dim')) + +@CHIMERA.layout.implementation def chimera_layout(G, scale=1., center=None, dim=2): """Positions the nodes of graph ``G`` in a Chimera layout. @@ -71,7 +72,7 @@ def chimera_layout(G, scale=1., center=None, dim=2): # now we get chimera coordinates for the translation # first, check if we made it - if G.graph.get("family") == "chimera": + if G.graph.get("family") == CHIMERA: m = G.graph['rows'] n = G.graph['columns'] t = G.graph['tile'] @@ -189,6 +190,7 @@ def _xy_coords(i, j, u, k): return _xy_coords +@CHIMERA.draw.implementation def draw_chimera(G, **kwargs): """Draws graph ``G`` in a Chimera layout. @@ -230,6 +232,7 @@ def draw_chimera(G, **kwargs): draw_qubit_graph(G, chimera_layout(G), **kwargs) +@CHIMERA.draw_embedding.implementation def draw_chimera_embedding(G, *args, **kwargs): """Draws an embedding onto the Chimera graph ``G``. @@ -282,6 +285,7 @@ def draw_chimera_embedding(G, *args, **kwargs): draw_embedding(G, chimera_layout(G), *args, **kwargs) +@CHIMERA.draw_yield.implementation def draw_chimera_yield(G, **kwargs): """Draws graph ``G`` with highlighted faults. diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index 753ecd77..3d3cc697 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -19,8 +19,8 @@ from networkx import draw import numpy as np -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield, qubit_layout -from dwave_networkx.generators.pegasus import pegasus_graph, pegasus_coordinates, defect_free_pegasus +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield +from dwave_networkx.generators.pegasus import pegasus_coordinates, defect_free_pegasus from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d from ..topology import PEGASUS @@ -30,7 +30,7 @@ 'draw_pegasus_yield', ] -@qubit_layout.install_dispatch(PEGASUS, pop_kwargs = ('scale', 'center', 'dim', 'crosses')) +@PEGASUS.layout.implementation def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): """Positions the nodes of graph ``G`` in a Pegasus topology. @@ -70,7 +70,7 @@ def pegasus_layout(G, scale=1., center=None, dim=2, crosses=False): """ - if not isinstance(G, nx.Graph) or G.graph.get("family") != "pegasus": + 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': @@ -188,7 +188,7 @@ def _xy_coords(u, w, k, z): return _xy_coords - +@PEGASUS.draw.implementation def draw_pegasus(G, crosses=False, **kwargs): """Draws graph ``G`` in a Pegasus topology. @@ -236,6 +236,7 @@ def draw_pegasus(G, crosses=False, **kwargs): draw_qubit_graph(G, pegasus_layout(G, crosses=crosses), **kwargs) +@PEGASUS.draw_embedding.implementation def draw_pegasus_embedding(G, *args, **kwargs): """Draws an embedding onto Pegasus graph ``G``. @@ -292,6 +293,7 @@ def draw_pegasus_embedding(G, *args, **kwargs): crosses = kwargs.pop("crosses", False) draw_embedding(G, pegasus_layout(G, crosses=crosses), *args, **kwargs) +@PEGASUS.draw_yield.implementation def draw_pegasus_yield(G, **kwargs): """Draws graph ``G`` with highlighted faults. diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index 61621bc5..9aefa587 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -23,21 +23,10 @@ from networkx import draw from dwave_networkx.drawing.distinguishable_colors import distinguishable_color_map -from ..utils.decorators import topology_dispatch -__all__ = ['draw_qubit_graph_generic', 'draw_embedding_generic', 'draw_yield_generic', 'qubit_layout'] +__all__ = ['draw_qubit_graph_generic', 'draw_embedding_generic', 'draw_yield_generic'] -@topology_dispatch -def qubit_layout(G): - raise NotImplementedError(f"no dispatch defined for layouts of {G.graph.get('family')} graphs") - -def _get_layout_and_kwargs(G, layout, kwargs): - if layout is None: - kwargs, layout_args = qubit_layout.pop_kwargs(G, kwargs) - layout = qubit_layout(G, **layout_args) - return layout, kwargs - def draw_qubit_graph(G, layout, linear_biases=None, quadratic_biases=None, nodelist=None, edgelist=None, midpoint=None, @@ -184,44 +173,6 @@ def node_color(v): draw(G, layout, ax=ax, nodelist=nodelist, edgelist=edgelist, **kwargs) -def draw_qubit_graph_generic(G, linear_biases=None, quadratic_biases=None, - nodelist=None, edgelist=None, midpoint=None, - **kwargs): - """Draws graph G with a layout computed according to its topology family. - - If `linear_biases` and/or `quadratic_biases` are provided, these - are visualized on the plot. - - Parameters - ---------- - G : NetworkX graph - The graph to be drawn. Must be a dwave_networkx qubit topology. - - 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, 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. - - midpoint : float (optional, default None) - A float that specifies where the center of the colormap should - be. If not provided, the colormap will default to the middle of - min/max values provided. - - 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. - - """ - - layout, kwargs = _get_layout_and_kwargs(G, kwargs) - return draw_qubit_graph(G, layout, linear_biases=linear_biases, quadratic_biases=quadratic_biases, - nodelist=nodelist, edgelist=edgelist, midpoint=midpoint, **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, @@ -420,70 +371,6 @@ def show(p, q, u, v): return True node_color=node_color, edge_color=edge_color, labels=labels, **kwargs) -def draw_embedding_generic(G, 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): - """Draws an embedding onto the graph G. - - If interaction_edges is not None, then only display the couplers in that - list. If embedded_graph is not None, the only display the couplers between - chains with intended couplings according to embedded_graph. - - The layout of G is computed according to its topology family. - - Parameters - ---------- - G : NetworkX graph - The graph to be drawn. Must be a dwave_networkx qubit topology. - - emb : dict - A dict of chains associated with each node in G. Should be - of the form {node: chain, ...}. Chains should be iterables - of qubit labels (qubits are nodes in G). - - embedded_graph : NetworkX graph (optional, default None) - A graph which contains all keys of emb as nodes. If specified, - edges of G will be considered interactions if and only if they - exist between two chains of emb if their keys are connected by - an edge in embedded_graph - - interaction_edges : list (optional, default None) - A list of edges which will be used as interactions. - - show_labels: boolean (optional, default False) - If show_labels is True, then each chain in emb is labelled with its key. - - chain_color : dict (optional, default None) - A dict of colors associated with each key in emb. Should be - of the form {node: rgba_color, ...}. Colors should be length-4 - tuples of floats between 0 and 1 inclusive. If chain_color is None, - each chain will be assigned a different color. - - cmap : str or matplotlib colormap (optional, default None) - A matplotlib colormap for coloring of chains. Only used if chain_color - is None. - - unused_color : tuple or color string (optional, default (0.9,0.9,0.9,1.0)) - The color to use for nodes and edges of G which are not involved - in chains, and edges which are neither chain edges nor interactions. - If unused_color is None, these nodes and edges will not be shown at all. - - overlapped_embedding: boolean (optional, default False) - If overlapped_embedding is True, then chains in emb may overlap (contain - the same vertices in G), and the drawing will display these overlaps as - concentric circles. - - 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. - """ - layout, kwargs = _get_layout_and_kwargs(G, kwargs) - return draw_embedding(G, layout, emb=emb, embedded_graph=embedded_graph, interaction_edges=interaction_edges, - chain_color=chain_color, unused_color=unused_color, cmap=cmap, - show_labels=show_labels, overlapped_embedding=overlapped_embedding, **kwargs) - def compute_bags(C, emb): # Given an overlapped embedding, compute the set of source nodes embedded at every target node. @@ -600,43 +487,5 @@ 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 draw_yield_generic(G, linear_biases=None, 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 graph G with highlighted faults. - - The graph layout will be computed according to the topology family of G. - Parameters - ---------- - G : NetworkX graph - The graph to be parsed for faults. Must be a dwave_networkx qubit topology. - - unused_color : tuple or color string (optional, default (0.9,0.9,0.9,1.0)) - The color to use for nodes and edges of G which are not faults. - 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 - length-4 tuples of floats between 0 and 1 inclusive. - - fault_shape : string, optional (default='x') - The shape of the fault nodes. Specification is as matplotlib.scatter - marker, one of 'so^>v Date: Tue, 31 Dec 2024 15:12:15 -0800 Subject: [PATCH 3/8] improve test coverage; remove unused function --- dwave_networkx/topology.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/dwave_networkx/topology.py b/dwave_networkx/topology.py index cc252926..a0ab1004 100644 --- a/dwave_networkx/topology.py +++ b/dwave_networkx/topology.py @@ -39,9 +39,6 @@ def __str__(self): def __hash__(self): return hash(self.value) - def _install_dispatch(self, name): - return partial(setattr, self, name) - def __init__(self, name): # DEV NOTE # From 569df4ed058cab5e26e4ae81371380a4c0249025 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Tue, 31 Dec 2024 15:28:32 -0800 Subject: [PATCH 4/8] added tests for defect_free functions --- tests/test_generator_chimera.py | 15 ++++++++++++++- tests/test_generator_pegasus.py | 22 ++++++++++++++++++++++ tests/test_generator_zephyr.py | 14 +++++++++++++- 3 files changed, 49 insertions(+), 2 deletions(-) diff --git a/tests/test_generator_chimera.py b/tests/test_generator_chimera.py index 4317ba7c..4f6b96b5 100644 --- a/tests/test_generator_chimera.py +++ b/tests/test_generator_chimera.py @@ -370,7 +370,20 @@ def test_edge_list(self): edge_list = [(0, t), (0, t)] G = dnx.chimera_graph(m, edge_list=edge_list, check_edge_list=True) - + + def test_defect_free_chimera(self): + G = dnx.chimera_graph(2, 4, 2) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.chimera.defect_free_chimera(H)) + + G = dnx.chimera_graph(2, 4, 2, coordinates=True) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.chimera.defect_free_chimera(H)) + class TestChimeraTorus(unittest.TestCase): def test(self): for m in range(1,4): diff --git a/tests/test_generator_pegasus.py b/tests/test_generator_pegasus.py index 698d2df5..a1524a3e 100644 --- a/tests/test_generator_pegasus.py +++ b/tests/test_generator_pegasus.py @@ -402,6 +402,28 @@ def test_edge_list(self): G = dnx.pegasus_graph(m, edge_list=edge_list, fabric_only=False, check_edge_list=True) + def test_defect_free_pegasus(self): + G = dnx.pegasus_graph(3, offset_lists=[(10,)*12, (6,)*12]) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + + G = dnx.pegasus_graph(3, offsets_index=1, coordinates=True) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + + G = dnx.pegasus_graph(3, nice_coordinates=True) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + + + + class TestTupleFragmentation(unittest.TestCase): def test_empty_list(self): diff --git a/tests/test_generator_zephyr.py b/tests/test_generator_zephyr.py index 246f453f..0e8fa47c 100644 --- a/tests/test_generator_zephyr.py +++ b/tests/test_generator_zephyr.py @@ -274,7 +274,19 @@ def test_edge_list(self): G = dnx.zephyr_graph(m, t, edge_list=edge_list, check_edge_list=True) - + def test_defect_free_zephyr(self): + G = dnx.zephyr_graph(2, 4) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) + + G = dnx.zephyr_graph(2, 2, coordinates=True) + H = G.copy() + H.remove_nodes_from([*H][::3]) + H.remove_edges_from([*H.edges][::3]) + self.assertEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) + class TestZephyrTorus(unittest.TestCase): def test(self): for m in [2,3,4]: From 5fcbcb5e3e08834d69d1329b98843b861e11700a Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Tue, 31 Dec 2024 15:38:58 -0800 Subject: [PATCH 5/8] corrected failing tests --- dwave_networkx/generators/pegasus.py | 4 ++-- tests/test_generator_chimera.py | 4 ++-- tests/test_generator_pegasus.py | 6 +++--- tests/test_generator_zephyr.py | 4 ++-- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/dwave_networkx/generators/pegasus.py b/dwave_networkx/generators/pegasus.py index 4d7f4dc7..c2e52ca7 100644 --- a/dwave_networkx/generators/pegasus.py +++ b/dwave_networkx/generators/pegasus.py @@ -338,12 +338,12 @@ def defect_free_pegasus(G): if family != PEGASUS: raise ValueError("G must be constructed by dwave_networkx.pegasus_graph") - offsets = eval(p.name.replace('pegasus_graph', ''))[1] + offsets = eval(G.name.replace('pegasus_graph', ''))[1] args = attrib['rows'], kwargs = { 'offsets_index' if isinstance(offsets, int) else 'offset_lists': offsets, 'coordinates': 'coordinate' == attrib['labels'], - 'nice': 'nice' == attrib['labels'], + 'nice_coordinates': 'nice' == attrib['labels'], } return pegasus_graph(*args, **kwargs) diff --git a/tests/test_generator_chimera.py b/tests/test_generator_chimera.py index 4f6b96b5..4960abc2 100644 --- a/tests/test_generator_chimera.py +++ b/tests/test_generator_chimera.py @@ -376,13 +376,13 @@ def test_defect_free_chimera(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.chimera.defect_free_chimera(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) G = dnx.chimera_graph(2, 4, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.chimera.defect_free_chimera(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) class TestChimeraTorus(unittest.TestCase): def test(self): diff --git a/tests/test_generator_pegasus.py b/tests/test_generator_pegasus.py index a1524a3e..2be2a49a 100644 --- a/tests/test_generator_pegasus.py +++ b/tests/test_generator_pegasus.py @@ -407,19 +407,19 @@ def test_defect_free_pegasus(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) G = dnx.pegasus_graph(3, offsets_index=1, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) G = dnx.pegasus_graph(3, nice_coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) diff --git a/tests/test_generator_zephyr.py b/tests/test_generator_zephyr.py index 0e8fa47c..f6b263be 100644 --- a/tests/test_generator_zephyr.py +++ b/tests/test_generator_zephyr.py @@ -279,13 +279,13 @@ def test_defect_free_zephyr(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) G = dnx.zephyr_graph(2, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) + self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) class TestZephyrTorus(unittest.TestCase): def test(self): From 8f26d6a22be1cb3f5c0547c541cdd15d0c612839 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Tue, 31 Dec 2024 15:53:44 -0800 Subject: [PATCH 6/8] fixed failing tests using old networkx --- tests/common.py | 2 ++ tests/test_generator_chimera.py | 5 +++-- tests/test_generator_pegasus.py | 8 +++++--- tests/test_generator_zephyr.py | 5 +++-- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 tests/common.py diff --git a/tests/common.py b/tests/common.py new file mode 100644 index 00000000..65fceb3f --- /dev/null +++ b/tests/common.py @@ -0,0 +1,2 @@ +def graphs_equal(G, H): + return type(G) == type(H) and {*G} == {*H} and len(G.edges) == len(H.edges) and all(H.has_edge(*e) for e in G.edges) diff --git a/tests/test_generator_chimera.py b/tests/test_generator_chimera.py index 4960abc2..41425a97 100644 --- a/tests/test_generator_chimera.py +++ b/tests/test_generator_chimera.py @@ -17,6 +17,7 @@ import networkx as nx import dwave_networkx as dnx import numpy as np +from .common import graphs_equal alpha_map = dict(enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')) @@ -376,13 +377,13 @@ def test_defect_free_chimera(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) + self.assertTrue(graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) G = dnx.chimera_graph(2, 4, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) + self.assertTrue(graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) class TestChimeraTorus(unittest.TestCase): def test(self): diff --git a/tests/test_generator_pegasus.py b/tests/test_generator_pegasus.py index 2be2a49a..5b8608fb 100644 --- a/tests/test_generator_pegasus.py +++ b/tests/test_generator_pegasus.py @@ -27,6 +27,8 @@ get_tuple_fragmentation_fn, ) +from .common import graphs_equal + alpha_map = dict(enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')) @@ -407,19 +409,19 @@ def test_defect_free_pegasus(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) G = dnx.pegasus_graph(3, offsets_index=1, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) G = dnx.pegasus_graph(3, nice_coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) diff --git a/tests/test_generator_zephyr.py b/tests/test_generator_zephyr.py index f6b263be..749f5ea3 100644 --- a/tests/test_generator_zephyr.py +++ b/tests/test_generator_zephyr.py @@ -17,6 +17,7 @@ import networkx as nx import dwave_networkx as dnx import numpy as np +from .common import graphs_equal class TestZephyrGraph(unittest.TestCase): def test_single_tile(self): @@ -279,13 +280,13 @@ def test_defect_free_zephyr(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) + self.assertTrue(graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) G = dnx.zephyr_graph(2, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(nx.utils.graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) + self.assertTrue(graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) class TestZephyrTorus(unittest.TestCase): def test(self): From b60e4de34362ac2d8680c2b1f3fabde7a094cf83 Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Wed, 1 Jan 2025 10:11:43 -0800 Subject: [PATCH 7/8] tests for ImplementationHook --- dwave_networkx/utils/decorators.py | 2 +- tests/test_decorators.py | 30 ++++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/dwave_networkx/utils/decorators.py b/dwave_networkx/utils/decorators.py index 0672b067..378849fb 100644 --- a/dwave_networkx/utils/decorators.py +++ b/dwave_networkx/utils/decorators.py @@ -116,5 +116,5 @@ def implementation(self, f): def __call__(self, *args, **kwargs): raise NotImplementedError( - f"the {self.name} method of {self.obj:r} has not been attached" + f"the {self.name} method of {self.obj!r} has not been attached" ) diff --git a/tests/test_decorators.py b/tests/test_decorators.py index 81e771a2..7fcb9f0f 100644 --- a/tests/test_decorators.py +++ b/tests/test_decorators.py @@ -15,7 +15,10 @@ import unittest import dwave_networkx as dnx -from dwave_networkx.utils import binary_quadratic_model_sampler +from dwave_networkx.utils.decorators import ( + binary_quadratic_model_sampler, + ImplementationHook, +) class MockSampler: @@ -31,7 +34,7 @@ def mock_function(G, sampler=None, **sampler_args): assert sampler is not None -class TestDecorators(unittest.TestCase): +class TestBQMSampler(unittest.TestCase): def test_default_set(self): dnx.set_default_sampler(MockSampler()) @@ -47,3 +50,26 @@ def test_no_sampler_set(self): def test_sampler_provided(self): mock_function(0, MockSampler()) + + +class HooksNeeded: + pass + + +hooks_needed = HooksNeeded() +hooks_needed.implemented = ImplementationHook(hooks_needed, "implemented") +hooks_needed.not_implemented = ImplementationHook(hooks_needed, "not_implemented") + + +@hooks_needed.implemented.implementation +def implemented(): + pass + + +class TestImplementationHook(unittest.TestCase): + def test_implemented_hook(self): + hooks_needed.implemented() + + def test_not_implemented_hook(self): + with self.assertRaises(NotImplementedError): + hooks_needed.not_implemented() From e8bd004df911da9a0524692d24475ecb8c5237ed Mon Sep 17 00:00:00 2001 From: Kelly Boothby Date: Thu, 2 Jan 2025 12:28:58 -0800 Subject: [PATCH 8/8] review comments --- dwave_networkx/drawing/chimera_layout.py | 2 +- dwave_networkx/drawing/pegasus_layout.py | 2 +- dwave_networkx/drawing/qubit_layout.py | 2 +- dwave_networkx/drawing/zephyr_layout.py | 4 +- dwave_networkx/generators/chimera.py | 4 +- dwave_networkx/generators/pegasus.py | 6 +-- dwave_networkx/generators/zephyr.py | 6 +-- dwave_networkx/topology.py | 50 ++++++++++++++++++------ tests/common.py | 49 ++++++++++++++++++++++- tests/test_generator_chimera.py | 26 +++++------- tests/test_generator_pegasus.py | 41 ++++++++----------- tests/test_generator_zephyr.py | 26 +++++------- 12 files changed, 134 insertions(+), 84 deletions(-) diff --git a/dwave_networkx/drawing/chimera_layout.py b/dwave_networkx/drawing/chimera_layout.py index a1281063..df4d0306 100644 --- a/dwave_networkx/drawing/chimera_layout.py +++ b/dwave_networkx/drawing/chimera_layout.py @@ -22,7 +22,7 @@ from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield from dwave_networkx.generators.chimera import find_chimera_indices, chimera_coordinates, defect_free_chimera -from ..topology import CHIMERA +from dwave_networkx.topology import CHIMERA __all__ = ['chimera_layout', 'draw_chimera', 'draw_chimera_embedding', 'draw_chimera_yield'] diff --git a/dwave_networkx/drawing/pegasus_layout.py b/dwave_networkx/drawing/pegasus_layout.py index 3d3cc697..58997fc2 100644 --- a/dwave_networkx/drawing/pegasus_layout.py +++ b/dwave_networkx/drawing/pegasus_layout.py @@ -22,7 +22,7 @@ from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield from dwave_networkx.generators.pegasus import pegasus_coordinates, defect_free_pegasus from dwave_networkx.drawing.chimera_layout import chimera_node_placer_2d -from ..topology import PEGASUS +from dwave_networkx.topology import PEGASUS __all__ = ['pegasus_layout', 'draw_pegasus', diff --git a/dwave_networkx/drawing/qubit_layout.py b/dwave_networkx/drawing/qubit_layout.py index 9aefa587..90adb498 100644 --- a/dwave_networkx/drawing/qubit_layout.py +++ b/dwave_networkx/drawing/qubit_layout.py @@ -24,7 +24,7 @@ from dwave_networkx.drawing.distinguishable_colors import distinguishable_color_map -__all__ = ['draw_qubit_graph_generic', 'draw_embedding_generic', 'draw_yield_generic'] +__all__ = ['draw_qubit_graph'] diff --git a/dwave_networkx/drawing/zephyr_layout.py b/dwave_networkx/drawing/zephyr_layout.py index c26bb659..4d79cfe8 100644 --- a/dwave_networkx/drawing/zephyr_layout.py +++ b/dwave_networkx/drawing/zephyr_layout.py @@ -20,9 +20,9 @@ from networkx import draw import numpy as np -from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding +from dwave_networkx.drawing.qubit_layout import draw_qubit_graph, draw_embedding, draw_yield from dwave_networkx.generators.zephyr import zephyr_coordinates, defect_free_zephyr -from ..topology import ZEPHYR +from dwave_networkx.topology import ZEPHYR __all__ = ['zephyr_layout', 'draw_zephyr', diff --git a/dwave_networkx/generators/chimera.py b/dwave_networkx/generators/chimera.py index 88f0ad8e..97365849 100644 --- a/dwave_networkx/generators/chimera.py +++ b/dwave_networkx/generators/chimera.py @@ -26,8 +26,8 @@ from itertools import product -from .common import _add_compatible_nodes, _add_compatible_edges, _add_compatible_terms -from ..topology import CHIMERA +from dwave_networkx.generators.common import _add_compatible_nodes, _add_compatible_edges, _add_compatible_terms +from dwave_networkx.topology import CHIMERA __all__ = ['chimera_graph', 'chimera_coordinates', diff --git a/dwave_networkx/generators/pegasus.py b/dwave_networkx/generators/pegasus.py index c2e52ca7..e2b718c9 100644 --- a/dwave_networkx/generators/pegasus.py +++ b/dwave_networkx/generators/pegasus.py @@ -23,9 +23,9 @@ import warnings from itertools import product -from .chimera import _chimera_coordinates_cache -from .common import _add_compatible_edges, _add_compatible_nodes, _add_compatible_terms -from ..topology import CHIMERA, PEGASUS +from dwave_networkx.generators.chimera import _chimera_coordinates_cache +from dwave_networkx.generators.common import _add_compatible_edges, _add_compatible_nodes, _add_compatible_terms +from dwave_networkx.topology import CHIMERA, PEGASUS __all__ = ['pegasus_graph', 'pegasus_coordinates', diff --git a/dwave_networkx/generators/zephyr.py b/dwave_networkx/generators/zephyr.py index 6b41634f..8691c4eb 100644 --- a/dwave_networkx/generators/zephyr.py +++ b/dwave_networkx/generators/zephyr.py @@ -23,10 +23,10 @@ from dwave_networkx.exceptions import DWaveNetworkXException -from .chimera import _chimera_coordinates_cache +from dwave_networkx.generators.chimera import _chimera_coordinates_cache -from .common import _add_compatible_edges, _add_compatible_nodes, _add_compatible_terms -from ..topology import CHIMERA, ZEPHYR +from dwave_networkx.generators.common import _add_compatible_edges, _add_compatible_nodes, _add_compatible_terms +from dwave_networkx.topology import CHIMERA, ZEPHYR __all__ = ['zephyr_graph', diff --git a/dwave_networkx/topology.py b/dwave_networkx/topology.py index a0ab1004..950c71dd 100644 --- a/dwave_networkx/topology.py +++ b/dwave_networkx/topology.py @@ -1,19 +1,47 @@ -from enum import Enum, auto +# Copyright 2024 D-Wave +# +# 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. + + +from enum import Enum as _Enum, auto as _auto try: - from enum import global_enum + from enum import global_enum as _global_enum except ImportError: # delete after Py3.10 EOL (2026/10) - import sys + def _global_enum(cls): + """This function is a specialized version of global_enum defined in py3.11 + + We've omitted the functionality we aren't using, and kept the two pieces + that are important. + + 1. The members of an enum should be exported to the module containing + them: `topology.ZEPHYR` is `topology.Topology.ZEPHYR` + 2. When `repr` is called on an enum member, it should look like + 'topology.ZEPHYR' rather than '' + """ + import sys - def global_enum(cls): toplevel = cls.__module__.split(".")[-1] cls.__repr__ = lambda self: f"{toplevel}.{self._name_}" + # the following two lines are quoted directly from CPython. It's hard + # to imagine another way of writing this; hopefully this constitutes + # fair use. sys.modules[cls.__module__].__dict__.update(cls.__members__) return cls -from .utils.decorators import ImplementationHook +from dwave_networkx.utils.decorators import ImplementationHook as _ImplementationHook class TopologyFamily: @@ -50,17 +78,17 @@ def __init__(self, name): # # This is accomplished by using an ImplementationHook object. for slot in self.__slots__: - setattr(self, slot, ImplementationHook(self, slot)) + setattr(self, slot, _ImplementationHook(self, slot)) -class TopologyEnum(TopologyFamily, Enum): +class TopologyEnum(TopologyFamily, _Enum): @staticmethod def _generate_next_value_(name, *_): # make auto() work like in StrEnum return name.lower() -@global_enum +@_global_enum class Topology(TopologyEnum): """An enumeration of qubit topologies supported by dwave_networkx. @@ -69,9 +97,9 @@ class Topology(TopologyEnum): functions specific to that topology. """ - CHIMERA = auto() - PEGASUS = auto() - ZEPHYR = auto() + CHIMERA = _auto() + PEGASUS = _auto() + ZEPHYR = _auto() __all__ = ["Topology", *Topology.__members__] diff --git a/tests/common.py b/tests/common.py index 65fceb3f..adb67e2d 100644 --- a/tests/common.py +++ b/tests/common.py @@ -1,2 +1,47 @@ -def graphs_equal(G, H): - return type(G) == type(H) and {*G} == {*H} and len(G.edges) == len(H.edges) and all(H.has_edge(*e) for e in G.edges) +# Copyright 2025 D-Wave +# +# 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. + + +class NullError(BaseException): + def __init__(self, *args): + raise RuntimeError("do not use this exception class") + + +class GraphTesting: + def assertGraphsEqual(self, G, H, msg=None): + try: + self.assertIsInstance( + G, type(H), "graphs G and H should have the same type" + ) + self.assertEqual( + set(G.nodes), + set(H.nodes), + "graphs G and H should have the same node set", + ) + self.assertEqual( + len(G.edges), + len(H.edges), + "graphs G and H should have the same number of edges", + ) + for e in G.edges: + self.assertTrue( + H.has_edge(*e), "graphs G and H have different edge sets" + ) + except NullError if msg is None else Exception as e: + # Sorry for the weirdness here. We don't want to create strings on + # the happy path, so we define a NullError that is never raised so + # exceptions fall straight through in that case. + + sub_msg, *_ = e.args + self.fail(f"{msg} : {sub_msg}") diff --git a/tests/test_generator_chimera.py b/tests/test_generator_chimera.py index 41425a97..6c5887d1 100644 --- a/tests/test_generator_chimera.py +++ b/tests/test_generator_chimera.py @@ -17,12 +17,12 @@ import networkx as nx import dwave_networkx as dnx import numpy as np -from .common import graphs_equal +from .common import GraphTesting alpha_map = dict(enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')) -class TestChimeraGraph(unittest.TestCase): +class TestChimeraGraph(unittest.TestCase, GraphTesting): def test_single_tile(self): # fully specified @@ -230,24 +230,15 @@ def test_nonsquare_coordinate_generator(self): def test_graph_relabeling(self): - def graph_equal(g, h): - self.assertEqual(set(g), set(h)) - self.assertEqual( - set(map(tuple, map(sorted, g.edges))), - set(map(tuple, map(sorted, g.edges))) - ) - for v, d in g.nodes(data=True): - self.assertEqual(h.nodes[v], d) - coords = dnx.chimera_coordinates(3) for data in True, False: c3l = dnx.chimera_graph(3, data=data) c3c = dnx.chimera_graph(3, data=data, coordinates=True) - graph_equal(c3l, coords.graph_to_linear(c3c)) - graph_equal(c3l, coords.graph_to_linear(c3l)) - graph_equal(c3c, coords.graph_to_chimera(c3l)) - graph_equal(c3c, coords.graph_to_chimera(c3c)) + self.assertGraphsEqual(c3l, coords.graph_to_linear(c3c)) + self.assertGraphsEqual(c3l, coords.graph_to_linear(c3l)) + self.assertGraphsEqual(c3c, coords.graph_to_chimera(c3l)) + self.assertGraphsEqual(c3c, coords.graph_to_chimera(c3c)) h = dnx.chimera_graph(2) del h.graph['labels'] @@ -377,13 +368,14 @@ def test_defect_free_chimera(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) + self.assertGraphsEqual(G, dnx.generators.chimera.defect_free_chimera(H)) G = dnx.chimera_graph(2, 4, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.chimera.defect_free_chimera(H))) + self.assertGraphsEqual(G, dnx.generators.chimera.defect_free_chimera(H)) + class TestChimeraTorus(unittest.TestCase): def test(self): diff --git a/tests/test_generator_pegasus.py b/tests/test_generator_pegasus.py index 5b8608fb..b7131909 100644 --- a/tests/test_generator_pegasus.py +++ b/tests/test_generator_pegasus.py @@ -27,12 +27,12 @@ get_tuple_fragmentation_fn, ) -from .common import graphs_equal +from .common import GraphTesting alpha_map = dict(enumerate('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz')) -class TestPegasusGraph(unittest.TestCase): +class TestPegasusGraph(unittest.TestCase, GraphTesting): def test_p2(self): G = dnx.pegasus_graph(2, fabric_only=False) @@ -73,7 +73,7 @@ def test(self): g.remove_nodes_from(g_translated.nodes()) self.assertEqual(g.number_of_nodes(),0) -class TestPegasusCoordinates(unittest.TestCase): +class TestPegasusCoordinates(unittest.TestCase, GraphTesting): def test_connected_component(self): test_offsets = [[0] * 12] * 2, [[2] * 12, [6] * 12], [[6] * 12, [2, 2, 6, 6, 10, 10] * 2], [[2, 2, 6, 6, 10, 10] * 2] * 2 @@ -227,15 +227,6 @@ def test_coordinate_subgraphs(self): self.assertEqual(EH, sorted(map(sorted, coords.iter_linear_to_pegasus_pairs(Gn.edges())))) def test_graph_relabeling(self): - def graph_equal(g, h): - self.assertEqual(set(g), set(h)) - self.assertEqual( - set(map(tuple, map(sorted, g.edges))), - set(map(tuple, map(sorted, g.edges))) - ) - for v, d in g.nodes(data=True): - self.assertEqual(h.nodes[v], d) - coords = dnx.pegasus_coordinates(3) nodes_nice = dnx.pegasus_graph(3, nice_coordinates=True) nodes_linear = list(coords.iter_nice_to_linear(nodes_nice)) @@ -246,17 +237,17 @@ def graph_equal(g, h): p3p = dnx.pegasus_graph(3, data=data, coordinates=True).subgraph(nodes_pegasus) p3n = dnx.pegasus_graph(3, data=data, nice_coordinates=True) - graph_equal(p3l, coords.graph_to_linear(p3l)) - graph_equal(p3l, coords.graph_to_linear(p3p)) - graph_equal(p3l, coords.graph_to_linear(p3n)) + self.assertGraphsEqual(p3l, coords.graph_to_linear(p3l)) + self.assertGraphsEqual(p3l, coords.graph_to_linear(p3p)) + self.assertGraphsEqual(p3l, coords.graph_to_linear(p3n)) - graph_equal(p3p, coords.graph_to_pegasus(p3l)) - graph_equal(p3p, coords.graph_to_pegasus(p3p)) - graph_equal(p3p, coords.graph_to_pegasus(p3n)) + self.assertGraphsEqual(p3p, coords.graph_to_pegasus(p3l)) + self.assertGraphsEqual(p3p, coords.graph_to_pegasus(p3p)) + self.assertGraphsEqual(p3p, coords.graph_to_pegasus(p3n)) - graph_equal(p3n, coords.graph_to_nice(p3l)) - graph_equal(p3n, coords.graph_to_nice(p3p)) - graph_equal(p3n, coords.graph_to_nice(p3n)) + self.assertGraphsEqual(p3n, coords.graph_to_nice(p3l)) + self.assertGraphsEqual(p3n, coords.graph_to_nice(p3p)) + self.assertGraphsEqual(p3n, coords.graph_to_nice(p3n)) h = dnx.pegasus_graph(2) del h.graph['labels'] @@ -409,19 +400,21 @@ def test_defect_free_pegasus(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertGraphsEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) G = dnx.pegasus_graph(3, offsets_index=1, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertGraphsEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + G = dnx.pegasus_graph(3, nice_coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.pegasus.defect_free_pegasus(H))) + self.assertGraphsEqual(G, dnx.generators.pegasus.defect_free_pegasus(H)) + diff --git a/tests/test_generator_zephyr.py b/tests/test_generator_zephyr.py index 749f5ea3..e63b2b05 100644 --- a/tests/test_generator_zephyr.py +++ b/tests/test_generator_zephyr.py @@ -17,9 +17,9 @@ import networkx as nx import dwave_networkx as dnx import numpy as np -from .common import graphs_equal +from .common import GraphTesting -class TestZephyrGraph(unittest.TestCase): +class TestZephyrGraph(unittest.TestCase, GraphTesting): def test_single_tile(self): # fully specified @@ -131,25 +131,16 @@ def test_coordinate_subgraphs(self): self.assertEqual(EH, sorted(map(sorted, coords.iter_linear_to_zephyr_pairs(Gn.edges())))) def test_graph_relabeling(self): - def graph_equal(g, h): - self.assertEqual(set(g), set(h)) - self.assertEqual( - set(map(tuple, map(sorted, g.edges))), - set(map(tuple, map(sorted, g.edges))) - ) - for v, d in g.nodes(data=True): - self.assertEqual(h.nodes[v], d) - coords = dnx.zephyr_coordinates(3) for data in True, False: z3l = dnx.zephyr_graph(3, data=data) z3c = dnx.zephyr_graph(3, data=data, coordinates=True) - graph_equal(z3l, coords.graph_to_linear(z3l)) - graph_equal(z3l, coords.graph_to_linear(z3c)) + self.assertGraphsEqual(z3l, coords.graph_to_linear(z3l)) + self.assertGraphsEqual(z3l, coords.graph_to_linear(z3c)) - graph_equal(z3c, coords.graph_to_zephyr(z3c)) - graph_equal(z3c, coords.graph_to_zephyr(z3l)) + self.assertGraphsEqual(z3c, coords.graph_to_zephyr(z3c)) + self.assertGraphsEqual(z3c, coords.graph_to_zephyr(z3l)) h = dnx.zephyr_graph(2) del h.graph['labels'] @@ -280,13 +271,14 @@ def test_defect_free_zephyr(self): H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) + self.assertGraphsEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) G = dnx.zephyr_graph(2, 2, coordinates=True) H = G.copy() H.remove_nodes_from([*H][::3]) H.remove_edges_from([*H.edges][::3]) - self.assertTrue(graphs_equal(G, dnx.generators.zephyr.defect_free_zephyr(H))) + self.assertGraphsEqual(G, dnx.generators.zephyr.defect_free_zephyr(H)) + class TestZephyrTorus(unittest.TestCase): def test(self):