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

Shots refactor I of III #1075

Merged
merged 33 commits into from
Feb 10, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
c33c289
accept shots argument per call
mariaschuld Feb 8, 2021
2470142
tiny fix
mariaschuld Feb 8, 2021
64f8e92
add to docs
mariaschuld Feb 8, 2021
152bd58
Merge branch 'master' into shots-refactor
mariaschuld Feb 8, 2021
1e5a4f9
Update .github/CHANGELOG.md
mariaschuld Feb 8, 2021
0c53d41
Update pennylane/tape/qnode.py
mariaschuld Feb 8, 2021
b7768e8
warning if shots in signature
mariaschuld Feb 8, 2021
a3d5223
Merge branch 'shots-refactor' of github.com:PennyLaneAI/pennylane int…
mariaschuld Feb 8, 2021
f8a3e35
fix some tests that use None for a qfunc
mariaschuld Feb 8, 2021
080bce7
Merge branch 'master' into shots-refactor
josh146 Feb 9, 2021
f0ec3d7
Update .github/CHANGELOG.md
mariaschuld Feb 9, 2021
e7d4166
Update doc/introduction/circuits.rst
mariaschuld Feb 9, 2021
5823945
Update pennylane/__init__.py
mariaschuld Feb 9, 2021
4068500
Merge branch 'master' into shots-refactor
mariaschuld Feb 9, 2021
6a5f34a
apply changes from Josh
mariaschuld Feb 9, 2021
90c4f28
minor formatting
mariaschuld Feb 9, 2021
b2386e4
change to warning
mariaschuld Feb 9, 2021
1e60866
Merge branch 'shots-refactor' of github.com:PennyLaneAI/pennylane int…
mariaschuld Feb 9, 2021
38b01c3
Merge branch 'master' into shots-refactor
mariaschuld Feb 9, 2021
4e4578c
make changes backwards compatible
mariaschuld Feb 9, 2021
7d41ade
Merge branch 'shots-refactor' of github.com:PennyLaneAI/pennylane int…
mariaschuld Feb 9, 2021
a2cacec
adapt test
mariaschuld Feb 9, 2021
538cb35
black
mariaschuld Feb 9, 2021
4f659b6
Update pennylane/__init__.py
mariaschuld Feb 9, 2021
0f018f7
formatting
mariaschuld Feb 9, 2021
1d19a79
improve tests
mariaschuld Feb 9, 2021
dd8e332
implement two more comments from Josh
mariaschuld Feb 9, 2021
ba9dd12
Update pennylane/tape/qnode.py
mariaschuld Feb 9, 2021
d1581d0
Update tests/tape/tapes/test_qnode.py
mariaschuld Feb 9, 2021
23f1e09
Update tests/tape/tapes/test_qnode.py
mariaschuld Feb 9, 2021
d6dd9b7
Merge branch 'master' into shots-refactor
mariaschuld Feb 9, 2021
4e85796
fix formatting
mariaschuld Feb 10, 2021
4f780f0
Merge branch 'master' into shots-refactor
mariaschuld Feb 10, 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
16 changes: 16 additions & 0 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,22 @@

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

- The number of shots can now be specified on a temporary basis when evaluating a QNode.
[(#1075)](https://github.com/PennyLaneAI/pennylane/pull/1075)
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

```python
dev = qml.device('default.qubit', wires=1, shots=10) # default is 10

@qml.qnode(dev)
def circuit(a):
qml.RX(a, wires=0)
return qml.sample(qml.PauliZ(wires=0))

print(circuit(0.8)) # [ 1 1 1 -1 -1 1 1 1 1 1]
print(circuit(0.8, shots=3)) # [ 1 1 1] -> default is changed for this call
print(circuit(0.8)) # [ 1 1 1 -1 -1 1 1 1 1 1]
```

- Added the `ControlledPhaseShift` gate as well as the `QFT` operation for applying quantum Fourier
transforms.
[(#1064)](https://github.com/PennyLaneAI/pennylane/pull/1064)
Expand Down
4 changes: 3 additions & 1 deletion doc/introduction/circuits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,9 @@ devices, the options are:
and variances analytically. Only possible with simulator devices. Defaults to ``True``.

* ``shots`` (*int*): How many times the circuit should be evaluated (or sampled) to estimate
the expectation values. Defaults to 1000 if not specified.
the expectation values. Defaults to 1000 if not specified. This argument can be temporarily overwritten
when a QNode is called; for example ``my_qnode(shots=3)`` will temporarily evaluate ``my_qnode``
using three shots.

For a plugin device, refer to the plugin documentation for available device options.

Expand Down
23 changes: 22 additions & 1 deletion pennylane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ def device(name, *args, **kwargs):
of qubit-based quantum circuit architectures which allows
automatic differentiation through the simulation via python's autograd library.

In addition, additional devices are supported through plugins — see
Additional devices are supported through plugins — see
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
the `available plugins <https://pennylane.ai/plugins.html>`_ for more
details.

Expand Down Expand Up @@ -169,6 +169,27 @@ def circuit():
qml.CNOT(wires=['q12', -1] )
...

Most devices accept a ``shots`` argument which specifies how many circuit executions
are used to estimate stochastic return values. In particular, ``qml.sample()`` measurements
will return as many samples as specified in the shots argument.

.. code-block:: python

dev = qml.device('default.qubit', wires=1, shots=10)

@qml.qnode(dev)
def circuit(a):
qml.RX(a, wires=0)
return qml.sample(qml.PauliZ(wires=0))

print(circuit(0.8)) # [ 1 1 1 -1 -1 1 1 1 1 1] -> 10 samples are returned
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

The shots argument can be changed on a per-call basis using the built-in ``shots`` keyword argument:

..code-block:: python

print(circuit(0.8, shots=3)) # [ 1 1 1] -> default is overwritten for this call
print(circuit(0.8)) # [ 1 1 1 -1 -1 1 1 1 1 1] -> default is reinstated
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

Some devices may accept additional arguments. For instance,
``default.gaussian`` accepts the keyword argument ``hbar``, to set
Expand Down
16 changes: 16 additions & 0 deletions pennylane/tape/qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from collections.abc import Sequence
from functools import lru_cache, update_wrapper, wraps
import warnings
import inspect

import numpy as np

Expand Down Expand Up @@ -151,6 +152,11 @@ def __init__(
"Invalid device. Device must be a valid PennyLane device."
)

if "shots" in inspect.signature(func).parameters:
raise qml.QuantumFunctionError(
"The shots argument is reserved and will be overwritten!"
)
Copy link
Member

Choose a reason for hiding this comment

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

I would make this a warning rather than an exception. I can't quite explain, but it 'feels' more like a warning.

Like, we shouldn't stop the user from creating the qnode, but we should warn them that the shots argument won't act as they expect?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The reason why I changed to the error is that I played around and all sorts of strange things happened until I realised that I used a shots argument in my code and I had just overlooked the warning...

I think a warning is more appropriate but an exception safer? Happy to change though!


self.mutable = mutable
self.func = func
self._original_device = device
Expand Down Expand Up @@ -525,13 +531,23 @@ def construct(self, args, kwargs):
)

def __call__(self, *args, **kwargs):

# If shots specified from call, we override the device's default shots value.
shots = kwargs.pop("shots", None)
if shots is not None:
original_shots = self.device.shots # remember device shots
self.device.shots = shots # temporarily change shots
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

if self.mutable or self.qtape is None:
# construct the tape
self.construct(args, kwargs)

# execute the tape
res = self.qtape.execute(device=self.device)

if shots is not None:
self.device.shots = original_shots

# FIX: If the qnode swapped the device, increase the num_execution value on the original device.
# In the long run, we should make sure that the user's device is the one
# actually run so she has full control. This could be done by changing the class
Expand Down
50 changes: 42 additions & 8 deletions tests/tape/tapes/test_qnode.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@
from pennylane.tape import JacobianTape, QNode, draw, qnode, QubitParamShiftTape, CVParamShiftTape


def dummyfunc():
return None
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved


class TestValidation:
"""Tests for QNode creation and validation"""

Expand All @@ -33,12 +37,12 @@ def test_invalid_interface(self):
)

with pytest.raises(qml.QuantumFunctionError, match=expected_error):
QNode(None, dev, interface="something")
QNode(dummyfunc, dev, interface="something")

def test_invalid_device(self):
"""Test that an exception is raised for an invalid device"""
with pytest.raises(qml.QuantumFunctionError, match="Invalid device"):
QNode(None, None)
QNode(dummyfunc, None)

def test_validate_device_method(self, monkeypatch):
"""Test that the method for validating the device diff method
Expand Down Expand Up @@ -199,28 +203,28 @@ def test_diff_method(self, mocker):
mock_device = mocker.patch("pennylane.tape.QNode._validate_device_method")
mock_device.return_value = 7, 8, 9, {"method": "device"}

qn = QNode(None, dev, diff_method="best")
qn = QNode(dummyfunc, dev, diff_method="best")
assert qn._tape == mock_best.return_value[0]
assert qn.interface == mock_best.return_value[1]
assert qn.diff_options["method"] == mock_best.return_value[3]["method"]

qn = QNode(None, dev, diff_method="backprop")
qn = QNode(dummyfunc, dev, diff_method="backprop")
assert qn._tape == mock_backprop.return_value[0]
assert qn.interface == mock_backprop.return_value[1]
assert qn.diff_options["method"] == mock_backprop.return_value[3]["method"]
mock_backprop.assert_called_once()

qn = QNode(None, dev, diff_method="device")
qn = QNode(dummyfunc, dev, diff_method="device")
assert qn._tape == mock_device.return_value[0]
assert qn.interface == mock_device.return_value[1]
assert qn.diff_options["method"] == mock_device.return_value[3]["method"]
mock_device.assert_called_once()

qn = QNode(None, dev, diff_method="finite-diff")
qn = QNode(dummyfunc, dev, diff_method="finite-diff")
assert qn._tape == JacobianTape
assert qn.diff_options["method"] == "numeric"

qn = QNode(None, dev, diff_method="parameter-shift")
qn = QNode(dummyfunc, dev, diff_method="parameter-shift")
assert qn._tape == QubitParamShiftTape
assert qn.diff_options["method"] == "analytic"

Expand All @@ -234,7 +238,7 @@ def test_unknown_diff_method(self):
with pytest.raises(
qml.QuantumFunctionError, match="Differentiation method hello not recognized"
):
QNode(None, dev, diff_method="hello")
QNode(dummyfunc, dev, diff_method="hello")

def test_validate_adjoint_invalid_device(self):
"""Test if a ValueError is raised when an invalid device is provided to
Expand Down Expand Up @@ -915,3 +919,33 @@ def circuit(x):
# test differentiability. The circuit will assume an RZ gate
grad = qml.grad(circuit)(-0.5)
np.testing.assert_allclose(grad, 0, atol=tol, rtol=0)


class TestShots:
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved
"""Unittests for specifying shots per call."""

def test_specify_shots(self):
"""Tests that shots can be set per call."""
dev = qml.device('default.qubit', wires=1, shots=10)

@qml.qnode(dev)
def circuit(a):
qml.RX(a, wires=0)
return qml.sample(qml.PauliZ(wires=0))

assert len(circuit(0.8)) == 10
assert len(circuit(0.8, shots=2)) == 2
assert len(circuit(0.8, shots=3178)) == 3178
assert len(circuit(0.8)) == 10
mariaschuld marked this conversation as resolved.
Show resolved Hide resolved

def test_shots_reserved_qfunc_argument(self):
"""Tests that a warning is raised if the quantum function has a shots argument."""
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@josh146 is this the test you had in mind?

Copy link
Member

Choose a reason for hiding this comment

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

Yep looks good!


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

def circuit(a, shots=2):
qml.RX(a, wires=shots)
return qml.expval(qml.PauliZ(wires=0))

with pytest.raises(qml.QuantumFunctionError, match="The shots argument is reserved"):
qml.QNode(circuit, dev)