Repository: PennyLaneAI/pennylane
Pull request #1080 (2021-02-11 06:12:29)

Revert recent improvement to qml.grad until closer to the next release
**Context:** PR #1067 introduced an improvement to `qml.grad`, which removed the need to index into scalar gradients. However, it was determined that we did not want the behaviour of 0.15.0-dev to diverge from 0.14.0 until further in the release cycle.

**Description of the Change:** Reverts the change of #1067

**Benefits:** The same code for extracting the gradient of scalar functions will work on both 0.15.0-dev and 0.14.0

**Possible Drawbacks:** n/a

**Related GitHub Issues:** n/a


codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=h1) Report
> Merging [#1080](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=desc) (f7fcb29) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/f498bc2f256babc0c9db70549f697a1d19296c5f?el=desc) (f498bc2) will **decrease** coverage by `0.00%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master    #1080      +/-   ##
==========================================
- Coverage   97.72%   97.72%   -0.01%     
==========================================
  Files         154      154              
  Lines       11658    11652       -6     
==========================================
- Hits        11393    11387       -6     
  Misses        265      265              
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pennylane/\_grad.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080/diff?src=pr&el=tree#diff-cGVubnlsYW5lL19ncmFkLnB5) | `100.00% <ø> (ø)` | |
| [pennylane/optimize/gradient\_descent.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080/diff?src=pr&el=tree#diff-cGVubnlsYW5lL29wdGltaXplL2dyYWRpZW50X2Rlc2NlbnQucHk=) | `100.00% <ø> (ø)` | |
| [pennylane/optimize/nesterov\_momentum.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080/diff?src=pr&el=tree#diff-cGVubnlsYW5lL29wdGltaXplL25lc3Rlcm92X21vbWVudHVtLnB5) | `100.00% <ø> (ø)` | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=footer). Last update [f498bc2...f7fcb29](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1080?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 768953b4a..23cdff31c 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -95,11 +95,6 @@
   using backpropagation on `default.qubit`.
   [(#1072)](https://github.com/PennyLaneAI/pennylane/pull/1072)
 
-* If only one argument to the function `qml.grad` has the `requires_grad` attribute
-  set to True, then the returned gradient will be a NumPy array, rather than a
-  tuple of length 1.
-  [(#1067)](https://github.com/PennyLaneAI/pennylane/pull/1067)
-
 <h3>Documentation</h3>
 
 <h3>Contributors</h3>
diff --git a/pennylane/_grad.py b/pennylane/_grad.py
index 7f6a4b03f..e0a8de87a 100644
--- a/pennylane/_grad.py
+++ b/pennylane/_grad.py
@@ -85,9 +85,6 @@ class grad:
             if getattr(arg, "requires_grad", True):
                 argnum.append(idx)
 
-        if len(argnum) == 1:
-            argnum = argnum[0]
-
         return self._grad_with_forward(
             self._fun,
             argnum=argnum,
diff --git a/pennylane/optimize/gradient_descent.py b/pennylane/optimize/gradient_descent.py
index 17584f6f9..a175dbeba 100644
--- a/pennylane/optimize/gradient_descent.py
+++ b/pennylane/optimize/gradient_descent.py
@@ -127,9 +127,6 @@ class GradientDescentOptimizer:
         grad = g(*args, **kwargs)
         forward = getattr(g, "forward", None)
 
-        if len(args) == 1:
-            grad = (grad,)
-
         return grad, forward
 
     def apply_grad(self, grad, args):
diff --git a/pennylane/optimize/nesterov_momentum.py b/pennylane/optimize/nesterov_momentum.py
index a561afee2..08c1070ee 100644
--- a/pennylane/optimize/nesterov_momentum.py
+++ b/pennylane/optimize/nesterov_momentum.py
@@ -82,7 +82,4 @@ class NesterovMomentumOptimizer(MomentumOptimizer):
         grad = g(*shifted_args, **kwargs)
         forward = getattr(g, "forward", None)
 
-        if len(args) == 1:
-            grad = (grad,)
-
         return grad, forward


Test patch:
diff --git a/tests/devices/test_default_qubit_autograd.py b/tests/devices/test_default_qubit_autograd.py
index c9b063d0b..7f89b274e 100644
--- a/tests/devices/test_default_qubit_autograd.py
+++ b/tests/devices/test_default_qubit_autograd.py
@@ -352,7 +352,7 @@ class TestHighLevelIntegration:
             qml.init.strong_ent_layers_normal(n_wires=2, n_layers=2), requires_grad=True
         )
 
-        grad = qml.grad(circuit)(weights)
+        grad = qml.grad(circuit)(weights)[0]
         assert grad.shape == weights.shape
 
     def test_qnode_collection_integration(self):
@@ -374,7 +374,7 @@ class TestHighLevelIntegration:
         def cost(weights):
             return np.sum(qnodes(weights))
 
-        grad = qml.grad(cost)(weights)
+        grad = qml.grad(cost)(weights)[0]
         assert grad.shape == weights.shape
 
 class TestOps:
diff --git a/tests/interfaces/test_autograd.py b/tests/interfaces/test_autograd.py
index 782224e2e..25111b313 100644
--- a/tests/interfaces/test_autograd.py
+++ b/tests/interfaces/test_autograd.py
@@ -541,7 +541,8 @@ class TestParameterHandlingIntegration:
 
         # we do not check for correctness, just that the output
         # is the correct shape
-        assert res.shape == weights.shape
+        assert len(res) == 1
+        assert res[0].shape == weights.shape
 
         # check that the first arg was marked as non-differentiable
         assert circuit.get_trainable_args() == {0}
@@ -586,7 +587,8 @@ class TestParameterHandlingIntegration:
 
         # we do not check for correctness, just that the output
         # is the correct shape
-        assert res.shape == weights.shape
+        assert len(res) == 1
+        assert res[0].shape == weights.shape
 
         # check that the second arg was marked as non-differentiable
         assert circuit.get_trainable_args() == {1}
@@ -631,7 +633,8 @@ class TestParameterHandlingIntegration:
 
         # we do not check for correctness, just that the output
         # is the correct shape
-        assert res.shape == weights.shape
+        assert len(res) == 1
+        assert res[0].shape == weights.shape
 
         # check that the last arg was marked as non-differentiable
         assert circuit.get_trainable_args() == {2}
@@ -745,7 +748,7 @@ class TestParameterHandlingIntegration:
         grad_fn = qml.grad(cost)
         res = grad_fn(weights)
 
-        assert len(res) == 2
+        assert len(res[0]) == 2
 
     def test_gradient_value(self, tol):
         """Test that the returned gradient value for a qubit QNode is correct,
diff --git a/tests/math/test_autograd_box.py b/tests/math/test_autograd_box.py
index 249f5a92c..f079a8086 100644
--- a/tests/math/test_autograd_box.py
+++ b/tests/math/test_autograd_box.py
@@ -175,7 +175,7 @@ def test_autodifferentiation():
     cost_fn = lambda a: (qml.math.TensorBox(a).T() ** 2).unbox()[0, 1]
     grad_fn = qml.grad(cost_fn)
 
-    res = grad_fn(x)
+    res = grad_fn(x)[0]
     expected = np.array([[0.0, 0.0, 0.0], [8.0, 0.0, 0.0]])
     assert np.all(res == expected)
 
diff --git a/tests/math/test_functions.py b/tests/math/test_functions.py
index b3e55a791..8480089b1 100644
--- a/tests/math/test_functions.py
+++ b/tests/math/test_functions.py
@@ -975,7 +975,7 @@ class TestScatterElementAdd:
         assert isinstance(res, np.ndarray)
         assert fn.allclose(res, onp.array([[1.0, 1.0, 1.0], [1.0, 1.0, 1.3136]]))
 
-        grad = qml.grad(lambda weights: cost(weights)[1, 2])([x, y])
+        grad = qml.grad(lambda weights: cost(weights)[1, 2])([x, y])[0]
         assert fn.allclose(grad[0], onp.array([[0, 0, 0], [0, 0, 1.]]))
         assert fn.allclose(grad[1], 2 * y)
 
diff --git a/tests/tape/interfaces/test_qnode_autograd.py b/tests/tape/interfaces/test_qnode_autograd.py
index 2e20b1105..f7e70a15d 100644
--- a/tests/tape/interfaces/test_qnode_autograd.py
+++ b/tests/tape/interfaces/test_qnode_autograd.py
@@ -118,8 +118,9 @@ class TestQNode:
 
         # gradients should work
         grad = qml.grad(circuit)(a)
-        assert isinstance(grad, float)
-        assert grad.shape == tuple()
+        assert len(grad) == 1
+        assert isinstance(grad[0], np.ndarray)
+        assert grad[0].shape == tuple()
 
     def test_interface_swap(self, dev_name, diff_method, tol):
         """Test that the autograd interface can be applied to a QNode
@@ -151,9 +152,10 @@ class TestQNode:
 
         res = circuit(a)
         grad = qml.grad(circuit)(a)
+        assert len(grad) == 1
 
         assert np.allclose(res, res_tf, atol=tol, rtol=0)
-        assert np.allclose(grad, grad_tf, atol=tol, rtol=0)
+        assert np.allclose(grad[0], grad_tf, atol=tol, rtol=0)
 
     def test_jacobian(self, dev_name, diff_method, mocker, tol):
         """Test jacobian calculation"""
@@ -592,127 +594,6 @@ class TestQNode:
         assert res.shape == (2, 10)
         assert isinstance(res, np.ndarray)
 
-    def test_gradient_non_differentiable_exception(self, dev_name, diff_method):
-        """Test that an exception is raised if non-differentiable data is
-        differentiated"""
-        dev = qml.device(dev_name, wires=2)
-
-        @qml.qnode(dev, interface="autograd", diff_method=diff_method)
-        def circuit(data1):
-            qml.templates.AmplitudeEmbedding(data1, wires=[0, 1])
-            return qml.expval(qml.PauliZ(0))
-
-        grad_fn = qml.grad(circuit, argnum=0)
-        data1 = np.array([0, 1, 1, 0], requires_grad=False) / np.sqrt(2)
-
-        with pytest.raises(qml.numpy.NonDifferentiableError, match="is non-differentiable"):
-            grad_fn(data1)
-
-    def test_chained_qnodes(self, dev_name, diff_method):
-        """Test that the gradient of chained QNodes works without error"""
-        dev = qml.device(dev_name, wires=2)
-
-        @qml.qnode(dev, interface="autograd", diff_method=diff_method)
-        def circuit1(weights):
-            qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1])
-            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1))
-
-        @qml.qnode(dev, interface="autograd", diff_method=diff_method)
-        def circuit2(data, weights):
-            qml.templates.AngleEmbedding(data, wires=[0, 1])
-            qml.templates.StronglyEntanglingLayers(weights, wires=[0, 1])
-            return qml.expval(qml.PauliX(0))
-
-        def cost(weights):
-            w1, w2 = weights
-            c1 = circuit1(w1)
-            c2 = circuit2(c1, w2)
-            return np.sum(c2) ** 2
-
-        w1 = qml.init.strong_ent_layers_normal(n_wires=2, n_layers=3)
-        w2 = qml.init.strong_ent_layers_normal(n_wires=2, n_layers=4)
-
-        weights = [w1, w2]
-
-        grad_fn = qml.grad(cost)
-        res = grad_fn(weights)
-
-        assert len(res) == 2
-
-    def test_chained_gradient_value(self, dev_name, diff_method, tol):
-        """Test that the returned gradient value for two chained qubit QNodes
-        is correct."""
-        dev1 = qml.device(dev_name, wires=3)
-
-        @qml.qnode(dev1, diff_method=diff_method)
-        def circuit1(a, b, c):
-            qml.RX(a, wires=0)
-            qml.RX(b, wires=1)
-            qml.RX(c, wires=2)
-            qml.CNOT(wires=[0, 1])
-            qml.CNOT(wires=[1, 2])
-            return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliY(2))
-
-        dev2 = qml.device("default.qubit", wires=2)
-
-        @qml.qnode(dev2, diff_method=diff_method)
-        def circuit2(data, weights):
-            qml.RX(data[0], wires=0)
-            qml.RX(data[1], wires=1)
-            qml.CNOT(wires=[0, 1])
-            qml.RZ(weights[0], wires=0)
-            qml.RZ(weights[1], wires=1)
-            qml.CNOT(wires=[0, 1])
-            return qml.expval(qml.PauliX(0) @ qml.PauliY(1))
-
-        def cost(a, b, c, weights):
-            return circuit2(circuit1(a, b, c), weights)
-
-        grad_fn = qml.grad(cost)
-
-        # Set the first parameter of circuit1 as non-differentiable.
-        a = np.array(0.4, requires_grad=False)
-
-        # The remaining free parameters are all differentiable.
-        b = 0.5
-        c = 0.1
-        weights = np.array([0.2, 0.3])
-
-        res = grad_fn(a, b, c, weights)
-
-        # Output should have shape [dcost/db, dcost/dc, dcost/dw],
-        # where b,c are scalars, and w is a vector of length 2.
-        assert len(res) == 3
-        assert res[0].shape == tuple() # scalar
-        assert res[1].shape == tuple() # scalar
-        assert res[2].shape == (2,)    # vector
-
-        cacbsc = np.cos(a)*np.cos(b)*np.sin(c)
-
-        expected = np.array([
-            # analytic expression for dcost/db
-            -np.cos(a)*np.sin(b)*np.sin(c)*np.cos(cacbsc)*np.sin(weights[0])*np.sin(np.cos(a)),
-            # analytic expression for dcost/dc
-            np.cos(a)*np.cos(b)*np.cos(c)*np.cos(cacbsc)*np.sin(weights[0])*np.sin(np.cos(a)),
-            # analytic expression for dcost/dw[0]
-            np.sin(cacbsc)*np.cos(weights[0])*np.sin(np.cos(a)),
-            # analytic expression for dcost/dw[1]
-            0
-        ])
-
-        # np.hstack 'flattens' the ragged gradient array allowing it
-        # to be compared with the expected result
-        assert np.allclose(np.hstack(res), expected, atol=tol, rtol=0)
-
-        if diff_method != "backprop":
-            # Check that the gradient was computed
-            # for all parameters in circuit2
-            assert circuit2.qtape.trainable_params == {0, 1, 2, 3}
-
-            # Check that the parameter-shift rule was not applied
-            # to the first parameter of circuit1.
-            assert circuit1.qtape.trainable_params == {1, 2}
-
 
 def qtransform(qnode, a, framework=np):
     """Transforms every RY(y) gate in a circuit to RX(-a*cos(y))"""
diff --git a/tests/test_classical_gradients.py b/tests/test_classical_gradients.py
index 69dda25bc..fc3aa44be 100644
--- a/tests/test_classical_gradients.py
+++ b/tests/test_classical_gradients.py
@@ -306,7 +306,7 @@ class TestGrad:
         res = grad_fn(x, y)
         expected = np.array([np.cos(x) * np.cos(y) + y ** 2])
         assert np.allclose(res, expected, atol=tol, rtol=0)
-        assert spy.call_args_list[0][1]["argnum"] == 0
+        assert spy.call_args_list[0][1]["argnum"] == [0]
 
 
 class TestJacobian: