Repository: PennyLaneAI/pennylane
Pull request #1658 (2021-09-15 17:22:52)

Diff option backprop check rc
**Context**

In the QNode, for cases when `diff_method!='backprop'`, we apply the interface that was specified by the user.

With the changes in https://github.com/PennyLaneAI/pennylane/pull/1568, at the point where this is being done, we might have `diff_method='best'` represent backpropagation when we have a backpropagation device at hand. Therefore, we might be applying the interface even when we'll be using backpropagation. This is unwanted behaviour as the interface should not be applied when using backpropagation, as we're leaning on the machine learning framework to perform the gradient computation (instead of providing a gradient with the interface).

**Changes**

Extends the criterion used for checking when to apply the interface to a QNode.

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [.github/CHANGELOG.md](/PennyLaneAI/pennylane/blob/master/.github/CHANGELOG.md) with:
* A one-to-two sentence description of the change. You may include a small working example for new features.
* A link back to this PR.
* Your name (or GitHub username) in the contributors section.

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index f2fcbecc2..0884159da 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -2,6 +2,46 @@
 
 <h3>New features since last release</h3>
 
+<h4>PennyLane now comes packaged with <code>lightning.qubit</code></h4>
+
+* The C++-based [lightning.qubit](https://pennylane-lightning.readthedocs.io/en/stable/) device
+  is now included with installations of PennyLane. The `lightning.qubit` device is a fast
+  state-vector simulator equipped with the efficient
+  [adjoint method](https://arxiv.org/abs/2009.02823) for differentiating quantum circuits,
+  check out the plugin
+  [release notes](https://github.com/PennyLaneAI/pennylane-lightning/blob/v0.18.0/.github/CHANGELOG.md#new-features-since-last-release) for more details! The device can be accessed in the following way:
+  
+  ```python
+  import pennylane as qml
+
+  wires = 3
+  layers = 2
+  dev = qml.device("lightning.qubit", wires=wires)
+
+  @qml.qnode(dev, diff_method="adjoint")
+  def circuit(weights):
+      qml.templates.StronglyEntanglingLayers(weights, wires=range(wires))
+      return qml.expval(qml.PauliZ(0))
+
+  weights = qml.init.strong_ent_layers_normal(layers, wires, seed=1967)
+  ```
+  
+  Evaluating circuits and their gradients on the device can be achieved using the standard approach:
+  
+  ```pycon
+  >>> print(f"Circuit evaluated: {circuit(weights)}")
+  Circuit evaluated: 0.9801286266677633
+  >>> print(f"Circuit gradient:\n{qml.grad(circuit)(weights)}")
+  Circuit gradient:
+  [[[-9.35301749e-17 -1.63051504e-01 -4.14810501e-04]
+    [-7.88816484e-17 -1.50136528e-04 -1.77922957e-04]
+    [-5.20670796e-17 -3.92874550e-02  8.14523075e-05]]
+
+   [[-1.14472273e-04  3.85963953e-02 -9.39190132e-18]
+    [-5.76791765e-05 -9.78478343e-02  0.00000000e+00]
+    [ 0.00000000e+00  0.00000000e+00  0.00000000e+00]]] 
+  ```
+
 * Custom gradient transforms can now be created using the new
   `@qml.gradients.gradient_transform` decorator on a batch-tape transform.
   [(#1589)](https://github.com/PennyLaneAI/pennylane/pull/1589)
@@ -305,6 +345,10 @@
 
 <h3>Improvements</h3>
 
+* A warning is raised to inform the user that specifying a list of shots is
+  only supported for `QubitDevice` based devices.
+  [(#1659)](https://github.com/PennyLaneAI/pennylane/pull/1659)
+
 * The `qml.circuit_drawer.MPLDrawer` class provides manual circuit drawing
   functionality using Matplotlib. While not yet integrated with automatic circuit
   drawing, this class provides customization and control.
@@ -457,6 +501,9 @@ and requirements-ci.txt (unpinned). This latter would be used by the CI.
 
 <h3>Bug fixes</h3>
 
+* Fix bug with shot vectors and `Device` base class.
+  [(#1666)](https://github.com/PennyLaneAI/pennylane/pull/1666)
+
 * Fix bug with norm and Jax tracers (jit) when using `QubiStateVector`.
   [(#1649)](https://github.com/PennyLaneAI/pennylane/pull/1649)
   
diff --git a/pennylane/_device.py b/pennylane/_device.py
index f0285da06..3824d2a51 100644
--- a/pennylane/_device.py
+++ b/pennylane/_device.py
@@ -16,6 +16,7 @@ This module contains the :class:`Device` abstract base class.
 """
 # pylint: disable=too-many-format-args, use-maxsplit-arg
 import abc
+import warnings
 from collections.abc import Iterable, Sequence
 from collections import OrderedDict, namedtuple
 from functools import lru_cache
@@ -88,7 +89,7 @@ def _process_shot_sequence(shot_list):
     else:
         raise ValueError(f"Unknown shot sequence format {shot_list}")
 
-    total_shots = np.sum(np.prod(shot_vector, axis=1))
+    total_shots = int(np.sum(np.prod(shot_vector, axis=1)))
     return total_shots, shot_vector
 
 
@@ -406,6 +407,12 @@ class Device(abc.ABC):
         self._parameters.update(parameters)
 
         results = []
+        if self._shot_vector is not None:
+            # The following warning assumes that QubitDevice.execute is stand-alone
+            warnings.warn(
+                "Specifying a list of shots is only supported for "
+                "QubitDevice based devices. Falling back to executions using all shots in the shot list."
+            )
 
         with self.execution_context():
             self.pre_apply()
diff --git a/pennylane/circuit_drawer/mpldrawer.py b/pennylane/circuit_drawer/mpldrawer.py
index 58ba86653..2993363df 100644
--- a/pennylane/circuit_drawer/mpldrawer.py
+++ b/pennylane/circuit_drawer/mpldrawer.py
@@ -16,14 +16,12 @@ This module contains the MPLDrawer class for creating circuit diagrams with matp
 """
 from collections.abc import Iterable
 
+has_mpl = True
 try:
     import matplotlib.pyplot as plt
     from matplotlib import patches
 except (ModuleNotFoundError, ImportError) as e:
-    raise ImportError(
-        "Module matplotlib is required for ``MPLDrawer`` class. "
-        "You can install matplotlib via \n\n   pip install matplotlib"
-    ) from e
+    has_mpl = False
 
 
 def _to_tuple(a):
@@ -217,6 +215,12 @@ class MPLDrawer:
 
     def __init__(self, n_layers, n_wires, wire_options=None, figsize=None):
 
+        if not has_mpl:
+            raise ImportError(
+                "Module matplotlib is required for ``MPLDrawer`` class. "
+                "You can install matplotlib via \n\n   pip install matplotlib"
+            )
+
         self.n_layers = n_layers
         self.n_wires = n_wires
 
diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py
index 2dc774e73..83ecbe066 100644
--- a/pennylane/devices/default_qubit_tf.py
+++ b/pennylane/devices/default_qubit_tf.py
@@ -103,9 +103,10 @@ class DefaultQubitTF(DefaultQubit):
     * You must use the ``"tf"`` interface for classical backpropagation, as TensorFlow is
       used as the device backend.
 
-    * Only exact expectation values, variances, and probabilities are differentiable.
-      When instantiating the device with ``analytic=False``, differentiating QNode
-      outputs will result in ``None``.
+    * Only exact expectation values, variances, and probabilities are
+      differentiable. Creation of a backpropagation QNode with finite shots
+      raises an error. If you do try and take a derivative with finite shots on
+      this device, the gradient will be ``None``.
 
 
     If you wish to use a different machine-learning interface, or prefer to calculate quantum
diff --git a/pennylane/devices/default_qubit_torch.py b/pennylane/devices/default_qubit_torch.py
index 711331ba7..a2e3007a0 100644
--- a/pennylane/devices/default_qubit_torch.py
+++ b/pennylane/devices/default_qubit_torch.py
@@ -111,7 +111,7 @@ class DefaultQubitTorch(DefaultQubit):
 
     * Only exact expectation values, variances, and probabilities are differentiable.
       When instantiating the device with ``shots!=None``, differentiating QNode
-      outputs will result in ``None``.
+      outputs will result in an error.
 
     If you wish to use a different machine-learning interface, or prefer to calculate quantum
     gradients using the ``parameter-shift`` or ``finite-diff`` differentiation methods,
diff --git a/pennylane/math/__init__.py b/pennylane/math/__init__.py
index 2b32ab82c..969273142 100644
--- a/pennylane/math/__init__.py
+++ b/pennylane/math/__init__.py
@@ -88,5 +88,6 @@ __all__ = [
     "cov_matrix",
     "marginal_prob",
     "unwrap",
+    "frobenius_inner_product",
     "get_trainable_indices",
 ]
diff --git a/pennylane/ops/qubit/hamiltonian.py b/pennylane/ops/qubit/hamiltonian.py
index 2c85d4734..c81abe29c 100644
--- a/pennylane/ops/qubit/hamiltonian.py
+++ b/pennylane/ops/qubit/hamiltonian.py
@@ -368,8 +368,8 @@ class Hamiltonian(Observable):
 
         >>> H = qml.Hamiltonian([1, 1], [qml.PauliX(0) @ qml.PauliX(1), qml.PauliZ(0)])
         >>> print(H._obs_data())
-        {(1, frozenset({('PauliZ', <Wires = [1]>, ())})),
-        (1, frozenset({('PauliX', <Wires = [1]>, ()), ('PauliX', <Wires = [0]>, ())}))}
+        {(1, frozenset({('PauliX', <Wires = [1]>, ()), ('PauliX', <Wires = [0]>, ())})),
+         (1, frozenset({('PauliZ', <Wires = [0]>, ())}))}
         """
         data = set()
 
diff --git a/pennylane/qnode.py b/pennylane/qnode.py
index d718042e1..072d46096 100644
--- a/pennylane/qnode.py
+++ b/pennylane/qnode.py
@@ -592,7 +592,14 @@ class QNode:
         state_returns = any([m.return_type is State for m in measurement_processes])
 
         # apply the interface (if any)
-        if self.diff_options["method"] != "backprop" and self.interface is not None:
+
+        explicit_backprop = self.diff_options["method"] == "backprop"
+        best_and_passthru = (
+            self.diff_options["method"] == "best"
+            and "passthru_interface" in self.device.capabilities()
+        )
+        backprop_diff = explicit_backprop or best_and_passthru
+        if not backprop_diff and self.interface is not None:
             # pylint: disable=protected-access
             if state_returns and self.interface in ["torch", "tf"]:
                 # The state is complex and we need to indicate this in the to_torch or to_tf
diff --git a/setup.py b/setup.py
index 6cd0d0fdf..e6963555c 100644
--- a/setup.py
+++ b/setup.py
@@ -26,7 +26,8 @@ requirements = [
     "appdirs",
     "semantic_version==2.6",
     "autoray",
-    "cachetools"
+    "cachetools",
+    "pennylane-lightning>=0.18",
 ]
 
 info = {


Test patch:
diff --git a/tests/beta/test_default_tensor_tf.py b/tests/beta/test_default_tensor_tf.py
index adb1ead5b..b108722d8 100644
--- a/tests/beta/test_default_tensor_tf.py
+++ b/tests/beta/test_default_tensor_tf.py
@@ -453,7 +453,7 @@ class TestJacobianIntegration:
 
         dev = qml.device("default.tensor.tf", wires=1, representation=rep)
 
-        @qml.qnode(dev)
+        @qml.qnode(dev, diff_method="parameter-shift")
         def circuit(p):
             qml.RX(3 * p[0], wires=0)
             qml.RY(p[1], wires=0)
@@ -484,7 +484,7 @@ class TestJacobianIntegration:
         p = np.array([x, y, z])
         dev = qml.device("default.tensor.tf", wires=1, representation=rep)
 
-        @qml.qnode(dev)
+        @qml.qnode(dev, diff_method="parameter-shift")
         def circuit(x):
             qml.RX(x[1], wires=0)
             qml.Rot(x[0], x[1], x[2], wires=0)
@@ -567,7 +567,7 @@ class TestInterfaceDeviceIntegration:
 
         dev = qml.device("default.tensor.tf", wires=2, representation=rep)
 
-        @qml.qnode(dev, interface=interface)
+        @qml.qnode(dev, interface=interface, diff_method="parameter-shift")
         def circuit_fn(a, b):
             qml.RX(a, wires=0)
             qml.CRX(b, wires=[0, 1])
diff --git a/tests/devices/test_default_gaussian.py b/tests/devices/test_default_gaussian.py
index 696f121ac..c3f46cf18 100644
--- a/tests/devices/test_default_gaussian.py
+++ b/tests/devices/test_default_gaussian.py
@@ -753,6 +753,25 @@ class TestDefaultGaussianIntegration:
 
         assert np.mean(runs) == pytest.approx(p * np.sqrt(2 * hbar), abs=tol_stochastic)
 
+    def test_shot_list_warns(self):
+        """Test that specifying a list of shots is unsupported for
+        default.gaussian and emits a warning"""
+
+        shots = [10, 10, 10]
+        dev = qml.device("default.gaussian", wires=1, shots=shots)
+
+        @qml.qnode(dev)
+        def circuit():
+            """Test quantum function"""
+            return qml.sample(qml.X(0))
+
+        with pytest.warns(
+            UserWarning,
+            match="Specifying a list of shots is only supported for QubitDevice based devices.",
+        ):
+            circuit()
+        assert dev.shots == sum(shots)
+
     @pytest.mark.parametrize("g, qop", set(DefaultGaussian._operation_map.items()))
     def test_supported_gates(self, g, qop, gaussian_dev):
         """Test that all supported gates work correctly"""
diff --git a/tests/devices/test_default_qubit_jax.py b/tests/devices/test_default_qubit_jax.py
index 8db2f269e..981be9ff5 100644
--- a/tests/devices/test_default_qubit_jax.py
+++ b/tests/devices/test_default_qubit_jax.py
@@ -319,35 +319,34 @@ class TestPassthruIntegration:
         expected = jnp.sin(a)
         assert jnp.allclose(grad, expected, atol=tol, rtol=0)
 
+    @pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, np.pi, 7))
+    def test_CRot_gradient(self, theta, tol):
+        """Tests that the automatic gradient of a arbitrary controlled Euler-angle-parameterized
+        gate is correct."""
+        dev = qml.device("default.qubit.jax", wires=2)
+        a, b, c = np.array([theta, theta ** 3, np.sqrt(2) * theta])
+
+        @qml.qnode(dev, diff_method="backprop", interface="jax")
+        def circuit(a, b, c):
+            qml.QubitStateVector(np.array([1.0, -1.0]) / np.sqrt(2), wires=0)
+            qml.CRot(a, b, c, wires=[0, 1])
+            return qml.expval(qml.PauliX(0))
 
-@pytest.mark.parametrize("theta", np.linspace(-2 * np.pi, np.pi, 7))
-def test_CRot_gradient(theta, tol):
-    """Tests that the automatic gradient of a arbitrary controlled Euler-angle-parameterized
-    gate is correct."""
-    dev = qml.device("default.qubit.jax", wires=2)
-    a, b, c = np.array([theta, theta ** 3, np.sqrt(2) * theta])
-
-    @qml.qnode(dev, diff_method="backprop", interface="jax")
-    def circuit(a, b, c):
-        qml.QubitStateVector(np.array([1.0, -1.0]) / np.sqrt(2), wires=0)
-        qml.CRot(a, b, c, wires=[0, 1])
-        return qml.expval(qml.PauliX(0))
-
-    res = circuit(a, b, c)
-    expected = -np.cos(b / 2) * np.cos(0.5 * (a + c))
-    assert np.allclose(res, expected, atol=tol, rtol=0)
-
-    grad = jax.grad(circuit, argnums=(0, 1, 2))(a, b, c)
-    expected = np.array(
-        [
+        res = circuit(a, b, c)
+        expected = -np.cos(b / 2) * np.cos(0.5 * (a + c))
+        assert np.allclose(res, expected, atol=tol, rtol=0)
+
+        grad = jax.grad(circuit, argnums=(0, 1, 2))(a, b, c)
+        expected = np.array(
             [
-                0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)),
-                0.5 * np.sin(b / 2) * np.cos(0.5 * (a + c)),
-                0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)),
+                [
+                    0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)),
+                    0.5 * np.sin(b / 2) * np.cos(0.5 * (a + c)),
+                    0.5 * np.cos(b / 2) * np.sin(0.5 * (a + c)),
+                ]
             ]
-        ]
-    )
-    assert np.allclose(grad, expected, atol=tol, rtol=0)
+        )
+        assert np.allclose(grad, expected, atol=tol, rtol=0)
 
     def test_prob_differentiability(self, tol):
         """Test that the device probability can be differentiated"""
@@ -459,6 +458,20 @@ def test_CRot_gradient(theta, tol):
         ):
             qml.qnode(dev, diff_method="backprop", interface=interface)(circuit)
 
+    def test_no_jax_interface_applied(self):
+        """Tests that the JAX interface is not applied and no error is raised if qml.probs is used with the Jax
+        interface when diff_method='backprop'
+
+        When the JAX interface is applied, we can only get the expectation value and the variance of a QNode.
+        """
+        dev = qml.device("default.qubit.jax", wires=1, shots=None)
+
+        def circuit():
+            return qml.probs(wires=0)
+
+        qnode = qml.qnode(dev, diff_method="backprop", interface="jax")(circuit)
+        assert jnp.allclose(qnode(), jnp.array([1, 0]))
+
 
 class TestHighLevelIntegration:
     """Tests for integration with higher level components of PennyLane."""
diff --git a/tests/devices/test_lightning_qubit.py b/tests/devices/test_lightning_qubit.py
new file mode 100644
index 000000000..0bcc99dde
--- /dev/null
+++ b/tests/devices/test_lightning_qubit.py
@@ -0,0 +1,38 @@
+# 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.
+"""
+Tests for the accessibility of the Lightning-Qubit device
+"""
+import pennylane as qml
+import numpy as np
+
+
+def test_integration():
+    """Test that the execution of lightning.qubit is possible and agrees with default.qubit"""
+    wires = 2
+    layers = 2
+    dev_l = qml.device("lightning.qubit", wires=wires)
+    dev_d = qml.device("default.qubit", wires=wires)
+
+    def circuit(weights):
+        qml.templates.StronglyEntanglingLayers(weights, wires=range(wires))
+        return qml.expval(qml.PauliZ(0))
+
+    weights = qml.init.strong_ent_layers_normal(layers, wires, seed=1967)
+
+    qn_l = qml.QNode(circuit, dev_l)
+    qn_d = qml.QNode(circuit, dev_d)
+
+    assert np.allclose(qn_l(weights), qn_d(weights))
+    assert np.allclose(qml.grad(qn_l)(weights), qml.grad(qn_d)(weights))
diff --git a/tests/test_device.py b/tests/test_device.py
index 85518f218..4771ad21e 100644
--- a/tests/test_device.py
+++ b/tests/test_device.py
@@ -166,18 +166,41 @@ def mock_device(monkeypatch):
         yield get_device
 
 
-def test_shot_vector_property():
-    dev = qml.device("default.qubit", wires=1, shots=[1, 3, 3, 4, 4, 4, 3])
-    shot_vector = dev.shot_vector
-    assert len(shot_vector) == 4
-    assert shot_vector[0].shots == 1
-    assert shot_vector[0].copies == 1
-    assert shot_vector[1].shots == 3
-    assert shot_vector[1].copies == 2
-    assert shot_vector[2].shots == 4
-    assert shot_vector[2].copies == 3
-    assert shot_vector[3].shots == 3
-    assert shot_vector[3].copies == 1
+class TestShotVectors:
+    """Tests passing shot vectors, their validation, and processing."""
+
+    def test_shot_vector_property(self):
+        dev = qml.device("default.qubit", wires=1, shots=[1, 3, 3, 4, 4, 4, 3])
+        shot_vector = dev.shot_vector
+        assert len(shot_vector) == 4
+        assert shot_vector[0].shots == 1
+        assert shot_vector[0].copies == 1
+        assert shot_vector[1].shots == 3
+        assert shot_vector[1].copies == 2
+        assert shot_vector[2].shots == 4
+        assert shot_vector[2].copies == 3
+        assert shot_vector[3].shots == 3
+        assert shot_vector[3].copies == 1
+
+        assert dev.shots == 22
+
+    def test_process_shot_sequence(self):
+        """Tests that the helper `_process_shot_sequence` works as expected."""
+        shot_list = [1, 1, 3]
+        total_shots, shot_vector = qml._device._process_shot_sequence(shot_list)
+
+        assert total_shots == 5
+        assert isinstance(total_shots, int)
+
+        assert len(shot_vector) == 2
+
+        for tup in shot_vector:
+            assert isinstance(tup, qml._device.ShotTuple)
+
+        assert shot_vector[0].shots == 1
+        assert shot_vector[0].copies == 2
+        assert shot_vector[1].shots == 3
+        assert shot_vector[1].copies == 1
 
 
 class TestDeviceSupportedLogic: