Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[TAPE] Adds circuit drawing functionality to the quantum tape #824

Merged
merged 58 commits into from
Oct 13, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
e714e54
compatibility changes
josh146 Sep 24, 2020
27c2354
get tests passing
josh146 Sep 24, 2020
b50a412
Merge branch 'master' into tape-pr-9
josh146 Sep 24, 2020
2ae8ea6
more
josh146 Sep 24, 2020
8722350
Merge branch 'tape-pr-9' of github.com:PennyLaneAI/pennylane into tap…
josh146 Sep 24, 2020
b080cbf
more
josh146 Sep 24, 2020
2dd98e5
final
josh146 Sep 24, 2020
9b71cd6
final
josh146 Sep 24, 2020
0f81545
Update pennylane/wires.py
josh146 Sep 25, 2020
d007001
Merge branch 'master' into tape-pr-9
josh146 Sep 25, 2020
236fea7
fix hotfix
josh146 Sep 25, 2020
e1e5e40
Merge branch 'tape-pr-9' of github.com:PennyLaneAI/pennylane into tap…
josh146 Sep 25, 2020
1020dd3
restructure
josh146 Sep 25, 2020
b66e3e5
restructure
josh146 Sep 25, 2020
ebe9c27
more
josh146 Sep 25, 2020
a03ca1e
black
josh146 Sep 25, 2020
9623b09
test
josh146 Sep 25, 2020
660ffc9
test
josh146 Sep 25, 2020
4ab202d
add docs
josh146 Sep 25, 2020
a02331e
fixes
josh146 Sep 25, 2020
0179d9f
oops
josh146 Sep 25, 2020
fa3d988
more docs
josh146 Sep 25, 2020
ffcc32f
more docs
josh146 Sep 25, 2020
1cd9fdd
more docs
josh146 Sep 25, 2020
b0bf502
more docs
josh146 Sep 25, 2020
067e4bb
revert
josh146 Sep 25, 2020
74c98db
Merge branch 'tape-pr-9' into tape-pr-10
josh146 Sep 25, 2020
813148a
Added tests
josh146 Sep 25, 2020
7f3d0f1
more
josh146 Sep 25, 2020
ae92e4a
[TAPE] Adds circuit drawing functionality to the quantum tape
josh146 Sep 25, 2020
fb17b10
more
josh146 Sep 25, 2020
6b87f5c
merge master
josh146 Sep 25, 2020
b1eb83d
Merge branch 'tape-pr-10' into tape-draw
josh146 Sep 25, 2020
c6c77bd
black
josh146 Sep 25, 2020
ffef111
Update tests/tape/tapes/test_cv_param_shift.py
josh146 Sep 25, 2020
45ede30
Apply suggestions from code review
josh146 Sep 25, 2020
bb19718
Merge branch 'master' into tape-pr-10
trbromley Sep 25, 2020
fc8353b
Move new test
trbromley Sep 25, 2020
a35daaa
Fix imports
trbromley Sep 25, 2020
a249a8c
Merge branch 'tape-pr-10' into tape-draw
trbromley Sep 25, 2020
a7c0d8b
Merge branch 'master' into tape-pr-10
trbromley Sep 25, 2020
9e00ac1
Apply suggestions from code review
josh146 Sep 26, 2020
56fda5b
Merge branch 'master' into tape-pr-10
josh146 Sep 26, 2020
ae0d61e
Update doc/code/qml_tape.rst
josh146 Sep 26, 2020
bb7df64
Update tests/tape/tapes/test_qubit_param_shift.py
josh146 Sep 26, 2020
038ce3f
suggested changes
josh146 Sep 26, 2020
5aa79fd
Merge branch 'master' into tape-pr-10
josh146 Sep 26, 2020
4e2280f
linting
josh146 Sep 26, 2020
f111ff0
Merge branch 'tape-pr-10' of github.com:PennyLaneAI/pennylane into ta…
josh146 Sep 26, 2020
1ae072f
linting
josh146 Sep 26, 2020
ca91993
linting
josh146 Sep 26, 2020
2e4cca9
linting
josh146 Sep 26, 2020
cf0cf55
Merge branch 'tape-pr-10' into tape-draw
josh146 Sep 26, 2020
2a7d18c
Merge branch 'master' into tape-draw
josh146 Oct 5, 2020
2fa257e
Merge branch 'master' into tape-draw
josh146 Oct 13, 2020
def4fa6
Apply suggestions from code review
josh146 Oct 13, 2020
4fec39e
suggested changes
josh146 Oct 13, 2020
e72f9c3
linting
josh146 Oct 13, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 0 additions & 3 deletions doc/code/qml_tape.rst
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,6 @@ Tape mode provides several advantanges over the standard PennyLane QNode.
In tape-mode, the QNode does not yet have feature-parity with the standard PennyLane
QNode. Features currently not available in tape mode include:

* Circuit drawing and visualization

* Metric tensor computation

* The ability to automatically extract the layer structure of variational circuits
Expand All @@ -156,7 +154,6 @@ the internal structure of the QNode. When tape mode is enabled, the QNode is no
responsible for recording quantum operations, executing devices, or computing gradients---these
tasks have been delegated to an internal object that is created by the QNode, the **quantum tape**.


In addition to being created internally by QNodes in tape mode, quantum tapes can also be created,
nested, expanded (via :meth:`~.QuantumTape.expand`), and executed manually. Tape subclasses also provide
additional gradient methods:
Expand Down
2 changes: 1 addition & 1 deletion pennylane/beta/devices/default_tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
if int(v[0]) != 0 and int(v[1]) < 3:
raise ImportError("default.tensor device requires TensorNetwork>=0.3")
except ImportError as e:
raise ImportError("default.tensor device requires TensorNetwork>=0.3")
raise ImportError("default.tensor device requires TensorNetwork>=0.3") from e


# tolerance for numerical errors
Expand Down
7 changes: 5 additions & 2 deletions pennylane/circuit_drawer/representation_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ def single_parameter_representation(self, par):
if isinstance(par, str):
return par

return str(round(par, 3))
return f"{1.0 * par:.3g}"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is required since tape parameters can now be TF tensors or Torch tensors in addition to floats/NumPy arrays. Unfortunately round() doesn't work with Torch tensors, so we instead use string formatting.

The multiplication by 1.0 is to convert a TF Variable into a TF tensor without importing tensorflow. This is because TensorFlow Variables do not support string formatting for some reason 😢


@staticmethod
def _format_matrix_operation(operation, symbol, cache):
Expand Down Expand Up @@ -449,7 +449,10 @@ def element_representation(self, element, wire):
return ""
if isinstance(element, str):
return element
if isinstance(element, qml.operation.Observable) and element.return_type is not None:
if (
isinstance(element, (qml.operation.Observable, qml.tape.MeasurementProcess))
and element.return_type is not None
):
return self.output_representation(element, wire)

