Skip to content

Commit

Permalink
Merge branch 'master' into rename_measure_py
Browse files Browse the repository at this point in the history
  • Loading branch information
antalszava authored Feb 28, 2022
2 parents 35485d4 + a48e34f commit 19bb467
Show file tree
Hide file tree
Showing 19 changed files with 549 additions and 139 deletions.
11 changes: 11 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,18 @@
* The text based drawer accessed via `qml.draw` has been overhauled. The new drawer has
a `decimals` keyword for controlling parameter rounding, a different algorithm for determining positions,
deprecation of the `charset` keyword, and minor cosmetic changes.

* The text based drawer accessed via `qml.draw` has been overhauled.
[(#2128)](https://github.com/PennyLaneAI/pennylane/pull/2128)
[(#2198)](https://github.com/PennyLaneAI/pennylane/pull/2198)

The new drawer has:

* a `decimals` keyword for controlling parameter rounding
* a `show_matrices` keyword for controlling display of matrices
* a different algorithm for determining positions
* deprecation of the `charset` keyword
* additional minor cosmetic changes

```
@qml.qnode(qml.device('lightning.qubit', wires=2))
Expand Down
3 changes: 0 additions & 3 deletions doc/xanadu_theme/header.html
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,6 @@
<li class="nav-item">
<a class="nav-link" href="https://pennylane.ai/blog">Blog</a>
</li>
<li class="nav-item">
<a class="nav-link" href="https://qhack.ai"><img src="https://pennylane.ai/img/qhack_plain_black.png"></a>
</li>
</ul>
<!-- Links -->

Expand Down
99 changes: 88 additions & 11 deletions pennylane/drawer/tape_text.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ def _add_grouping_symbols(op, layer_str, wire_map):
return layer_str


def _add_op(op, layer_str, wire_map, decimals):
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 = op.control_wires
for w in control_wires:
layer_str[wire_map[w]] += "C"

label = op.label(decimals=decimals).replace("\n", "")
label = op.label(decimals=decimals, cache=cache).replace("\n", "")
for w in op.wires:
if w not in control_wires:
layer_str[wire_map[w]] += label
Expand All @@ -61,11 +61,14 @@ def _add_op(op, layer_str, wire_map, decimals):
}


def _add_measurement(m, layer_str, wire_map, decimals):
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)

obs_label = None if m.obs is None else m.obs.label(decimals=decimals).replace("\n", "")
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
Expand All @@ -79,7 +82,13 @@ def _add_measurement(m, layer_str, wire_map, decimals):

# pylint: disable=too-many-arguments
def tape_text(
tape, wire_order=None, show_all_wires=False, decimals=None, max_length=100, cache=None
tape,
wire_order=None,
show_all_wires=False,
decimals=None,
max_length=100,
show_matrices=False,
cache=None,
):
"""Text based diagram for a Quantum Tape.
Expand All @@ -93,8 +102,9 @@ def tape_text(
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.
cache (dict): Used to store information between recursive calls. Currently only used for
numbering nested tapes.
show_matrices=False (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.
Expand Down Expand Up @@ -177,6 +187,58 @@ def tape_text(
1: ─├QFT──RY─╰C─┤ ╰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``:
.. code-block:: python
with qml.tape.QuantumTape() as tape:
qml.QubitUnitary(np.eye(2), wires=0)
qml.QubitUnitary(np.eye(2), wires=1)
qml.expval(qml.Hermitian(np.eye(4), wires=(0,1)))
>>> print(tape_text(tape, show_matrices=True))
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(tape_text(tape, show_matrices=True, 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
Expand All @@ -202,8 +264,9 @@ def tape_text(
New tape offset: 4
"""
if cache is None:
cache = {"tape_offset": 0}
cache = cache or {}
cache.setdefault("tape_offset", 0)
cache.setdefault("matrices", [])
tape_cache = []

wire_map = convert_wire_order(
Expand Down Expand Up @@ -241,7 +304,7 @@ def tape_text(
layer_str[wire_map[w]] += label
tape_cache.append(op)
else:
layer_str = add(op, layer_str, wire_map, decimals)
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]
Expand All @@ -264,7 +327,21 @@ def tape_text(
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, cache)
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
61 changes: 52 additions & 9 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -799,13 +799,15 @@ def id(self):
def name(self, value):
self._name = value

def label(self, decimals=None, base_label=None):
def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
Args:
decimals=None (int): If ``None``, no parameters are included. Else,
specifies how to round the parameters.
base_label=None (str): overwrite the non-parameter component of the label
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
Expand All @@ -825,16 +827,58 @@ def label(self, decimals=None, base_label=None):
>>> op.label()
"RX⁻¹"
If the operation has a matrix-valued parameter and a cache dictionary is provided,
unique matrices will be cached in the ``'matrices'`` key list. The label will contain
the index of the matrix in the ``'matrices'`` list.
>>> op2 = qml.QubitUnitary(np.eye(2), wires=0)
>>> cache = {'matrices': []}
>>> op2.label(cache=cache)
'U(M0)'
>>> cache['matrices']
[tensor([[1., 0.],
[0., 1.]], requires_grad=True)]
>>> op3 = qml.QubitUnitary(np.eye(4), wires=(0,1))
>>> op3.label(cache=cache)
'U(M1)'
>>> cache['matrices']
[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)]
"""
op_label = base_label or self.__class__.__name__

if decimals is None or self.num_params == 0:
if self.num_params == 0:
return op_label

params = self.parameters

# matrix parameters not rendered
if len(qml.math.shape(params[0])) != 0:
# assume that if the first parameter is matrix-valued, there is only a single parameter
# this holds true for all current operations and templates
if (
cache is None
or not isinstance(cache.get("matrices", None), list)
or len(params) != 1
):
return op_label

for i, mat in enumerate(cache["matrices"]):
if qml.math.shape(params[0]) == qml.math.shape(mat) and qml.math.allclose(
params[0], mat
):
return f"{op_label}(M{i})"

# matrix not in cache
mat_num = len(cache["matrices"])
cache["matrices"].append(params[0])
return f"{op_label}(M{mat_num})"

if decimals is None:
return op_label

def _format(x):
Expand All @@ -844,9 +888,6 @@ def _format(x):
# If the parameter can't be displayed as a float
return format(x)

if self.num_params == 1:
return op_label + f"\n({_format(params[0])})"

param_string = ",\n".join(_format(p) for p in params)
return op_label + f"\n({param_string})"

Expand Down Expand Up @@ -1342,11 +1383,11 @@ def name(self):
"""Name of the operator."""
return self._name + ".inv" if self.inverse else self._name

def label(self, decimals=None, base_label=None):
def label(self, decimals=None, base_label=None, cache=None):
if self.inverse:
base_label = base_label or self.__class__.__name__
base_label += "⁻¹"
return super().label(decimals=decimals, base_label=base_label)
return super().label(decimals=decimals, base_label=base_label, cache=cache)

def __init__(self, *params, wires=None, do_queue=True, id=None):

Expand Down Expand Up @@ -1605,14 +1646,16 @@ def __init__(self, *args): # pylint: disable=super-init-not-called
self._args = args
self.queue(init=True)

def label(self, decimals=None, base_label=None):
def label(self, decimals=None, base_label=None, cache=None):
r"""How the operator is represented in diagrams and drawings.
Args:
decimals=None (Int): If ``None``, no parameters are included. Else,
how to round the parameters.
base_label=None (Iterable[str]): overwrite the non-parameter component of the label.
Must be same length as ``obs`` attribute.
cache=None (dict): dictionary that caries information between label calls
in the same drawing
Returns:
str: label to use in drawings
Expand Down
Loading

0 comments on commit 19bb467

Please sign in to comment.