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 convenience decorators for creating qfunc transforms #1315

Merged
merged 54 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from 47 commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
44b4c37
Add convenience decorators for creating transforms
josh146 May 14, 2021
05e4eba
black
josh146 May 14, 2021
6a061ae
add docs
josh146 May 14, 2021
8f9c792
linting
josh146 May 14, 2021
99f1b05
Merge branch 'master' into better-transforms
josh146 May 14, 2021
a86595a
Apply suggestions from code review
josh146 May 14, 2021
766e126
Update pennylane/transforms/decorators.py
josh146 May 14, 2021
3a3af5a
allow decorator to work with no arguments
josh146 May 17, 2021
5a17b32
Merge branch 'better-transforms' of github.com:PennyLaneAI/pennylane …
josh146 May 17, 2021
48733d8
Merge branch 'master' into better-transforms
josh146 May 17, 2021
4535d4e
Add tests
josh146 May 17, 2021
b15d16d
Update pennylane/transforms/decorators.py
josh146 May 17, 2021
def8edc
add tests
josh146 May 17, 2021
9c2c2c6
update
josh146 May 17, 2021
85fa341
Merge branch 'master' into better-transforms
josh146 May 17, 2021
762c7ca
Update pennylane/transforms/decorators.py
josh146 May 17, 2021
c30e7f7
Merge branch 'master' into better-transforms
josh146 May 17, 2021
3a826df
update
josh146 May 17, 2021
44e4f71
more tests
josh146 May 17, 2021
8c27fd1
Merge branch 'master' into better-transforms
josh146 May 18, 2021
9c3be12
Apply suggestions from code review
josh146 May 18, 2021
f3cde4e
Update pennylane/transforms/decorators.py
josh146 May 18, 2021
f121e09
more
josh146 May 20, 2021
0c280fc
merge master
josh146 May 26, 2021
1e37727
fixes
josh146 May 26, 2021
fc7df1a
update tests
josh146 May 26, 2021
1647f50
black
josh146 May 26, 2021
c275cb9
fix
josh146 May 26, 2021
4ad490b
fix tests
josh146 May 26, 2021
20cdba0
fix tests
josh146 May 26, 2021
d064fc3
nicer code
josh146 May 26, 2021
18efc2c
nicer code
josh146 May 26, 2021
a7646d7
more
josh146 May 26, 2021
83e1e9d
fix tests
josh146 May 26, 2021
aeb8d76
fix tests
josh146 May 26, 2021
40f7884
remove batch reduce
josh146 May 26, 2021
192e2a1
changelog
josh146 May 26, 2021
cceed13
Apply suggestions from code review
josh146 May 26, 2021
3e50255
add JAX tests
josh146 May 26, 2021
cbfdb3c
Merge branch 'better-transforms' of github.com:PennyLaneAI/pennylane …
josh146 May 26, 2021
b982228
Update tests/transforms/test_qfunc_transform.py
josh146 May 26, 2021
5ec3422
Merge branch 'master' into better-transforms
josh146 May 28, 2021
bdf8876
Merge branch 'master' into better-transforms
josh146 May 31, 2021
82027bc
Merge branch 'master' into better-transforms
josh146 Jun 2, 2021
3add7a9
better docstring
josh146 Jun 2, 2021
c668550
more
josh146 Jun 2, 2021
21b779a
fixes
josh146 Jun 2, 2021
8726032
suggested changes
josh146 Jun 2, 2021
c2d4204
Update tests/transforms/test_qfunc_transform.py
josh146 Jun 3, 2021
acba2d9
Merge branch 'master' into better-transforms
josh146 Jun 3, 2021
b31fa34
suggested changes
josh146 Jun 3, 2021
dbc8708
Merge branch 'better-transforms' of github.com:PennyLaneAI/pennylane …
josh146 Jun 3, 2021
39f77da
suggested changes
josh146 Jun 3, 2021
8afc1b3
fix git diff issue
josh146 Jun 3, 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
60 changes: 60 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,66 @@

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