return self.operator_representation(element, wire)
7 changes: 5 additions & 2 deletions pennylane/circuit_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -548,7 +548,8 @@ def greedy_layers(self):
operations[wire] = list(
filter(
lambda op: not (
isinstance(op, qml.operation.Observable) and op.return_type is not None
isinstance(op, (qml.operation.Observable, qml.tape.MeasurementProcess))
and op.return_type is not None
),
operations[wire],
)
Expand Down Expand Up @@ -576,7 +577,9 @@ def greedy_layers(self):
for wire in sorted(self._grid):
observables[wire] = list(
filter(
lambda op: isinstance(op, qml.operation.Observable)
lambda op: isinstance(
op, (qml.operation.Observable, qml.tape.MeasurementProcess)
)
and op.return_type is not None,
self._grid[wire],
)
Expand Down
7 changes: 7 additions & 0 deletions pennylane/tape/circuit_graph.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"""
import networkx as nx

import pennylane as qml
from pennylane import CircuitGraph


Expand All @@ -29,6 +30,12 @@ class TapeCircuitGraph(CircuitGraph):
def __init__(self, ops, obs, wires):
self._operations = ops
self._observables = obs
Comment on lines 31 to 32
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ops and obs are easily confused. 😆

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They are, but I didn't want to alter the signature while still inheriting from the old circuit graph! Something to keep in mind when we decide to drop backcompatibility


for m in self._observables:
if m.return_type is qml.operation.State:
# state measurements are applied to all device wires
m._wires = wires # pylint: disable=protected-access
Comment on lines +34 to +37
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this have something to do with allowing state measurements to be applied without declaring on which wires?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep! exactly


super().__init__(ops + obs, variable_deps={}, wires=wires)

@property
Expand Down
57 changes: 51 additions & 6 deletions pennylane/tape/qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,51 @@ def __call__(self, *args, **kwargs):

return __import__(res_type_namespace).squeeze(res)

def draw(self, charset="unicode"):
"""Draw the quantum tape as a circuit diagram.

Consider the following circuit as an example:

.. code-block:: python3

@qml.qnode(dev)
def circuit(a, w):
qml.Hadamard(0)
qml.CRX(a, wires=[0, 1])
qml.Rot(*w, wires=[1])
qml.CRX(-a, wires=[0, 1])
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

We can draw the QNode after execution:

>>> result = circuit(2.3, [1.2, 3.2, 0.7])
>>> print(circuit.draw())
0: ──H──╭C────────────────────────────╭C─────────╭┤ ⟨Z ⊗ Z⟩
1: ─────╰RX(2.3)──Rot(1.2, 3.2, 0.7)──╰RX(-2.3)──╰┤ ⟨Z ⊗ Z⟩
>>> print(circuit.draw(charset="ascii"))
0: --H--+C----------------------------+C---------+| <Z @ Z>
1: -----+RX(2.3)--Rot(1.2, 3.2, 0.7)--+RX(-2.3)--+| <Z @ Z>

Args:
charset (str, optional): The charset that should be used. Currently, "unicode" and
"ascii" are supported.

Raises:
ValueError: if the given charset is not supported
.QuantumFunctionError: drawing is impossible because the underlying
quantum tape has not yet been constructed

Returns:
str: the circuit representation of the tape

"""
if self.qtape is None:
raise qml.QuantumFunctionError(
"The QNode can only be drawn after its quantum tape has been constructed."
)

return self.qtape.draw(charset=charset)

def to_tf(self, dtype=None):
"""Apply the TensorFlow interface to the internal quantum tape.

Expand All @@ -467,7 +512,7 @@ def to_tf(self, dtype=None):
output. If not provided, the default is ``tf.float64``.

Raises:
qml.QuantumFunctionError: if TensorFlow >= 2.1 is not installed
.QuantumFunctionError: if TensorFlow >= 2.1 is not installed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this usually how it's written? 🤔

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this was originally written by another contributor (and was copied from the old core), but I quite like how it avoids the redundancy of saying "raises a QuantumFunctionError if TensorFlow >= 2.1 is not installed".

"""
# pylint: disable=import-outside-toplevel
try:
Expand All @@ -484,11 +529,11 @@ def to_tf(self, dtype=None):
if self.qtape is not None:
TFInterface.apply(self.qtape, dtype=tf.as_dtype(self.dtype))

except ImportError:
except ImportError as e:
raise qml.QuantumFunctionError(
"TensorFlow not found. Please install the latest "
"version of TensorFlow to enable the 'tf' interface."
)
) from e

def to_torch(self, dtype=None):
"""Apply the Torch interface to the internal quantum tape.
Expand All @@ -498,7 +543,7 @@ def to_torch(self, dtype=None):
output. If not provided, the default is ``torch.float64``.

Raises:
qml.QuantumFunctionError: if PyTorch >= 1.3 is not installed
.QuantumFunctionError: if PyTorch >= 1.3 is not installed
"""
# pylint: disable=import-outside-toplevel
try:
Expand All @@ -518,11 +563,11 @@ def to_torch(self, dtype=None):
if self.qtape is not None:
TorchInterface.apply(self.qtape, dtype=self.dtype)

except ImportError:
except ImportError as e:
raise qml.QuantumFunctionError(
"PyTorch not found. Please install the latest "
"version of PyTorch to enable the 'torch' interface."
)
) from e

def to_autograd(self):
"""Apply the Autograd interface to the internal quantum tape."""
Expand Down
35 changes: 35 additions & 0 deletions pennylane/tape/tapes/tape.py
Original file line number Diff line number Diff line change
Expand Up @@ -812,6 +812,41 @@ def graph(self):

return self._graph

def draw(self, charset="unicode"):
"""Draw the quantum tape as a circuit diagram.

Consider the following circuit as an example:

.. code-block:: python3

with QuantumTape() as tape:
qml.Hadamard(0)
qml.CRX(2.3, wires=[0, 1])
qml.Rot(1.2, 3.2, 0.7, wires=[1])
qml.CRX(-2.3, wires=[0, 1])
qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

We can draw the tape after construction:

>>> print(tape.draw())
0: ──H──╭C────────────────────────────╭C─────────╭┤ ⟨Z ⊗ Z⟩
1: ─────╰RX(2.3)──Rot(1.2, 3.2, 0.7)──╰RX(-2.3)──╰┤ ⟨Z ⊗ Z⟩
>>> print(tape.draw(charset="ascii"))
0: --H--+C----------------------------+C---------+| <Z @ Z>
1: -----+RX(2.3)--Rot(1.2, 3.2, 0.7)--+RX(-2.3)--+| <Z @ Z>

Args:
charset (str, optional): The charset that should be used. Currently, "unicode" and
"ascii" are supported.

Raises:
ValueError: if the given charset is not supported

Returns:
str: the circuit representation of the tape
"""
return self.graph.draw(charset=charset, show_variable_names=False)

@property
def data(self):
"""Alias to :meth:`~.get_parameters` and :meth:`~.set_parameters`
Expand Down
4 changes: 2 additions & 2 deletions pennylane/wires.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,11 @@ def map(self, wire_map):

try:
new_wires = Wires(new_wires)
except WireError:
except WireError as e:
raise WireError(
"Failed to implement wire map {}. Make sure that the new labels are unique and "
"valid wire labels.".format(w, wire_map)
)
) from e

return new_wires

Expand Down
4 changes: 2 additions & 2 deletions tests/circuit_drawer/test_representation_resolver.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ def test_index_of_array_or_append(self, list, element, index, list_after):
assert RepresentationResolver.index_of_array_or_append(element, list) == index
assert list == list_after

@pytest.mark.parametrize("par,expected", [(3, "3"), (5.236422, "5.236"),])
@pytest.mark.parametrize("par,expected", [(3, "3"), (5.236422, "5.24"),])
def test_single_parameter_representation(self, unicode_representation_resolver, par, expected):
"""Test that single parameters are properly resolved."""
assert unicode_representation_resolver.single_parameter_representation(par) == expected
Expand All @@ -95,7 +95,7 @@ def test_single_parameter_representation_kwarg_variable(
unicode_representation_resolver.single_parameter_representation(kwarg_variable) == "1"
)

@pytest.mark.parametrize("par,expected", [(3, "3"), (5.236422, "5.236"),])
@pytest.mark.parametrize("par,expected", [(3, "3"), (5.236422, "5.24"),])
def test_single_parameter_representation_varnames(
self, unicode_representation_resolver_varnames, par, expected
):
Expand Down
Loading