qml.drawer.draw

draw(qnode, wire_order=None, show_all_wires=False, decimals=2, max_length=100, show_matrices=True, **kwargs)[source]

Create a function that draws the given qnode or quantum function.

Parameters
  • qnode (QNode or Callable) – the input QNode or quantum function that is to be drawn.

  • wire_order (Sequence[Any]) – the order (from top to bottom) to print the wires of the circuit. If not provided, the wire order defaults to the device wires. If device wires are not available, the circuit wires are sorted if possible.

  • show_all_wires (bool) – If True, all wires, including empty wires, are printed.

  • decimals (int) – How many decimal points to include when formatting operation parameters. None will omit parameters from operation labels.

  • max_length (int) – Maximum string width (columns) when printing the circuit

  • show_matrices=False (bool) – show matrix valued parameters below all circuit diagrams

Keyword Arguments
  • level (None, str, int, slice) – An indication of what transforms to apply before drawing. Check get_transform_program() for more information on the allowed values and usage details of this argument.

  • expansion_strategy (str) –

    The strategy to use when circuit expansions or decompositions are required.

    • gradient: The QNode will attempt to decompose the internal circuit such that all circuit operations are supported by the gradient method.

    • device: The QNode will attempt to decompose the internal circuit such that all circuit operations are natively supported by the device.

Returns

A function that has the same argument signature as qnode. When called, the function will draw the QNode/qfunc.

Note

At most, one of level or expansion_strategy needs to be provided. If neither is provided, qnode.expansion_strategy will be used instead. Users are encouraged to predominantly use level, as it allows for the same values as expansion_strategy and offers more flexibility in choosing the desired transforms/expansions.

Example

@qml.qnode(qml.device('lightning.qubit', wires=2))
def circuit(a, w):
    qml.Hadamard(0)
    qml.CRX(a, wires=[0, 1])
    qml.Rot(*w, wires=[1], id="arbitrary")
    qml.CRX(-a, wires=[0, 1])
    return qml.expval(qml.Z(0) @ qml.Z(1))
>>> print(qml.draw(circuit)(a=2.3, w=[1.2, 3.2, 0.7]))
0: ──H─╭●─────────────────────────────────────────╭●─────────┤ ╭<Z@Z>
1: ────╰RX(2.30)──Rot(1.20,3.20,0.70,"arbitrary")─╰RX(-2.30)─┤ ╰<Z@Z>

By specifying the decimals keyword, parameters are displayed to the specified precision.

>>> print(qml.draw(circuit, decimals=4)(a=2.3, w=[1.2, 3.2, 0.7]))
0: ──H─╭●─────────────────────────────────────────────────╭●───────────┤ ╭<Z@Z>
1: ────╰RX(2.3000)──Rot(1.2000,3.2000,0.7000,"arbitrary")─╰RX(-2.3000)─┤ ╰<Z@Z>

Parameters can be omitted by requesting decimals=None:

>>> print(qml.draw(circuit, decimals=None)(a=2.3, w=[1.2, 3.2, 0.7]))
0: ──H─╭●────────────────────╭●──┤ ╭<Z@Z>
1: ────╰RX──Rot("arbitrary")─╰RX─┤ ╰<Z@Z>

If the parameters are not acted upon by classical processing like -a, then qml.draw can handle string-valued parameters as well:

>>> @qml.qnode(qml.device('lightning.qubit', wires=1))
... def circuit2(x):
...     qml.RX(x, wires=0)
...     return qml.expval(qml.Z(0))
>>> print(qml.draw(circuit2)("x"))
0: ──RX(x)─┤  <Z>

When requested with show_matrices=True (the default), matrix valued parameters are printed below the circuit. For show_matrices=False, they are not printed:

>>> @qml.qnode(qml.device('default.qubit', wires=2))
... def circuit3():
...     qml.QubitUnitary(np.eye(2), wires=0)
...     qml.QubitUnitary(-np.eye(4), wires=(0,1))
...     return qml.expval(qml.Hermitian(np.eye(2), wires=1))
>>> print(qml.draw(circuit3)())
0: ──U(M0)─╭U(M1)─┤
1: ────────╰U(M1)─┤  <𝓗(M0)>
M0 =
[[1. 0.]
[0. 1.]]
M1 =
[[-1. -0. -0. -0.]
[-0. -1. -0. -0.]
[-0. -0. -1. -0.]
[-0. -0. -0. -1.]]
>>> print(qml.draw(circuit3, show_matrices=False)())
0: ──U(M0)─╭U(M1)─┤
1: ────────╰U(M1)─┤  <𝓗(M0)>

The max_length keyword warps long circuits:

rng = np.random.default_rng(seed=42)
shape = qml.StronglyEntanglingLayers.shape(n_wires=3, n_layers=3)
params = rng.random(shape)

@qml.qnode(qml.device('lightning.qubit', wires=3))
def longer_circuit(params):
    qml.StronglyEntanglingLayers(params, wires=range(3))
    return [qml.expval(qml.Z(i)) for i in range(3)]