* Adds a decorator `@qml.qfunc_transform` to easily create a transformation
that modifies the behaviour of a quantum function.
[(#1315)](https://github.com/PennyLaneAI/pennylane/pull/1315)

For example, consider the following transform, which replaces all
`CRX` gates with a sequence of `RX`, `RY`, and `CZ` gates:

```python
@qml.qfunc_transform
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does it take a tape if it's a "qfunc" transform?

I thought "qfunc" transforms took in quantum functions and returned quantum functions?

Copy link
Member Author

Choose a reason for hiding this comment

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

@albi3ro that's exactly right! You could always write a qfunc transform manually like so, without ever needing to use any decorators:

def transform(old_qfunc, params):
    def new_qfunc(*args, **kwargs):
        # 1. extract the tape from the old qfunc, being
        # careful *not* to have it queued.
        tape = make_tape(old_qfunc)(*args, **kwargs)

        # 2. transform the tape
        new_tape = tape_transform(tape, params)

        # 3. queue the *new* tape to the active queuing context
        new_tape.queue()
    return new_qfunc

Note that this is pseudocode - steps (1) and (3) are much more complicated in practice than they are presented here! It's actually quite non-trivial to get the above working in general, because it requires:

  • an intimate knowledge of the queuing system and how it works

  • how to convert qfuncs into tapes without them being queued

  • dealing with potentially nested queuing contexts

  • having to take into account that qfuncs can be called in lots of different places

Steps (1) and (3) are actually the same no matter the qfunc transform; really, it is only step (2), tape_transform and the corresponding tape transform parameters, that really define the qfunc transformation.

So the idea with the decorator is therefore:

  • Allow a dev to provide a tape transform that defines the intended qfunc transform
  • All the queuing boilerplate (steps 1 and 3) are added automatically by the decorator (and are also tested thoroughly by the decorator tests, reducing maintenance overhead and chances of bugs!)

In essence, you can think of the decorator as taking a tape_transform, and elevating it into a qfunc transform.

def my_transform(tape, x, y):
for op in tape.operations + tape.measurements:
if op.name == "CRX":
wires = op.wires
param = op.parameters[0]
qml.RX(x * param, wires=wires[1])
qml.RY(y * qml.math.sqrt(param), wires=wires[1])
qml.CZ(wires=[wires[1], wires[0]])
else:
op.queue()
josh146 marked this conversation as resolved.
Show resolved Hide resolved
```

We can now apply this transform to any quantum function:

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

def ansatz(x):
qml.Hadamard(wires=0)
qml.CRX(x, wires=[0, 1])

@qml.qnode(dev)
def circuit(param, transform_weights):
qml.RX(0.1, wires=0)

# apply the transform to the ansatz
my_transform(*transform_weights)(ansatz)(param)
Copy link
Contributor

Choose a reason for hiding this comment

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

This is a very confusing example, or maybe just confusing syntax. Why are we transforming the first of the two parameters? I would expect something like my_transform(ansatz)(transform_wieghts, param).

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 agree in that it does come a bit out of left-field 😆

Funnily enough, my intuition prior to working on this PR was slightly different - I was expecting something along the lines of

my_transform(ansatz, transform_weights)(param)

(in that the transform_weights are a dependent quantity when applying the transform).

The reason instead for

my_transform(transform_weights)(ansatz)(param)

Is due almost completely to how decorators work in Python; decorators must always be of the form callable -> callable, which causes the above 'restriction'. However, it is also slightly more flexible - you can break it up into the following 'steps':

# 1. create a transform defined by `transform_weights`
specific_transform = my_transform(transform_weights)  
# 2. Apply the transform (callable -> callable) to the qfunc
new_qfunc = specific_transform(new_qfunc)
# 3. evaluate the new, transformed, quantum function
new_qfunc(params)

If we decide to drop the decorator functionality, then we could definitely support

my_transform(ansatz, transform_weights)(param)

@glassnotes, what do you think?

  • Drop the decorator functionality in favour of a clearer 'inline' syntax?
  • Keep the decorator functionality at the expense of a slightly more complicated 'inline' syntax?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm in favour of the decorators because of how much they abstract away in terms of writing the actual transform. However we should definitely add the example you wrote above to the docs, to show the equivalence of the inline version vs. "expanded" version.


return qml.expval(qml.PauliZ(1))
```

We can print this QNode to show that the qfunc transform is taking place:

```pycon
>>> x = np.array(0.5, requires_grad=True)
>>> y = np.array([0.1, 0.2], requires_grad=True)
>>> print(qml.draw(circuit)(x, y))
0: ──RX(0.1)───H──────────╭Z──┤
1: ──RX(0.05)──RY(0.141)──╰C──┤ ⟨Z⟩
```

Evaluating the QNode, as well as the derivative, with respect to the gate
parameter *and* the transform weights:

```pycon
>>> circuit(x, y)
0.9887793925354269
>>> qml.grad(circuit)(x, y)
(array(-0.02485651), array([-0.02474011, -0.09954244]))
```

* Added validation for noise channel parameters. Invalid noise parameters now
raise a `ValueError`. [(#1357)](https://github.com/PennyLaneAI/pennylane/pull/1357)

Expand Down
2 changes: 2 additions & 0 deletions pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
ctrl,
measurement_grouping,
metric_tensor,
qfunc_transform,
single_tape_transform,
)
from pennylane.utils import inv
from pennylane.vqe import ExpvalCost, Hamiltonian, VQECost
Expand Down
146 changes: 80 additions & 66 deletions pennylane/transforms/__init__.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,80 @@
# Copyright 2018-2021 Xanadu Quantum Technologies Inc.
josh146 marked this conversation as resolved.
Show resolved Hide resolved

# 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 subpackage contains QNode, quantum function, device, and tape transforms.


.. currentmodule:: pennylane

QNode transforms
----------------

The following transforms act on QNodes. They return new transformed functions
that compute the desired quantity.

.. autosummary::
:toctree: api

~transforms.classical_jacobian
~draw
~metric_tensor

Quantum function transforms
---------------------------

The following transforms act on quantum functions (Python functions
containing quantum operations) that are used *inside* QNodes.

.. autosummary::
:toctree: api

~adjoint
~ctrl
~transforms.invisible

Tape transforms
---------------

The following transforms act on quantum tapes, and return one or
more tapes as well as a classical processing function.

.. autosummary::
:toctree: api

~transforms.measurement_grouping
~transforms.metric_tensor_tape
~transforms.hamiltonian_expand
"""
from .adjoint import adjoint
from .classical_jacobian import classical_jacobian
from .control import ControlledOperation, ctrl
from .draw import draw
from .invisible import invisible
from .measurement_grouping import measurement_grouping
from .metric_tensor import metric_tensor, metric_tensor_tape
from .hamiltonian_expand import hamiltonian_expand
# 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 subpackage contains QNode, quantum function, device, and tape transforms.


.. currentmodule:: pennylane

QNode transforms
----------------

The following transforms act on QNodes. They return new transformed functions
that compute the desired quantity.

.. autosummary::
:toctree: api

~transforms.classical_jacobian
~draw
~metric_tensor

Quantum function transforms
---------------------------
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
Quantum function transforms
---------------------------

Would it make sense, while things are in flux, to just remove the headers that name the type of transforms, and keep instead just the one-line description that explains how they work? This keeps transforms with similar behaviour grouped, but doesn't bind us to a specific name yet.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done!


The following transforms act on quantum functions (Python functions
containing quantum operations) that are used *inside* QNodes.

.. autosummary::
:toctree: api

~adjoint
~ctrl
~transforms.invisible

Tape transforms
Copy link
Contributor

Choose a reason for hiding this comment

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

Do these contents need to be updated to fit the new description of "tape transform", or will that be done as part of #1362?

Copy link
Member Author

Choose a reason for hiding this comment

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

Oh good question. I think I have been avoiding updating the docs for now, since this is so fluid....

Copy link
Contributor

Choose a reason for hiding this comment

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

I think that tape transforms themselves are in a reasonably final form, with the only factor left being the multiple tape case. That said, since the focus and decorator here is single_tape_transform we could go with that as the definition of tape transform for now, but leave open the potential to later include multi_tape_transform (or something like this) as an additional part of the definition.

Copy link
Contributor

Choose a reason for hiding this comment

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

Though I guess if we did that we would have to move the three transforms in the autosummary to a different section 😕

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'm kinda tempted to leave it very loosely defined for now, to avoid locking us in to anything too early. Instead:

  • a tape transform is simply a function that maps tape(s) to tape(s). Any custom function can be written that does this.
  • @qml.single_tape_transform provides an API for easily creating a particular type of tape transform that is restricted in some snse.

That is, rather than @qml.single_tape_transform or some future @qml.multi_tape_transform defining what a tape transform is, they simply provide accessible ways to easily create two restricted types of tape transforms.

Copy link
Contributor

Choose a reason for hiding this comment

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

Nice, okay I'm on board with that!

---------------

The following transforms act on quantum tapes, and return one or
more tapes as well as a classical processing function.

.. autosummary::
:toctree: api

~transforms.measurement_grouping
~transforms.metric_tensor_tape
~transforms.hamiltonian_expand

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

The following decorators and convenience functions are provided
to help build custom QNode, quantum function, and tape transforms:

.. autosummary::
:toctree: api

~single_tape_transform
~qfunc_transform
~transforms.make_tape
"""
from .adjoint import adjoint
from .classical_jacobian import classical_jacobian
from .control import ControlledOperation, ctrl
from .draw import draw
from .hamiltonian_expand import hamiltonian_expand
from .invisible import invisible
from .measurement_grouping import measurement_grouping
from .metric_tensor import metric_tensor, metric_tensor_tape
from .qfunc_transforms import make_tape, single_tape_transform, qfunc_transform
Loading