Source code for pennylane.drawer.tape_text
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
# 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.
"""
This module contains logic for the text based circuit drawer through the ``tape_text`` function.
"""
import pennylane as qml
from pennylane.measurements import Expectation, Probability, Sample, Variance, State
from .drawable_layers import drawable_layers
from .utils import convert_wire_order, unwrap_controls
def _add_grouping_symbols(op, layer_str, wire_map):
"""Adds symbols indicating the extent of a given object."""
if len(op.wires) > 1:
mapped_wires = [wire_map[w] for w in op.wires]
min_w, max_w = min(mapped_wires), max(mapped_wires)
layer_str[min_w] = "╭"
layer_str[max_w] = "╰"
for w in range(min_w + 1, max_w):
layer_str[w] = "├" if w in mapped_wires else "│"
return layer_str
def _add_op(op, layer_str, wire_map, decimals, cache):
"""Updates ``layer_str`` with ``op`` operation."""
layer_str = _add_grouping_symbols(op, layer_str, wire_map)
control_wires, control_values = unwrap_controls(op)
if control_values:
for w, val in zip(control_wires, control_values):
layer_str[wire_map[w]] += "●" if val else "○"
else:
for w in control_wires:
layer_str[wire_map[w]] += "●"
label = op.label(decimals=decimals, cache=cache).replace("\n", "")
if len(op.wires) == 0: # operation (e.g. barrier, snapshot) across all wires
for i, s in enumerate(layer_str):
layer_str[i] = s + label
else:
for w in op.wires:
if w not in control_wires:
layer_str[wire_map[w]] += label
return layer_str
measurement_label_map = {
Expectation: lambda label: f"<{label}>",
Probability: lambda label: f"Probs[{label}]" if label else "Probs",
Sample: lambda label: f"Sample[{label}]" if label else "Sample",
Variance: lambda label: f"Var[{label}]",
State: lambda label: "State",
}
def _add_measurement(m, layer_str, wire_map, decimals, cache):
"""Updates ``layer_str`` with the ``m`` measurement."""
layer_str = _add_grouping_symbols(m, layer_str, wire_map)
if m.obs is None:
obs_label = None
else:
obs_label = m.obs.label(decimals=decimals, cache=cache).replace("\n", "")
meas_label = measurement_label_map[m.return_type](obs_label)
if len(m.wires) == 0: # state or probability across all wires
for i, s in enumerate(layer_str):
layer_str[i] = s + meas_label
for w in m.wires:
layer_str[wire_map[w]] += meas_label
return layer_str
# pylint: disable=too-many-arguments
[docs]def tape_text(
tape,
wire_order=None,
show_all_wires=False,
decimals=None,
max_length=100,
show_matrices=True,
cache=None,
):
"""Text based diagram for a Quantum Tape.
Args:
tape (QuantumTape): the operations and measurements to draw
Keyword Args:
wire_order (Sequence[Any]): the order (from top to bottom) to print the wires of the circuit
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.
Default ``None`` will omit parameters from operation labels.
max_length (Int) : Maximum length of a individual line. After this length, the diagram will
begin anew beneath the previous lines.
show_matrices=True (bool): show matrix valued parameters below all circuit diagrams
cache (dict): Used to store information between recursive calls. Necessary keys are ``'tape_offset'``
and ``'matrices'``.
Returns:
str : String based graphic of the circuit.
**Example:**
.. code-block:: python
ops = [
qml.QFT(wires=(0, 1, 2)),
qml.RX(1.234, wires=0),
qml.RY(1.234, wires=1),
qml.RZ(1.234, wires=2),
qml.Toffoli(wires=(0, 1, "aux"))
]
measurements = [
qml.expval(qml.PauliZ("aux")),
qml.var(qml.PauliZ(0) @ qml.PauliZ(1)),
qml.probs(wires=(0, 1, 2, "aux"))
]
tape = qml.tape.QuantumTape(ops, measurements)
>>> print(qml.drawer.tape_text(tape))
0: ─╭QFT──RX─╭●─┤ ╭Var[Z@Z] ╭Probs
1: ─├QFT──RY─├●─┤ ╰Var[Z@Z] ├Probs
2: ─╰QFT──RZ─│──┤ ├Probs
aux: ──────────╰X─┤ <Z> ╰Probs
.. details::
:title: Usage Details
By default, parameters are omitted. By specifying the ``decimals`` keyword, parameters
are displayed to the specified precision. Matrix-valued parameters are never displayed.
>>> print(qml.drawer.tape_text(tape, decimals=2))
0: ─╭QFT──RX(1.23)─╭●─┤ ╭Var[Z@Z] ╭Probs
1: ─├QFT──RY(1.23)─├●─┤ ╰Var[Z@Z] ├Probs
2: ─╰QFT──RZ(1.23)─│──┤ ├Probs
aux: ────────────────╰X─┤ <Z> ╰Probs
The ``max_length`` keyword wraps long circuits:
.. code-block:: python
rng = np.random.default_rng(seed=42)
shape = qml.StronglyEntanglingLayers.shape(n_wires=5, n_layers=5)
params = rng.random(shape)
tape2 = qml.StronglyEntanglingLayers(params, wires=range(5)).expand()
print(qml.drawer.tape_text(tape2, max_length=60))
.. code-block:: none
0: ──Rot─╭●──────────╭X──Rot─╭●───────╭X──Rot──────╭●────╭X
1: ──Rot─╰X─╭●───────│───Rot─│──╭●────│──╭X────Rot─│──╭●─│─
2: ──Rot────╰X─╭●────│───Rot─╰X─│──╭●─│──│─────Rot─│──│──╰●
3: ──Rot───────╰X─╭●─│───Rot────╰X─│──╰●─│─────Rot─╰X─│────
4: ──Rot──────────╰X─╰●──Rot───────╰X────╰●────Rot────╰X───
───Rot───────────╭●─╭X──Rot──────╭●──────────────╭X─┤
──╭X────Rot──────│──╰●─╭X────Rot─╰X───╭●─────────│──┤
──│────╭X────Rot─│─────╰●───╭X────Rot─╰X───╭●────│──┤
──╰●───│─────Rot─│──────────╰●───╭X────Rot─╰X─╭●─│──┤
───────╰●────Rot─╰X──────────────╰●────Rot────╰X─╰●─┤
The ``wire_order`` keyword specifies the order of the wires from
top to bottom:
>>> print(qml.drawer.tape_text(tape, wire_order=["aux", 2, 1, 0]))
aux: ──────────╭X─┤ <Z> ╭Probs
2: ─╭QFT──RZ─│──┤ ├Probs
1: ─├QFT──RY─├●─┤ ╭Var[Z@Z] ├Probs
0: ─╰QFT──RX─╰●─┤ ╰Var[Z@Z] ╰Probs
If the wire order contains empty wires, they are only shown if the ``show_all_wires=True``.
>>> print(qml.drawer.tape_text(tape, wire_order=["a", "b", "aux", 0, 1, 2], show_all_wires=True))
a: ─────────────┤
b: ─────────────┤
aux: ──────────╭X─┤ <Z> ╭Probs
0: ─╭QFT──RX─├●─┤ ╭Var[Z@Z] ├Probs
1: ─├QFT──RY─╰●─┤ ╰Var[Z@Z] ├Probs
2: ─╰QFT──RZ────┤ ╰Probs
Matrix valued parameters are always denoted by ``M`` followed by an integer corresponding to
unique matrices. The list of unique matrices can be printed at the end of the diagram by
selecting ``show_matrices=True`` (the default):
.. code-block:: python
ops = [
qml.QubitUnitary(np.eye(2), wires=0),
qml.QubitUnitary(np.eye(2), wires=1)
]
measurements = [qml.expval(qml.Hermitian(np.eye(4), wires=(0,1)))]
tape = qml.tape.QuantumTape(ops, measurements)
>>> print(qml.drawer.tape_text(tape))
0: ──U(M0)─┤ ╭<𝓗(M1)>
1: ──U(M0)─┤ ╰<𝓗(M1)>
M0 =
[[1. 0.]
[0. 1.]]
M1 =
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
An existing matrix cache can be passed via the ``cache`` keyword. Note that the dictionary
passed to ``cache`` will be modified during execution to contain any new matrices and the
tape offset.
>>> cache = {'matrices': [-np.eye(3)]}
>>> print(qml.drawer.tape_text(tape, cache=cache))
0: ──U(M1)─┤ ╭<𝓗(M2)>
1: ──U(M1)─┤ ╰<𝓗(M2)>
M0 =
[[-1. -0. -0.]
[-0. -1. -0.]
[-0. -0. -1.]]
M1 =
[[1. 0.]
[0. 1.]]
M2 =
[[1. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 1. 0.]
[0. 0. 0. 1.]]
>>> cache
{'matrices': [tensor([[-1., -0., -0.],
[-0., -1., -0.],
[-0., -0., -1.]], requires_grad=True), tensor([[1., 0.],
[0., 1.]], requires_grad=True), tensor([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]], requires_grad=True)], 'tape_offset': 0}
When the provided tape has nested tapes inside, this function is called recursively.
To maintain numbering of tapes to arbitrary levels of nesting, the ``cache`` keyword
uses the ``"tape_offset"`` value to determine numbering. Note that the value is updated
during the call.
.. code-block:: python
with qml.tape.QuantumTape() as tape:
with qml.tape.QuantumTape() as tape_inner:
qml.PauliX(0)
cache = {'tape_offset': 3}
print(qml.drawer.tape_text(tape, cache=cache))
print("New tape offset: ", cache['tape_offset'])
.. code-block:: none
0: ──Tape:3─┤
Tape:3
0: ──X─┤
New tape offset: 4
"""
cache = cache or {}
cache.setdefault("tape_offset", 0)
cache.setdefault("matrices", [])
tape_cache = []
wire_map = convert_wire_order(tape, wire_order=wire_order, show_all_wires=show_all_wires)
n_wires = len(wire_map)
if n_wires == 0:
return ""
totals = [f"{wire}: " for wire in wire_map]
line_length = max(len(s) for s in totals)
totals = [s.rjust(line_length, " ") for s in totals]
# Used to store lines that are hitting the maximum length
finished_lines = []
layers_list = [
drawable_layers(tape.operations, wire_map=wire_map),
drawable_layers(tape.measurements, wire_map=wire_map),
]
add_list = [_add_op, _add_measurement]
fillers = ["─", " "]
enders = [True, False] # add "─┤" after all operations
for layers, add, filler, ender in zip(layers_list, add_list, fillers, enders):
for layer in layers:
layer_str = [filler] * n_wires
for op in layer:
if isinstance(op, qml.tape.QuantumScript):
layer_str = _add_grouping_symbols(op, layer_str, wire_map)
label = f"Tape:{cache['tape_offset']+len(tape_cache)}"
for w in op.wires:
layer_str[wire_map[w]] += label
tape_cache.append(op)
else:
layer_str = add(op, layer_str, wire_map, decimals, cache)
max_label_len = max(len(s) for s in layer_str)
layer_str = [s.ljust(max_label_len, filler) for s in layer_str]
line_length += max_label_len + 1 # one for the filler character
if line_length > max_length:
# move totals into finished_lines and reset totals
finished_lines += totals
finished_lines[-1] += "\n"
totals = [filler] * n_wires
line_length = 2 + max_label_len
totals = [filler.join([t, s]) for t, s in zip(totals, layer_str)]
if ender:
totals = [s + "─┤" for s in totals]
line_length += 2
# Recursively handle nested tapes #
tape_totals = "\n".join(finished_lines + totals)
current_tape_offset = cache["tape_offset"]
cache["tape_offset"] += len(tape_cache)
for i, nested_tape in enumerate(tape_cache):
label = f"\nTape:{i+current_tape_offset}"
tape_str = tape_text(
nested_tape,
wire_order,
show_all_wires,
decimals,
max_length,
show_matrices=False,
cache=cache,
)
tape_totals = "\n".join([tape_totals, label, tape_str])
if show_matrices:
mat_str = ""
for i, mat in enumerate(cache["matrices"]):
mat_str += f"\nM{i} = \n{mat}"
return tape_totals + mat_str
return tape_totals
_modules/pennylane/drawer/tape_text
Download Python script
Download Notebook
View on GitHub