>>> print(qml.draw(longer_circuit, max_length=60, expansion_strategy="device")(params))
0: ──Rot(0.77,0.44,0.86)─╭●────╭X──Rot(0.45,0.37,0.93)─╭●─╭X
1: ──Rot(0.70,0.09,0.98)─╰X─╭●─│───Rot(0.64,0.82,0.44)─│──╰●
2: ──Rot(0.76,0.79,0.13)────╰X─╰●──Rot(0.23,0.55,0.06)─╰X───
───Rot(0.83,0.63,0.76)──────────────────────╭●────╭X─┤  <Z>
──╭X────────────────────Rot(0.35,0.97,0.89)─╰X─╭●─│──┤  <Z>
──╰●────────────────────Rot(0.78,0.19,0.47)────╰X─╰●─┤  <Z>

The wire_order keyword specifies the order of the wires from top to bottom:

>>> print(qml.draw(circuit, wire_order=[1,0])(a=2.3, w=[1.2, 3.2, 0.7]))
1: ────╭RX(2.30)──Rot(1.20,3.20,0.70,"arbitrary")─╭RX(-2.30)─┤ ╭<Z@Z>
0: ──H─╰●─────────────────────────────────────────╰●─────────┤ ╰<Z@Z>

If the device or wire_order has wires not used by operations, those wires are omitted unless requested with show_all_wires=True

>>> empty_qfunc = lambda : qml.expval(qml.Z(0))
>>> empty_circuit = qml.QNode(empty_qfunc, qml.device('lightning.qubit', wires=3))
>>> print(qml.draw(empty_circuit, show_all_wires=True)())
0: ───┤  <Z>
1: ───┤
2: ───┤

Drawing also works on batch transformed circuits:

from functools import partial
from pennylane import numpy as pnp

@partial(qml.gradients.param_shift, shifts=[(0.1,)])
@qml.qnode(qml.device('default.qubit', wires=1))
def transformed_circuit(x):
    qml.RX(x, wires=0)
    return qml.expval(qml.Z(0))
>>> print(qml.draw(transformed_circuit)(pnp.array(1.0, requires_grad=True)))
0: ──RX(1.10)─┤  <Z>
0: ──RX(0.90)─┤  <Z>

The function also accepts quantum functions rather than QNodes. This can be especially helpful if you want to visualize only a part of a circuit that may not be convertible into a QNode, such as a sub-function that does not return any measurements.

>>> def qfunc(x):
...     qml.RX(x, wires=[0])
...     qml.CNOT(wires=[0, 1])
>>> print(qml.draw(qfunc)(1.1))
0: ──RX(1.10)─╭●─┤
1: ───────────╰X─┤

Levels:

The level keyword argument allows one to select a subset of the transforms to apply on the QNode before carrying out any drawing. Take, for example, this circuit:

@qml.transforms.merge_rotations
@qml.transforms.cancel_inverses
@qml.qnode(qml.device("default.qubit"), diff_method="parameter-shift")
def circ(weights, order):
    qml.RandomLayers(weights, wires=(0, 1))
    qml.Permute(order, wires=(0, 1, 2))
    qml.PauliX(0)
    qml.PauliX(0)
    qml.RX(0.1, wires=0)
    qml.RX(-0.1, wires=0)
    return qml.expval(qml.PauliX(0))

order = [2, 1, 0]
weights = qml.numpy.array([[1.0, 20]])

One can print the circuit without any transforms applied by passing level="top" or level=0:

>>> print(qml.draw(circ, level="top")(weights, order))
0: ─╭RandomLayers(M0)─╭Permute──X──X──RX(0.10)──RX(-0.10)─┤  <X>
1: ─╰RandomLayers(M0)─├Permute────────────────────────────┤
2: ───────────────────╰Permute────────────────────────────┤
M0 =
[[ 1. 20.]]

Or print the circuit after applying the transforms manually applied on the QNode (merge_rotations and cancel_inverses):

>>> print(qml.draw(circ, level="user", show_matrices=False)(weights, order))
0: ─╭RandomLayers(M0)─╭Permute─┤  <X>
1: ─╰RandomLayers(M0)─├Permute─┤
2: ───────────────────╰Permute─┤

To apply all of the transforms, including those carried out by the differentiation method and the device, use level=None:

>>> print(qml.draw(circ, level=None, show_matrices=False)(weights, order))
0: ──RY(1.00)──╭SWAP─┤  <X>
1: ──RX(20.00)─│─────┤
2: ────────────╰SWAP─┤

Slices can also be passed to the level argument. So one can, for example, request that only the merge_rotations transform is applied:

>>> print(qml.draw(circ, level=slice(1, 2), show_matrices=False)(weights, order))
0: ─╭RandomLayers(M0)─╭Permute──X──X─┤  <X>
1: ─╰RandomLayers(M0)─├Permute───────┤
2: ───────────────────╰Permute───────┤