23
23
from networkx import draw
24
24
25
25
from dwave_networkx .drawing .distinguishable_colors import distinguishable_color_map
26
+ from itertools import repeat , chain
27
+
28
+ from numbers import Number
26
29
27
30
__all__ = ['draw_qubit_graph' ]
28
31
29
32
30
33
def draw_qubit_graph (G , layout , linear_biases = {}, quadratic_biases = {},
31
34
nodelist = None , edgelist = None , cmap = None , edge_cmap = None , vmin = None , vmax = None ,
32
- edge_vmin = None , edge_vmax = None , midpoint = None ,
35
+ edge_vmin = None , edge_vmax = None , midpoint = None , line_plot = False ,
33
36
** kwargs ):
34
37
"""Draws graph G according to layout.
35
38
@@ -60,6 +63,16 @@ def draw_qubit_graph(G, layout, linear_biases={}, quadratic_biases={},
60
63
be. If not provided, the colormap will default to the middle of
61
64
min/max values provided.
62
65
66
+ line_plot : boolean (optional, default False)
67
+ If line_plot is True, then qubits are drawn as line segments, and edges
68
+ are drawn either as line segments between qubits, or as circles where
69
+ two qubits overlap. In this drawing style, the interpretation the width
70
+ and node_size parameters (provided in kwargs) determines the area of the
71
+ circles, and line widths, respectively. Qubit line segments are given
72
+ twice the width of edges. Layout should be a dict of the form
73
+ {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes
74
+ are associated with endpoints of n-dimensional line segments.
75
+
63
76
kwargs : optional keywords
64
77
See networkx.draw_networkx() for a description of optional keywords,
65
78
with the exception of the `pos` parameter which is not used by this
@@ -178,15 +191,21 @@ def node_color(v):
178
191
if ax is None :
179
192
ax = fig .add_axes ([0.01 , 0.01 , 0.98 , 0.98 ])
180
193
181
- draw (G , layout , ax = ax , nodelist = nodelist , edgelist = edgelist ,
182
- cmap = cmap , edge_cmap = edge_cmap , vmin = vmin , vmax = vmax , edge_vmin = edge_vmin ,
183
- edge_vmax = edge_vmax ,
184
- ** kwargs )
194
+ if line_plot :
195
+ draw_lineplot (G , layout , ax = ax , nodelist = nodelist , edgelist = edgelist ,
196
+ cmap = cmap , edge_cmap = edge_cmap , vmin = vmin , vmax = vmax , edge_vmin = edge_vmin ,
197
+ edge_vmax = edge_vmax ,
198
+ ** kwargs )
199
+ else :
200
+ draw (G , layout , ax = ax , nodelist = nodelist , edgelist = edgelist ,
201
+ cmap = cmap , edge_cmap = edge_cmap , vmin = vmin , vmax = vmax , edge_vmin = edge_vmin ,
202
+ edge_vmax = edge_vmax ,
203
+ ** kwargs )
185
204
186
205
187
206
def draw_embedding (G , layout , emb , embedded_graph = None , interaction_edges = None ,
188
207
chain_color = None , unused_color = (0.9 ,0.9 ,0.9 ,1.0 ), cmap = None ,
189
- show_labels = False , overlapped_embedding = False , ** kwargs ):
208
+ show_labels = False , overlapped_embedding = False , line_plot = False , ** kwargs ):
190
209
"""Draws an embedding onto the graph G, according to layout.
191
210
192
211
If interaction_edges is not None, then only display the couplers in that
@@ -240,6 +259,16 @@ def draw_embedding(G, layout, emb, embedded_graph=None, interaction_edges=None,
240
259
the same vertices in G), and the drawing will display these overlaps as
241
260
concentric circles.
242
261
262
+ line_plot : boolean (optional, default False)
263
+ If line_plot is True, then qubits are drawn as line segments, and edges
264
+ are drawn either as line segments between qubits, or as circles where
265
+ two qubits overlap. In this drawing style, the interpretation the width
266
+ and node_size parameters (provided in kwargs) determines the area of the
267
+ circles, and line widths, respectively. Qubit line segments are given
268
+ twice the width of edges. Layout should be a dict of the form
269
+ {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes
270
+ are associated with endpoints of n-dimensional line segments.
271
+
243
272
kwargs : optional keywords
244
273
See networkx.draw_networkx() for a description of optional keywords,
245
274
with the exception of the `pos` parameter which is not used by this
@@ -252,6 +281,12 @@ def draw_embedding(G, layout, emb, embedded_graph=None, interaction_edges=None,
252
281
except ImportError :
253
282
raise ImportError ("Matplotlib and numpy required for draw_chimera()" )
254
283
284
+ if line_plot and show_labels :
285
+ raise NotImplementedError ("line_plot style drawings do not currently support node labels" )
286
+
287
+ if line_plot and overlapped_embedding :
288
+ raise NotImplementedError ("line_plot style drawings do not currently support overlapped embeddings" )
289
+
255
290
if nx .utils .is_string_like (unused_color ):
256
291
from matplotlib .colors import colorConverter
257
292
alpha = kwargs .get ('alpha' , 1.0 )
@@ -371,15 +406,25 @@ def show(p, q, u, v): return True
371
406
c = emb [v ]
372
407
labels [list (c )[0 ]] = str (v )
373
408
374
- # draw the background (unused) graph first
375
- if unused_color is not None :
376
- draw (G , layout , nodelist = nodelist , edgelist = background_edgelist ,
377
- node_color = node_color , edge_color = background_edge_color ,
378
- ** kwargs )
409
+ if line_plot :
410
+ if unused_color is not None :
411
+ draw_lineplot (G , layout , nodelist = nodelist , edgelist = background_edgelist ,
412
+ node_color = node_color , edge_color = background_edge_color ,
413
+ ** kwargs )
379
414
380
- draw (G , layout , nodelist = nodelist , edgelist = edgelist ,
381
- node_color = node_color , edge_color = edge_color , labels = labels ,
382
- ** kwargs )
415
+ draw_lineplot (G , layout , nodelist = nodelist , edgelist = edgelist ,
416
+ node_color = node_color , edge_color = edge_color , z_offset = 10 ,
417
+ ** kwargs )
418
+ else :
419
+ # draw the background (unused) graph first
420
+ if unused_color is not None :
421
+ draw (G , layout , nodelist = nodelist , edgelist = background_edgelist ,
422
+ node_color = node_color , edge_color = background_edge_color ,
423
+ ** kwargs )
424
+
425
+ draw (G , layout , nodelist = nodelist , edgelist = edgelist ,
426
+ node_color = node_color , edge_color = edge_color , labels = labels ,
427
+ ** kwargs )
383
428
384
429
385
430
def compute_bags (C , emb ):
@@ -426,7 +471,7 @@ def unoverlapped_embedding(G, emb, interaction_edges):
426
471
def draw_yield (G , layout , perfect_graph , unused_color = (0.9 ,0.9 ,0.9 ,1.0 ),
427
472
fault_color = (1.0 ,0.0 ,0.0 ,1.0 ), fault_shape = 'o' ,
428
473
fault_style = 'dashed' , incident_fault_color = (1.0 ,0.8 ,0.8 ,1.0 ),
429
- ** kwargs ):
474
+ line_plot = False , ** kwargs ):
430
475
"""Draws the given graph G with highlighted faults, according to layout.
431
476
432
477
Parameters
@@ -461,6 +506,16 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0),
461
506
fault_style : string, optional (default='dashed')
462
507
Edge fault line style (solid|dashed|dotted,dashdot)
463
508
509
+ line_plot : boolean (optional, default False)
510
+ If line_plot is True, then qubits are drawn as line segments, and edges
511
+ are drawn either as line segments between qubits, or as circles where
512
+ two qubits overlap. In this drawing style, the interpretation the width
513
+ and node_size parameters (provided in kwargs) determines the area of the
514
+ circles, and line widths, respectively. Qubit line segments are given
515
+ twice the width of edges. Layout should be a dict of the form
516
+ {node: ((x0, y0), (y0, x0)), ...} -- instead of coordinates, the nodes
517
+ are associated with endpoints of n-dimensional line segments.
518
+
464
519
kwargs : optional keywords
465
520
See networkx.draw_networkx() for a description of optional keywords,
466
521
with the exception of the `pos` parameter which is not used by this
@@ -481,22 +536,36 @@ def draw_yield(G, layout, perfect_graph, unused_color=(0.9,0.9,0.9,1.0),
481
536
incident_edgelist = edgeset (perfect_graph .edges ) - edgeset (perfect_graph .subgraph (nodelist ).edges )
482
537
faults_edgelist = edgeset (perfect_graph .subgraph (nodelist ).edges ) - edgeset (G .edges )
483
538
484
- faults_node_color = [fault_color for v in faults_nodelist ]
485
- faults_edge_color = [fault_color for e in faults_edgelist ]
486
- incident_edge_color = [incident_fault_color for e in incident_edgelist ]
539
+ if line_plot :
540
+ if unused_color is not None :
541
+ node_color = [fault_color if v in faults_nodelist else unused_color for v in perfect_graph ]
542
+ long_edgelist = list (edgeset (perfect_graph .edges ) - incident_edgelist - faults_edgelist )
543
+ else :
544
+ node_color = []
545
+ long_edgelist = []
546
+ edge_color = [unused_color ]* len (long_edgelist )
547
+ long_edgelist .extend (incident_edgelist )
548
+ edge_color .extend ([incident_fault_color ]* len (incident_edgelist ))
549
+ long_edgelist .extend (faults_edgelist )
550
+ edge_color .extend ([fault_color ]* len (faults_edgelist ))
551
+ draw_lineplot (perfect_graph , layout , edgelist = long_edgelist , node_color = node_color , edge_color = edge_color , ** kwargs )
552
+ else :
553
+ faults_node_color = [fault_color for v in faults_nodelist ]
554
+ faults_edge_color = [fault_color for e in faults_edgelist ]
555
+ incident_edge_color = [incident_fault_color for e in incident_edgelist ]
487
556
488
- # Draw edges first, in the order (unused, incident, faults)
489
- if unused_color is not None :
490
- unused_edge_color = [unused_color for e in G .edges ()]
491
- nx .draw_networkx_edges (G , layout , edge_color = unused_edge_color , ** kwargs )
492
- nx .draw_networkx_edges (perfect_graph , layout , incident_edgelist , style = fault_style , edge_color = incident_edge_color , ** kwargs )
493
- nx .draw_networkx_edges (perfect_graph , layout , faults_edgelist , style = fault_style , edge_color = faults_edge_color , ** kwargs )
557
+ # Draw edges first, in the order (unused, incident, faults)
558
+ if unused_color is not None :
559
+ unused_edge_color = [unused_color for e in G .edges ()]
560
+ nx .draw_networkx_edges (G , layout , edge_color = unused_edge_color , ** kwargs )
561
+ nx .draw_networkx_edges (perfect_graph , layout , incident_edgelist , style = fault_style , edge_color = incident_edge_color , ** kwargs )
562
+ nx .draw_networkx_edges (perfect_graph , layout , faults_edgelist , style = fault_style , edge_color = faults_edge_color , ** kwargs )
494
563
495
- # Draw nodes second, in the order (unused, faults)
496
- if unused_color is not None :
497
- unused_node_color = [unused_color for e in G ]
498
- nx .draw_networkx_nodes (G , layout , node_color = unused_node_color , ** kwargs )
499
- nx .draw_networkx_nodes (perfect_graph , layout , faults_nodelist , node_shape = fault_shape , node_color = faults_node_color , ** kwargs )
564
+ # Draw nodes second, in the order (unused, faults)
565
+ if unused_color is not None :
566
+ unused_node_color = [unused_color for e in G ]
567
+ nx .draw_networkx_nodes (G , layout , node_color = unused_node_color , ** kwargs )
568
+ nx .draw_networkx_nodes (perfect_graph , layout , faults_nodelist , node_shape = fault_shape , node_color = faults_node_color , ** kwargs )
500
569
501
570
502
571
def normalize_size_and_aspect (scale , node_scale , kwargs ):
@@ -532,3 +601,162 @@ def normalize_size_and_aspect(scale, node_scale, kwargs):
532
601
else :
533
602
kwargs ['width' ] = 2 * (fig_scale / scale )
534
603
604
+
605
+ def draw_lineplot (G , layout , * , ax , node_size , width , nodelist = None ,
606
+ edgelist = None , node_color = 'blue' , edge_color = 'black' ,
607
+ cmap = None , vmin = None , vmax = None , edge_cmap = None ,
608
+ edge_vmin = None , edge_vmax = None , z_offset = 0 ):
609
+ """Draws the graph G with line segments representing nodes
610
+
611
+ This function is meant to be a drop-in replacement for :func:`networkx.draw`
612
+ where nodes are associated with line segments (specified as 2x2 matrices
613
+ [[x0, y0], [x1, y1]]). This function makes significant assumptions about
614
+ the edges of the graph G, that hold when G is a Chimera, Pegasus, or Zephyr
615
+ graph and the line segments are provided by :func:`chimera_layout`,
616
+ :func:`pegasus_layout` and :func:`zephyr_layout` respectively. These graphs
617
+ have three classes of edges:
618
+
619
+ * internal edges between qubits whose line segments are perpendicular
620
+ and intersect at a point, which we draw with a circle located at the
621
+ point of intersection,
622
+ * external edges between qubits whose line segments are colinear, which
623
+ we draw as a line segment between the nearest endpoints, and
624
+ * odd edges between parallel qubits whose line segments are parallel and
625
+ overlap in a perpendicular projection, which we draw as a line segment
626
+ between the midpoints of the respective perpendicular projections
627
+
628
+ Parameters
629
+ ----------
630
+
631
+ G : networkx.Graph
632
+ A graph constructed by :func:`chimera_layout`, :func:`pegasus_layout, or
633
+ :func:`zephyr_layout`
634
+
635
+ pos : dict
636
+ A dictionary with nodes as keys and 2x2 matrices [[x0, y0], [x1, y1]]
637
+ representing the line segments of nodes.
638
+
639
+ ax : matplotlib.Axis
640
+ The matplotlib Axis object to draw the graph on.
641
+
642
+ node_size : float
643
+ The size (in area) of the circles used to depict internal edges.
644
+
645
+ width : float
646
+ The width of line segments associated with edges, and half the width of
647
+ line segments associated with nodes.
648
+
649
+ nodelist : iterable or None (default=None)
650
+ The set of nodes to draw. If None, all nodes from G are drawn.
651
+
652
+ edgelist : iterable or None (default=None)
653
+ The set of edges to draw. If both nodelist and edgelist are None, all
654
+ edges of G are drawn. If edgelist is None, all edges from the subgraph
655
+ ``G.subgraph(nodelist)`` are drawn.
656
+
657
+ node_color : iterable or string (default='blue')
658
+ The sequence of colors to use in drawing nodes of G. If node_color is
659
+ not a string, the colors are taken in the same order as nodelist, and
660
+ each color is either a float, a 3-tuple or 4-tuple of floats.
661
+
662
+ edge_color : iterable or string (default='black')
663
+ The sequence of colors to use in drawing edges of G. If edge_color is
664
+ not a string, the colors are taken in the same order as edgelist, and
665
+ each color is either a float, a 3-tuple or 4-tuple of floats.
666
+
667
+ cmap : string or matplotlib.ColorMap or None (default=None)
668
+ A colormap to color nodes with. Presumes that node_color is a sequence
669
+ of floats.
670
+
671
+ vmin : float or None (default=None)
672
+ Minimum value to use to use when normalizing node colors through cmap.
673
+
674
+ vmax : float or None (default=None)
675
+ Maximum value to use to use when normalizing node colors through cmap.
676
+
677
+ edge_cmap : string or matplotlib.ColorMap or None (default=None)
678
+ A colormap to color edges with. Presumes that edge_color is a sequence
679
+ of floats.
680
+
681
+ edge_vmin : float or None (default=None)
682
+ Minimum value to use to use when normalizing edge colors through
683
+ edge_cmap
684
+
685
+ edge_vmax : float or None (default=None)
686
+ Maximum value to use to use when normalizing edge colors through
687
+ edge_cmap
688
+
689
+ z_offset : int (default=0)
690
+ An offset to the zorder that various elements are drawn in. Edge lines
691
+ are drawn with zorder=z_offset; horizontal node lines are drawn with
692
+ zorder=zoffset+1; vertical node lines are drawn with zorder=zoffset+2,
693
+ and edge circles are drawn with zorder=zoffset+3. This parameter can be
694
+ used to layer line plots over or under eachother.
695
+ """
696
+
697
+ from networkx .drawing .nx_pylab import apply_alpha
698
+ import numpy as np
699
+ from matplotlib .collections import LineCollection , CircleCollection
700
+
701
+ if not isinstance (node_size , Number ) or not isinstance (width , Number ):
702
+ raise NotImplementedError ("Varying node size and edge width per element in line plots is not implemented" )
703
+
704
+ if edgelist is None :
705
+ if nodelist is not None :
706
+ edgelist = G .subgraph (nodelist ).edges
707
+ else :
708
+ edgelist = G .edges
709
+ if nodelist is None :
710
+ nodelist = G
711
+
712
+ node_color = apply_alpha (node_color , 1 , nodelist , cmap = cmap , vmin = vmin , vmax = vmax )
713
+ node_lines = np .array ([layout [v ] for v in nodelist ], dtype = 'float' )
714
+ vertical = np .array ([abs (x0 - x1 ) < abs (y0 - y1 ) for (x0 , y0 ), (x1 , y1 ) in node_lines ], dtype = 'bool' )
715
+ if node_color .shape == (1 , 4 ):
716
+ vcolor = hcolor = node_color
717
+ else :
718
+ vcolor = node_color [vertical ]
719
+ hcolor = node_color [~ vertical ]
720
+ ax .add_collection (LineCollection (node_lines [~ vertical ], edgecolor = hcolor , linewidths = width , zorder = 1 + z_offset , capstyle = 'round' ))
721
+ ax .add_collection (LineCollection (node_lines [vertical ], edgecolor = vcolor , linewidths = width , zorder = 2 + z_offset , capstyle = 'round' ))
722
+
723
+ if edge_color is not None and len (edgelist ):
724
+ vertical = dict (zip (nodelist , map (int , vertical )))
725
+ as_line = np .full (len (edgelist ), False , dtype = 'bool' )
726
+ edge_data = np .empty ((len (edgelist ), 2 , 2 ), dtype = 'float' )
727
+ for i , (u , v ) in enumerate (edgelist ):
728
+ u0 , u1 = layout [u ]
729
+ v0 , v1 = layout [v ]
730
+ orientation = vertical [u ]
731
+ if orientation == vertical [v ]:
732
+ as_line [i ] = True
733
+ if u0 [orientation ] > v1 [orientation ]:
734
+ # external; v1 < u0
735
+ edge_data [i ] = v1 , u0
736
+ elif v0 [orientation ] > u1 [orientation ]:
737
+ # external; u1 < v0
738
+ edge_data [i ] = u1 , v0
739
+ elif orientation :
740
+ # odd, vertical
741
+ ymean = (u0 [1 ] + u1 [1 ] + v0 [1 ] + v1 [1 ])/ 4
742
+ edge_data [i ] = (u0 [0 ], ymean ), (v0 [0 ], ymean )
743
+ else :
744
+ # odd, horizontal
745
+ xmean = (u0 [0 ] + u1 [0 ] + v0 [0 ] + v1 [0 ])/ 4
746
+ edge_data [i ] = (xmean , u0 [1 ]), (xmean , v0 [1 ])
747
+ elif orientation :
748
+ # internal, u is vertical and v is horizontal
749
+ edge_data [i , 0 ] = u0 [0 ], v0 [1 ]
750
+ else :
751
+ # internal, v is vertical and u is horizontal
752
+ edge_data [i , 0 ] = v0 [0 ], u0 [1 ]
753
+
754
+ edge_color = apply_alpha (edge_color , 1 , edgelist , cmap = edge_cmap , vmin = edge_vmin , vmax = edge_vmax )
755
+ if edge_color .shape == (1 , 4 ):
756
+ edge_line_color = edge_spot_color = edge_color
757
+ else :
758
+ edge_line_color = edge_color [as_line ]
759
+ edge_spot_color = edge_color [~ as_line ]
760
+ ax .add_collection (LineCollection (edge_data [as_line ], edgecolor = edge_line_color , linewidths = width / 2 , zorder = z_offset ))
761
+ ax .scatter (* edge_data [~ as_line , 0 ].T , c = edge_spot_color , zorder = 3 + z_offset , s = node_size )
762
+ ax .autoscale_view ()
0 commit comments