Repository: PennyLaneAI/pennylane
Pull request #1834 (2021-11-01 21:56:01)

Update `QNGOptimizer` to handle deprecations
**Context**

The `QNGOptimizer` raises deprecation warnings because (see [related demo](http://pennylane.ai-dev.s3-website-us-east-1.amazonaws.com/qml/demos/tutorial_quantum_natural_gradient.html#quantum-natural-gradient-optimization)):

* inputs have to explicitly specify requires_grad=True;
* the keyword argument diag_approx is deprecated.

**Changes**

* Switches to using PennyLane NumPy for the optimizer
* Switches how the optimizer uses `qml.metric_tensor`
* Adds a deprecation warning when `diag_approx` is specified with `QNGOptimizer`

github-actions[bot]: Hello. You may have forgotten to update the changelog!
Please edit [`doc/releases/changelog-dev.md`](/PennyLaneAI/pennylane/blob/master/doc/releases/changelog-dev.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.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1834?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> :exclamation: No coverage uploaded for pull request base (`v0.19.0-rc0@b9fc6d4`). [Click here to learn what that means](https://docs.codecov.io/docs/error-reference?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#section-missing-base-commit).
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1834/graphs/tree.svg?width=650&height=150&src=pr&token=9S3YzlUgGk&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1834?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@              Coverage Diff               @@
##             v0.19.0-rc0    #1834   +/-   ##
==============================================
  Coverage               ?   98.96%           
==============================================
  Files                  ?      218           
  Lines                  ?    16370           
  Branches               ?        0           
==============================================
  Hits                   ?    16200           
  Misses                 ?      170           
  Partials               ?        0           
```



------

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

antalszava: Hi @josh146 thank you :slightly_smiling_face: The warnings related to the `requires_grad=True` casting seem to arise if we step the optimizer once more:

```python
import pennylane as qml
from pennylane import numpy as np

dev = qml.device("default.qubit", wires=1)

@qml.qnode(dev)
def circuit(params):
    qml.RX(params[0], wires=0)
    qml.RY(params[1], wires=0)
    return qml.expval(qml.PauliX(0) + qml.PauliZ(0))

theta = np.array([0.011, 0.012], requires_grad=True)

opt = qml.QNGOptimizer(0.01)
new_params = opt.step(circuit, theta)

res = opt.metric_tensor
exp = np.diag([0.25, (np.cos(theta[0]) ** 2) / 4])
assert np.allclose(res, exp)

theta = new_params
new_params = opt.step(circuit, theta)
```

Patch:
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 1a53dcad8..991c08717 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -625,6 +625,10 @@
 
 <h3>Improvements</h3>
 
+* Updated the `qml.QNGOptimizer.step_and_cost` method to avoid the use of
+  deprecated functionality.
+  [(#1834)](https://github.com/PennyLaneAI/pennylane/pull/1834)
+
 * The default for an `Operation`'s `control_wires` attribute is now an empty `Wires`
   object instead of the attribute raising a `NonImplementedError`.
   [(#1821)](https://github.com/PennyLaneAI/pennylane/pull/1821)
@@ -891,12 +895,6 @@
 
   If `hybrid=False`, the changed expansion rule might lead to a changed output.
 
-* The `qml.metric_tensor` keyword argument `diag_approx` is deprecated.
-  Approximations can be controlled with the more fine-grained `approx`
-  keyword argument, with `approx="block-diag"` (the default) reproducing
-  the old behaviour.
-  [(#1721)](https://github.com/PennyLaneAI/pennylane/pull/1721)
-
 * The `default.qubit.torch` device automatically determines if computations
   should be run on a CPU or a GPU and doesn't take a `torch_device` argument
   anymore.
@@ -923,6 +921,14 @@
 
 <h3>Deprecations</h3>
 
+* The `qml.metric_tensor` and `qml.QNGOptimizer` keyword argument `diag_approx`
+  is deprecated.
+  Approximations can be controlled with the more fine-grained `approx` keyword
+  argument, with `approx="block-diag"` (the default) reproducing the old
+  behaviour.
+  [(#1721)](https://github.com/PennyLaneAI/pennylane/pull/1721)
+  [(#1834)](https://github.com/PennyLaneAI/pennylane/pull/1834)
+
 * The `template` decorator is now deprecated with a warning message and will be removed
   in release `v0.20.0`. It has been removed from different PennyLane functions.
   [(#1794)](https://github.com/PennyLaneAI/pennylane/pull/1794)
diff --git a/pennylane/optimize/qng.py b/pennylane/optimize/qng.py
index 17a46e309..5f9f85fd0 100644
--- a/pennylane/optimize/qng.py
+++ b/pennylane/optimize/qng.py
@@ -14,8 +14,9 @@
 """Quantum natural gradient optimizer"""
 # pylint: disable=too-many-branches
 # pylint: disable=too-many-arguments
+import warnings
 
-import numpy as np
+from pennylane import numpy as np
 
 import pennylane as qml
 from pennylane.utils import _flatten, unflatten
@@ -150,9 +151,23 @@ class QNGOptimizer(GradientDescentOptimizer):
             to be applied at each optimization step
     """
 
-    def __init__(self, stepsize=0.01, diag_approx=False, lam=0):
+    def __init__(self, stepsize=0.01, approx="block-diag", diag_approx=None, lam=0):
         super().__init__(stepsize)
-        self.diag_approx = diag_approx
+
+        approx_set = False
+        if diag_approx is not None:
+
+            warnings.warn(
+                "The keyword argument diag_approx is deprecated. Please use approx='diag' instead.",
+                UserWarning,
+            )
+            if diag_approx:
+                self.approx = "diag"
+                approx_set = True
+
+        if not approx_set:
+            self.approx = approx
+
         self.metric_tensor = None
         self.lam = lam
 
@@ -192,13 +207,14 @@ class QNGOptimizer(GradientDescentOptimizer):
 
         if recompute_tensor or self.metric_tensor is None:
             if metric_tensor_fn is None:
-                metric_tensor_fn = qml.metric_tensor(qnode, diag_approx=self.diag_approx)
+
+                metric_tensor_fn = qml.metric_tensor(qnode, approx=self.approx)
 
             self.metric_tensor = metric_tensor_fn(*args, **kwargs)
             self.metric_tensor += self.lam * np.identity(self.metric_tensor.shape[0])
 
         g, forward = self.compute_grad(qnode, args, kwargs, grad_fn=grad_fn)
-        new_args = self.apply_grad(g, args)
+        new_args = np.array(self.apply_grad(g, args), requires_grad=True)
 
         if forward is None:
             forward = qnode(*args, **kwargs)


Test patch:
diff --git a/tests/optimize/test_qng.py b/tests/optimize/test_qng.py
index 345e6cdd6..253cb7866 100644
--- a/tests/optimize/test_qng.py
+++ b/tests/optimize/test_qng.py
@@ -1,4 +1,4 @@
-# Copyright 2018-2020 Xanadu Quantum Technologies Inc.
+# 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.
@@ -14,6 +14,7 @@
 """Tests for the QNG optimizer"""
 import pytest
 import scipy as sp
+import warnings
 
 import pennylane as qml
 from pennylane import numpy as np
@@ -228,7 +229,7 @@ class TestOptimize:
         # check final cost
         assert np.allclose(cost_fn(theta), -1.41421356, atol=tol, rtol=0)
 
-    def test_single_qubit_vqe_using_vqecost(self, tol):
+    def test_single_qubit_vqe_using_vqecost(self, tol, recwarn):
         """Test single-qubit VQE using ExpvalCost
         has the correct QNG value every step, the correct parameter updates,
         and correct cost after 200 steps"""
@@ -252,7 +253,7 @@ class TestOptimize:
             return np.array([da, db])
 
         eta = 0.01
-        init_params = np.array([0.011, 0.012])
+        init_params = np.array([0.011, 0.012], requires_grad=True)
         num_steps = 200
 
         opt = qml.QNGOptimizer(eta)
@@ -275,3 +276,65 @@ class TestOptimize:
 
         # check final cost
         assert np.allclose(cost_fn(theta), -1.41421356, atol=tol, rtol=0)
+        assert len(recwarn) == 0
+
+    def test_single_qubit_vqe_using_expval_h_multiple_input_params(self, tol, recwarn):
+        """Test single-qubit VQE by returning qml.expval(H) in the QNode and
+        check for the correct QNG value every step, the correct parameter updates, and
+        correct cost after 200 steps"""
+        dev = qml.device("default.qubit", wires=1)
+        coeffs = [1, 1]
+        obs_list = [qml.PauliX(0), qml.PauliZ(0)]
+
+        H = qml.Hamiltonian(coeffs=coeffs, observables=obs_list)
+
+        @qml.qnode(dev)
+        def circuit(x, y, wires=0):
+            qml.RX(x, wires=wires)
+            qml.RY(y, wires=wires)
+            return qml.expval(H)
+
+        eta = 0.01
+        x = np.array(0.011, requires_grad=True)
+        y = np.array(0.022, requires_grad=True)
+
+        def gradient(params):
+            """Returns the gradient"""
+            da = -np.sin(params[0]) * (np.cos(params[1]) + np.sin(params[1]))
+            db = np.cos(params[0]) * (np.cos(params[1]) - np.sin(params[1]))
+            return np.array([da, db])
+
+        eta = 0.01
+        num_steps = 200
+
+        opt = qml.QNGOptimizer(eta)
+
+        # optimization for 200 steps total
+        for t in range(num_steps):
+            theta = np.array([x, y])
+            x, y = opt.step(circuit, x, y)
+
+            # check metric tensor
+            res = opt.metric_tensor
+            exp = np.diag([0.25, (np.cos(x) ** 2) / 4])
+            assert np.allclose(res, exp, atol=0.00001, rtol=0)
+
+            # check parameter update
+            theta_new = np.array([x, y])
+            dtheta = eta * sp.linalg.pinvh(exp) @ gradient(theta)
+            assert np.allclose(dtheta, theta - theta_new, atol=0.000001, rtol=0)
+
+        # check final cost
+        assert np.allclose(circuit(x, y), -1.41421356, atol=tol, rtol=0)
+        assert len(recwarn) == 0
+
+    @pytest.mark.parametrize(
+        "diag_approx, approx_expected", [(True, "diag"), (False, "block-diag")]
+    )
+    def test_deprecate_diag_approx(self, diag_approx, approx_expected):
+        """Test that using the diag_approx argument raises a warning due to
+        deprecation."""
+        with pytest.warns(UserWarning, match="keyword argument diag_approx is deprecated"):
+            opt = qml.QNGOptimizer(0.1, diag_approx=True)
+
+        assert opt.approx == "diag"