Qiskit / qiskit

Qiskit is an open-source SDK for working with quantum computers at the level of extended quantum circuits, operators, and primitives.
https://www.ibm.com/quantum/qiskit
Apache License 2.0
5.04k stars 2.32k forks source link

Update plot_gate_map (and related visualization functions) to leverage rustworkx.visualization.graphviz_draw for unknown coupling maps #9031

Closed mtreinish closed 11 months ago

mtreinish commented 1 year ago

What should we add?

Right now the plot_gate_map() visualization function (and similar functions like plot_circuit_layout() use rustworkx's spring_layout() function when there a backend is passed in and there isn't a hardcoded layout available and passes that to a mpl visualization. For moderate numbers of qubits this works reasonably well, but for large numbers of qubits the layout doesn't scale positions appropriately and the output is typically cluttered and not a useful visualization. Graphviz is particularly well suited for doing this type of graph visualization (as it is a specialized tool for doing graph visualization). So instead of trying to rebuild what we get from graphviz we should just call out to graphviz if it's available and leverage the rustwork function for graph visualization using graphivz to generate the visualization. The only potential issue is potentially plot_error_map which has histograms and color bars built using matplotlib and integrating the two different visualization tools might prove difficult.

Here is some example code I used to leverage graphviz_draw() to build a view like plot_circuit_layout():

from rustworkx.visualization import graphviz_draw

graph = target.build_coupling_map().graph
for node in graph.node_indices():
    graph[node] = node

for edge, triple in graph.edge_index_map().items():
    graph.update_edge_by_index(edge, (triple[0], triple[1]))

physical_bits = layout.get_physical_bits()
qubit_indices = {bit: index for index, bit in enumerate(linear_circuit.qubits)}

def color_node(node):
    if node in physical_bits:
        out_dict = {
            "label": str(qubit_indices[physical_bits[node]]),
            "color": "red",
            "fillcolor": "red",
            "style": "filled",
            "shape": "circle",
        }
    else:
        out_dict = {
            "label": "",
            "color": "blue",
            "fillcolor": "blue",
            "style": "filled",
            "shape": "circle",
        }
    return out_dict

def color_edge(edge):
    if all(x in physical_bits for x in edge):
        out_dict = {
            "color": "red",
            "fillcolor": "red",
        }
    else:
        out_dict = {
            "color": "blue",
            "fillcolor": "blue",
        }
    return out_dict

graphviz_draw(graph, method="neato", node_attr_fn=color_node, edge_attr_fn=color_edge)

which in this case output something like plot_circuit_layout_graphviz

1ucian0 commented 1 year ago

For reference, the current plot_gate_map and plot_error_map problems with larger backends:

from qiskit_ibm_provider import IBMProvider
from qiskit.visualization import plot_gate_map

backend = IBMProvider().get_backend('ibm_washington')
print(backend.num_qubits)  # 127
plot_gate_map(backend)

image

from qiskit.visualization import plot_error_map
plot_error_map(backend)

image

husayngokal commented 1 year ago

Hey! Is this still available for UnitaryHack @1ucian0? I'd love to take a shot 💪

maxwell04-wq commented 1 year ago

@husayngokal can collaborate with you, if you'd like?

husayngokal commented 1 year ago

Sure! PM me on LinkedIn and we can work on this together: https://www.linkedin.com/in/husayn-gokal/

maxwell04-wq commented 1 year ago

@1ucian0 @mtreinish I have a few questions:

maxwell04-wq commented 1 year ago

@mtreinish can you please assign me to this issue?

mtreinish commented 1 year ago

@1ucian0 @mtreinish I have a few questions:

* Should we remove matplotlib from the functions entirely or provide the user with the choice b/w graphviz and matplotlib?

I think for the visualization just always using graphviz is probably easiest. I don't think we'll be able to fully remove matplotlib from the function though mostly because it will probably be needed to build a color map and also the graphs for plot_error_map(). So considering that maybe giving the option is nice just in case people don't have graphviz installed locally. But it's not really a requirement as we can just do everything in graphviz (except for what I noted before).

* A comment in the source code asks to replace the `spring_layout()` function with `planar_layout()`. The graph is getting rendered with `planar_layout()`. However, it's still not available in the documentation and the output graphs have overlapping edges (as per my understanding, the `planar_layout()` is meant to avoid just this). Should I stick with `spring_layout()`?

That comment is out of date, at the time I wrote it my thinking was that planar_layout() (once implemented in rustworkx) would be a better default especially at large graph sizes. But in reality it's not suitable for this application (you can see the in progress PR for this here: https://github.com/Qiskit/rustworkx/pull/645). Using graphviz_draw() side steps the issues associated with using rustworkx's graph layout function.

* Does this issue require us to modify only the functions in the `qiskit/visualization/gate_map.py` file:

  * plot_gate_map
  * plot_coupling_map
  * plot_circuit_layout
  * plot_error_map ?

Yeah, the only place this comes into play is the visualization functions in gate_map.py. This issue is specifically for updating the default behavior to leverage graphviz (via rustworkx's graphviz_draw()) to do the graph layout. Algorithmic graph layout is a complex problem and graphviz being a dedicated tool for it is the best solution.

* Is there a way to add custom colors to rusworkx graphs? In the existing code, the default node and edge color is '#648fff'; I am unable to use it in `rx` graphs.

Using rustworkx's graphviz draw the way you set custom colors is via node or edge attributes via the callback functions. In my example code in the OP these are the color_node() and color_edge() functions. In the return dictionary from those you can set the dictionary key "color" to any valid graphviz color string, which is either a name from: https://graphviz.org/doc/info/colors.html or you can use a custom color code using: https://graphviz.org/docs/attr-types/color/

For all the values in the output from color_node() and color_edge() you can refer to the graphviz documentation for valid fields. Those output dictionaries get passed directly to the intermediate .dot files that the function passes to graphviz. So that's how you can control the visualization at the per node and edge level.

  * This is especially important for error maps. If nothing is available, I am thinking of using webcolors to get the names of the colors in the colormap.

The way I've typically done quantitative color maps with graphviz_draw() is using matplotlib to build a separate color map and convert that to a color code string that graphviz understands. There is an example I put in the documentation for how to do this here: https://qiskit.org/ecosystem/rustworkx/tutorial/betweenness_centrality.html#visualize-the-betweenness-centrality