catalyst.draw_graph

draw_graph(qnode: QJIT, *, level: int | None = None) Callable[source]

Visualize a single QJIT compiled QNode, showing wire flow through quantum operations, program structure, and pass-by-pass impacts on compiled programs.

Note

The draw_graph function visualizes a QJIT-compiled QNode in a similar manner as view-op-graph does in MLIR, which leverages Graphviz to show data-flow in the compiled IR.

As such, use of draw_graph requires installation of Graphviz, pydot, and matplotlib software packages. Please consult the links provided for installation instructions.

Additionally, it is recommended to use draw_graph with PennyLane’s program capture enabled (see qml.capture.enable).

Warning

This function only visualizes quantum operations contained in workflows involving a single qjit-compiled QNode. Workflows involving multiple QNodes or operations outside QNodes cannot yet be visualized.

Only transformations found within the Catalyst compiler can be visualized. Any PennyLane tape transform will have already been applied before lowering to MLIR and will appear as the base state (level=0) in this visualization.

Parameters:
  • qnode (QJIT) – The input qjit-compiled QNode that is to be visualized. The QNode is assumed to be compiled with qjit.

  • level (int | None) – The level of transformation to visualize. If None, the final level is visualized.

Returns:

A function that has the same argument signature as the compiled QNode. When called, the function will return the graph as a tuple of (matplotlib.figure.Figure, matplotlib.axes._axes.Axes) pairs.

Return type:

Callable

Raises:
  • VisualizationError – If the circuit contains operations that cannot be converted to a graphical representation.

  • TypeError – If the level argument is not of type integer or None. If the input QNode is not qjit-compiled.

  • ValueError – If the level argument is a negative integer.

Warns:
  • UserWarning – If the level argument provided is larger than the number of passes present in the compilation pipeline.

  • Lastly, ``catalyst.draw_graph`` is currently not compatible with dynamic wire allocation.

  • This includes :func:`pennylane.allocation.allocate` and dynamic wire allocation that may

  • occur in MLIR directly (via quantum.alloc_qb instructions)

Example

Using draw_graph requires a qjit’d QNode and a level argument, which denotes the cumulative set of applied compilation transforms (in the order they appear) to be applied and visualized.

import pennylane as qml
import catalyst

qml.capture.enable()

@qml.qjit
@qml.transforms.merge_rotations
@qml.transforms.cancel_inverses
@qml.qnode(qml.device("null.qubit", wires=3))
def circuit():
    qml.H(0)
    qml.T(1)
    qml.H(0)
    qml.RX(0.1, wires=0)
    qml.RX(0.2, wires=0)
    return qml.expval(qml.X(0))

With level=0, the graphical visualization will display the program as if no transforms are applied:

>>> fig, ax = catalyst.draw_graph(circuit, level=0)()
>>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
Graphical representation of circuit with level=0

Though you can print the output of catalyst.draw_graph, it is recommended to use the savefig method of matplotlib.figure.Figure for better control over image resolution (DPI). Please consult the matplotlib documentation for usage details of savefig.

With level=2, both merge_rotations() and cancel_inverses() will be applied, resulting in the two Hadamards cancelling and the two rotations merging:

>>> fig, ax = catalyst.draw_graph(circuit, level=2)()
>>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
Graphical representation of circuit with level=2

Visualizing Control Flow

The draw_graph function can be used to visualize control flow, resulting in a scalable representation that preserves program structure:

@qml.qjit(autograph=True)
@qml.qnode(qml.device("null.qubit", wires=3))
def circuit():
    qml.H(0)
    for i in range(3):
        if i == 1:
            qml.X(0)
        elif i == 2:
            qml.Y(0)
        else:
            qml.Z(0)
    return qml.probs()
>>> fig, ax = catalyst.draw_graph(circuit)()
>>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
Graphical representation of circuit with control flow

As one can see, the program structure is preserved in the figure.

Visualizing Dynamic Circuits

Circuits can depend on parameters that are not known at compile time, which result in conventional visualization tools failing. Consider the following circuit.

@qml.qjit
@qml.qnode(qml.device("null.qubit", wires=3))
def circuit(x, y):
    qml.X(0)
    qml.Y(1)
    qml.Z(2)
    qml.H(x) # 'x' is a dynamic wire index
    qml.S(0)
    qml.T(2)
    qml.H(x)
    return qml.expval(qml.Z(y))

The two qml.H gates act on wires that are dynamic. In order to preserve qubit data flow, each dynamic operator acts as a “choke point” to all currently active wires. To visualize this clearly, we use dashed lines to represent a dynamic dependency and solid lines for static/known values:

>>> x, y = 1, 0
>>> fig, ax = catalyst.draw_graph(circuit)(x, y)
>>> fig.savefig('path_to_file.png', dpi=300, bbox_inches="tight")
Graphical representation of circuit with control flow