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

Add transform to insert operations into circuits #1795

Merged
merged 69 commits into from
Oct 27, 2021
Merged
Show file tree
Hide file tree
Changes from 51 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
c470e10
Add to init
trbromley Oct 22, 2021
76eb9c8
Add to docs
trbromley Oct 22, 2021
68494fd
Add base transform
trbromley Oct 22, 2021
801496c
Add tests
trbromley Oct 22, 2021
36212a4
Add qfunc transform
trbromley Oct 22, 2021
3e5057a
Add qfunc transform
trbromley Oct 22, 2021
8575a59
Add test
trbromley Oct 22, 2021
c0ad944
Add state prep support
trbromley Oct 22, 2021
887f878
Add tests
trbromley Oct 22, 2021
e94095a
Add to changelog
trbromley Oct 22, 2021
e50402c
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 22, 2021
f738e39
Update changelog
trbromley Oct 22, 2021
8e2ef87
Fix changelog
trbromley Oct 22, 2021
73dbc9f
Fix codefactor
trbromley Oct 22, 2021
89feb7a
Fix CI
trbromley Oct 22, 2021
9433575
Update docstrings
trbromley Oct 22, 2021
1ade5b3
Fix
trbromley Oct 22, 2021
a4d64f3
Rename
trbromley Oct 25, 2021
2f1bbd8
Add to init
trbromley Oct 25, 2021
5940470
Improve docstrings
trbromley Oct 25, 2021
807b33a
Add to changelog
trbromley Oct 25, 2021
31e5b2c
Add to docstring
trbromley Oct 25, 2021
7aed33c
Add test for add_noise_to_dev
trbromley Oct 25, 2021
59b6f9e
Fix CI
trbromley Oct 25, 2021
d06d66f
Attempt
trbromley Oct 25, 2021
de8594d
Revert qfunc changes
trbromley Oct 25, 2021
71a7075
Update tests
trbromley Oct 25, 2021
7406919
Fix CI
trbromley Oct 25, 2021
f31ac72
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 25, 2021
b4e512a
Reword changelog
trbromley Oct 25, 2021
27e4666
Fix CI
trbromley Oct 25, 2021
c110d9a
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 25, 2021
7777df6
Rename module from noise to insert
trbromley Oct 26, 2021
dfb0cc1
Rename add_noise to insert
trbromley Oct 26, 2021
f0854e0
Rename add_noise_to_dev to insert_in_dev
trbromley Oct 26, 2021
21cc485
Update docstrings
trbromley Oct 26, 2021
7989ed0
Rename test
trbromley Oct 26, 2021
ca834bf
Fix tests
trbromley Oct 26, 2021
fad9028
Polish docstrings
trbromley Oct 26, 2021
9fadacd
Update to not modify dev in place
trbromley Oct 26, 2021
64c2cc2
Fix test
trbromley Oct 26, 2021
ce7468a
Merge branch 'add_noisy_tape_transform' of github.com:XanaduAI/pennyl…
trbromley Oct 26, 2021
b24adb8
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 26, 2021
c9517fa
Move from improvements to new features
trbromley Oct 26, 2021
c9ff037
State prep update
trbromley Oct 26, 2021
441d6ed
Fix CI
trbromley Oct 26, 2021
c48ed3a
Provide single dispatch in qfunc_transform
trbromley Oct 26, 2021
ec83d76
Update to avoid using tape_fn
trbromley Oct 26, 2021
55a733b
Changelog
trbromley Oct 26, 2021
6c5bfba
Rename
trbromley Oct 26, 2021
ae204f8
Add qfunc test
trbromley Oct 26, 2021
1e3b18a
Fix CI
trbromley Oct 26, 2021
a3b3248
Remove single_tape_transform
trbromley Oct 27, 2021
4f71fb9
Update pennylane/transforms/insert_ops.py
trbromley Oct 27, 2021
dda77e3
Update pennylane/transforms/insert_ops.py
trbromley Oct 27, 2021
5dead27
Update pennylane/transforms/insert_ops.py
trbromley Oct 27, 2021
171f6ef
Update
trbromley Oct 27, 2021
eca476b
Add single dispatch
trbromley Oct 27, 2021
540f253
Update changelog
trbromley Oct 27, 2021
94c69db
Work on docstrings
trbromley Oct 27, 2021
42ea5bd
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 27, 2021
c3cca25
Add to changelog
trbromley Oct 27, 2021
e23ff95
Add to tests
trbromley Oct 27, 2021
503f2d2
Update docstrings
trbromley Oct 27, 2021
ee23212
Move device transform API into the `@qfunc_transform` decorator (#1809)
josh146 Oct 27, 2021
788a68b
Update docstrings
trbromley Oct 27, 2021
c76abaa
Merge branch 'master' into add_noisy_tape_transform
trbromley Oct 27, 2021
62c58b5
Apply suggestions from code review
trbromley Oct 27, 2021
96cb3d0
Mention state preps
trbromley Oct 27, 2021
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
34 changes: 33 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,38 @@

<h3>New features since last release</h3>

* The `insert` and `insert_in_dev` transforms have now been added,
providing a way to insert single-qubit operations into a quantum circuit.
[(#1795)](https://github.com/PennyLaneAI/pennylane/pull/1795)

The following QNode can be transformed to add noise to the circuit:

```python
from pennylane.transforms import insert

dev = qml.device("default.mixed", wires=2)

@qml.qnode(dev)
@insert(qml.AmplitudeDamping, 0.2, position="end")
def f(w, x, y, z):
qml.RX(w, wires=0)
qml.RY(x, wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(y, wires=0)
qml.RX(z, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))
```

Executions of this circuit will differ from the noise-free value:

```pycon
>>> f(0.9, 0.4, 0.5, 0.6)
tensor(0.754847, requires_grad=True)
>>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6))
0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩
1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩
```

* Common tape expansion functions are now available in `qml.transforms`,
alongside a new `create_expand_fn` function for easily creating expansion functions
from stopping criteria.
Expand Down Expand Up @@ -797,6 +829,6 @@

This release contains contributions from (in alphabetical order):

Utkarsh Azad, Akash Narayanan B, Sam Banning, Olivia Di Matteo, Andrew Gardhouse, David Ittah, Josh Izaac, Christina Lee,
Utkarsh Azad, Akash Narayanan B, Sam Banning, Thomas Bromley, Olivia Di Matteo, Andrew Gardhouse, David Ittah, Josh Izaac, Christina Lee,
Romain Moyard, Carrie-Anne Rubidge, Maria Schuld, Rishabh Singh, Ingrid Strandberg, Antal Száva, Cody Wang,
David Wierichs, Moritz Willmann.
13 changes: 13 additions & 0 deletions pennylane/transforms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
~ctrl
~apply_controlled_Q
~quantum_monte_carlo
~transforms.insert

Transforms for circuit compilation
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Expand Down Expand Up @@ -86,6 +87,17 @@
~transforms.measurement_grouping
~transforms.hamiltonian_expand

Transforms that act on devices
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These transforms apply to circuits just before they are
executed on the device.

.. autosummary::
:toctree: api

~transforms.insert_in_dev

Decorators and utility functions
--------------------------------

Expand Down Expand Up @@ -117,6 +129,7 @@
from .hamiltonian_expand import hamiltonian_expand
from .measurement_grouping import measurement_grouping
from .metric_tensor import metric_tensor
from .insert_ops import insert, insert_in_dev
from .optimization import (
cancel_inverses,
commute_controlled,
Expand Down
259 changes: 259 additions & 0 deletions pennylane/transforms/insert_ops.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
# Copyright 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.
"""
Provides transforms for inserting operations into quantum circuits.
"""
from collections.abc import Sequence
from copy import deepcopy
from types import FunctionType
from typing import Type, Union

from pennylane import BasisState, Device, QubitStateVector, apply
from pennylane.operation import Operation
from pennylane.tape import QuantumTape
from pennylane.transforms.qfunc_transforms import qfunc_transform, single_tape_transform


@qfunc_transform
@single_tape_transform
trbromley marked this conversation as resolved.
Show resolved Hide resolved
def insert(
circuit: Union[callable, QuantumTape],
op: Union[callable, Type[Operation]],
op_args: Union[tuple, float],
position: str = "all",
) -> Union[callable, QuantumTape]:
"""Insert an operation into specified points in an input circuit.

The circuit will be updated to have the operation, specified by the ``op`` argument, added
according to the positioning specified in the ``position`` argument. Only single qubit
operations are permitted.

The type of ``op`` can be either a single operation or a quantum function. A quantum function
can be used to specify a sequence of operations acting on a single qubit, see the usage details
for more information.
trbromley marked this conversation as resolved.
Show resolved Hide resolved

Args:
circuit (callable or QuantumTape): the input circuit
op (callable or Type[Operation]): the single-qubit operation, or sequence of operations
Copy link
Contributor

Choose a reason for hiding this comment

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

What is the reason for restricting to single-qubit operations? I think a key use-case of this feature would be for tinkering with the noise on, e.g., adding depolarization to all the CNOTs in a circuit.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went with the single-qubit restriction on the inserted operation just to make placement easier. For example, if the inserted operation is CNOT (which wouldn't currently be allowed) and position="all", I'm not sure where we'd place the second wire after, e.g., a Hadamard gate.

On the other hand, we can insert single-qubit gates in circuits that already include two-qubit gates. So we could place depolarizing noise after a CNOT, but just not selectively yet (i.e., it'd be after all gates).

I agree though, a bit more flexibility in this function would be great!

acting on a single qubit, to be inserted into the circuit
op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single
float
position (str): Specification of where to add the operation. Should be one of: ``"all"`` to
Copy link
Contributor

Choose a reason for hiding this comment

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

It could be useful too to add an option for all gates of a certain type; e.g., if you just wanted to add depolarizing noise to a particular gate, or different amounts of noise to different types of gates.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, definitely agree with these points! I did initially think about having the position argument be a mapping from gate to inserted gate, but decided to keep things simple for an initial attempt.

Copy link
Contributor

Choose a reason for hiding this comment

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

Makes sense! 💯

add the operation after all gates; ``"start"`` to add the operation to all wires
at the start of the circuit; ``"end"`` to add the operation to all wires at the
Copy link
Contributor

Choose a reason for hiding this comment

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

Perhaps mention here the interaction with state prep operations and 'start' option. (Or maybe better in usage details?)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Good point, made that change: 96cb3d0

end of the circuit.

Returns:
callable or QuantumTape: the updated version of the input circuit

Raises:
ValueError: if a single operation acting on multiple wires is passed to ``op``
ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or
``'all'``

**Example:**

The following QNode can be transformed to add noise to the circuit:

.. code-block:: python3

dev = qml.device("default.mixed", wires=2)

@qml.qnode(dev)
@qml.transforms.insert(qml.AmplitudeDamping, 0.2, position="end")
def f(w, x, y, z):
qml.RX(w, wires=0)
qml.RY(x, wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(y, wires=0)
qml.RX(z, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

Executions of this circuit will differ from the noise-free value:

>>> f(0.9, 0.4, 0.5, 0.6)
tensor(0.754847, requires_grad=True)
>>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6))
0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.2)──╭┤ ⟨Z ⊗ Z⟩
1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.2)──╰┤ ⟨Z ⊗ Z⟩

.. UsageDetails::

**Specifying the operation as a quantum function:**

Instead of specifying ``op`` as a single :class:`~.Operation`, we can instead define a
quantum function. For example:

.. code-block:: python3

def op(x, y, wires):
qml.RX(x, wires=wires)
qml.PhaseShift(y, wires=wires)

This operation can be inserted into the following circuit:

.. code-block:: python3

dev = qml.device("default.qubit", wires=2)

@qml.qnode(dev)
@insert(op, [0.2, 0.3], position="end")
def f(w, x, y, z):
qml.RX(w, wires=0)
qml.RY(x, wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(y, wires=0)
qml.RX(z, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

To check this, let's print out the circuit:

>>> print(qml.draw(f)(0.9, 0.4, 0.5, 0.6))
0: ──RX(0.9)──╭C──RY(0.5)──RX(0.2)──Rϕ(0.3)──╭┤ ⟨Z ⊗ Z⟩
1: ──RY(0.4)──╰X──RX(0.6)──RX(0.2)──Rϕ(0.3)──╰┤ ⟨Z ⊗ Z⟩

**Transforming tapes:**

Consider the following tape:

.. code-block:: python3

with qml.tape.QuantumTape() as tape:
qml.RX(0.9, wires=0)
qml.RY(0.4, wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(0.5, wires=0)
qml.RX(0.6, wires=1)
qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

We can add the :class:`~.AmplitudeDamping` channel to the end of the circuit using:

>>> from pennylane.transforms import insert
>>> noisy_tape = insert(qml.AmplitudeDamping, 0.05, position="end")(tape)
>>> print(noisy_tape.draw())
0: ──RX(0.9)──╭C──RY(0.5)──AmplitudeDamping(0.05)──╭┤ ⟨Z ⊗ Z⟩
1: ──RY(0.4)──╰X──RX(0.6)──AmplitudeDamping(0.05)──╰┤ ⟨Z ⊗ Z⟩
trbromley marked this conversation as resolved.
Show resolved Hide resolved
"""
if not isinstance(op, FunctionType) and op.num_wires != 1:
raise ValueError("Only single-qubit operations can be inserted into the circuit")
if position not in ("start", "end", "all"):
raise ValueError("Position must be either 'start', 'end', or 'all' (default)")
trbromley marked this conversation as resolved.
Show resolved Hide resolved

if not isinstance(op_args, Sequence):
op_args = [op_args]

num_preps = sum(isinstance(o, (QubitStateVector, BasisState)) for o in circuit.operations)
trbromley marked this conversation as resolved.
Show resolved Hide resolved

for i in range(num_preps):
apply(circuit.operations[i])

if position == "start":
for w in circuit.wires:
op(*op_args, wires=w)

for circuit_op in circuit.operations[num_preps:]:
apply(circuit_op)
if position == "all":
for w in circuit_op.wires:
op(*op_args, wires=w)

if position == "end":
for w in circuit.wires:
op(*op_args, wires=w)

for m in circuit.measurements:
apply(m)


def insert_in_dev(
Copy link
Member

Choose a reason for hiding this comment

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

this is not blocking approval, but I was staring at this trying to come up with a better name, when I realized that the signature is almost identical to the transform above --- only differing in the type of the first argument.

This makes it the perfect use-case for @functools.singledispatch, which would then allow

@qml.qnode(dev)
@insert(op, "end")
def circuit():
   ...

and

new_dev = insert(dev, op, "end")

which solves the naming!

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nice, one transform to rule them all! This is cool, am checking out.

device: Device,
op: Union[callable, Type[Operation]],
op_args: Union[tuple, float],
position: str = "all",
) -> Device:
"""Insert an operation into specified points in circuits during device execution.

After applying this transform, circuits executed on the device will have operations inserted.
The operations are specified by the ``op`` argument and positioned according to the
``position`` argument. Only single qubit operations are permitted.

The type of ``op`` can be either a single operation or a quantum function. A quantum function
can be used to specify a sequence of operations acting on a single qubit.

.. warning::

This device transform is a beta feature. Use the :class:`pennylane.beta.QNode` decorator to
create compatible QNodes and use :func:`~.execute` to execute quantum tapes.
Copy link
Member

Choose a reason for hiding this comment

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

how come execute is mentioned here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In this line I'm trying to say:

  • If you want to make a QNode, use pennylane.beta.QNode
  • If you have a tape and you want to execute it on the transformed device, use the execute() function (rather than the tape.execute() method, which won't work because expand_fn is not called.

Think it needs a reword? 🤔 Or maybe I have misunderstood?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, it was the 'and',

Use the :class:`pennylane.beta.QNode` decorator to
create compatible QNodes *and* use :func:`~.execute` to execute quantum tapes.

which confused me, made me think both needed to be used to run this transform.

Even though it is not the full information, I would probably truncate this to

This device transform is a beta feature. Use the :class:`pennylane.beta.QNode` decorator to create compatible QNodes.

since QNodes will be 99% of use-cases, and tape.execute() will be removed shortly anyway.


Args:
device (Device): the device to be transformed
op (callable or Type[Operation]): the single-qubit operation, or sequence of operations
acting on a single qubit, to be inserted into circuits
op_args (tuple or float): the arguments fed to the operation, either as a tuple or a single
float
position (str): Specification of where to add the operation. Should be one of: ``"all"`` to
add the operation after all gates; ``"start"`` to add the operation to all wires
at the start of the circuit; ``"end"`` to add the operation to all wires at the
end of the circuit.

Returns:
Device: the updated device

Raises:
ValueError: if a single operation acting on multiple wires is passed to ``op``
ValueError: if the requested ``position`` argument is not ``'start'``, ``'end'`` or
``'all'``

**Example:**

Consider the following QNode:

.. code-block:: python3

from pennylane.beta import qnode

dev = qml.device("default.mixed", wires=2)

@qnode(dev)
def f(w, x, y, z):
qml.RX(w, wires=0)
qml.RY(x, wires=1)
qml.CNOT(wires=[0, 1])
qml.RY(y, wires=0)
qml.RX(z, wires=1)
return qml.expval(qml.PauliZ(0) @ qml.PauliZ(1))

Execution of the circuit on ``dev`` will be noise-free:

>>> f(0.9, 0.4, 0.5, 0.6)
tensor(0.86243536, requires_grad=True)

However, noise can be easily added to the device:

>>> qml.transforms.insert_in_dev(dev, qml.AmplitudeDamping, 0.2)
>>> f(0.9, 0.4, 0.5, 0.6)
tensor(0.72945434, requires_grad=True)
trbromley marked this conversation as resolved.
Show resolved Hide resolved
"""
# TODO: Remove warning in docstrings once new QNode replaces the old

new_dev = deepcopy(device)
original_expand_fn = new_dev.expand_fn

def new_expand_fn(circuit, max_expansion=10):
new_tape = insert(op, op_args, position)(circuit)
return original_expand_fn(new_tape, max_expansion=max_expansion)

new_dev.expand_fn = new_expand_fn

return new_dev
trbromley marked this conversation as resolved.
Show resolved Hide resolved
3 changes: 3 additions & 0 deletions pennylane/transforms/qfunc_transforms.py
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,9 @@ def __call__(self, tape, *args, **kwargs):
def _create_qfunc_internal_wrapper(fn, tape_transform, transform_args, transform_kwargs):
"""Convenience function to create the internal wrapper function
generated by the qfunc_transform decorator"""
if isinstance(fn, qml.tape.QuantumTape):
return tape_transform(fn, *transform_args, **transform_kwargs)

if not callable(fn):
raise ValueError(
f"The qfunc to transform, {fn}, does not appear "
Expand Down
Loading