Repository: PennyLaneAI/pennylane
Pull request #1541 (2021-08-17 03:20:47)

[Code Together] Add support for qml.ResetError in default.mixed
**Context:** Add support for qml.ResetError for default.mixed device, and a test of the gradient computation

**Description of the Change:** Added ResetError to the list of supported operations, and a test function for gradient check 

**Benefits:** Support single-qubit reset error channel that simulates the reset of qubits to 0 or 1 given a probability.

**Possible Drawbacks:**

**Related GitHub Issues:** [1530](https://github.com/PennyLaneAI/pennylane/issues/1530)


codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1541](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (4c92793) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/3397b49b678df2fd80f683986e7907347d804ecf?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (3397b49) will **increase** coverage by `0.00%`.
> The diff coverage is `n/a`.

[![Impacted file tree graph](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541/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/1541?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)

```diff
@@           Coverage Diff           @@
##           master    #1541   +/-   ##
=======================================
  Coverage   98.23%   98.23%           
=======================================
  Files         187      187           
  Lines       13370    13392   +22     
=======================================
+ Hits        13134    13156   +22     
  Misses        236      236           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/devices/default\_mixed.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9taXhlZC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/transforms/adjoint.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvYWRqb2ludC5weQ==) | `100.00% <0.00%> (ø)` | |
| [pennylane/transforms/control.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL3RyYW5zZm9ybXMvY29udHJvbC5weQ==) | `100.00% <0.00%> (ø)` | |
| [qchem/pennylane\_qchem/qchem/structure.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cWNoZW0vcGVubnlsYW5lX3FjaGVtL3FjaGVtL3N0cnVjdHVyZS5weQ==) | `97.26% <0.00%> (+0.02%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541?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/1541?src=pr&el=footer&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Last update [3397b49...4c92793](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1541?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).

wongwsvincent: Hi @antalszava , I have made the changes as requested. This PR is ready for another review.
antalszava: Hi @wongwsvincent, thanks! It seems that adding tests for [applying the channel](https://github.com/PennyLaneAI/pennylane/blob/0088704258018171ad955d83238fb08ef94ed88d/tests/devices/test_default_mixed.py#L266) using `default.mixed` would still remain.

Also, there seems to be a conflict in the changelog. This can be resolved by pulling master (`git checkout master` followed by `git pull`), switching to the feature branch (`git checkout add_reseterror_support`) and then merging `master` (`git merge master`). The culprit seems to be the changelog file. We'd like to have all the entries kept there, for some reason `git` could not resolve the addition there well.
wongwsvincent: Hi @antalszava , thanks for reviewing and catching the missing tests! I have added a few tests for ```qml.ResetError``` in ```tests/devices/test_default_mixed.py```.  I have also fixed the conflicts in CHANGELOG.md. 

All checks look good without complaints. It should be ready for review.
wongwsvincent: > Hi @wongwsvincent, the additional tests are looking awesome, thank you! 💯 There seem to be small things to be adjusted. Overall, however, this is near approval.

Thanks for the suggestions @antalszava ! All the issues are addressed. Ready for a (hopefully) final review.

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 063de1709..6e470dd75 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -136,6 +136,8 @@
 and requirements-ci.txt (unpinned). This latter would be used by the CI.
   [(#1535)](https://github.com/PennyLaneAI/pennylane/pull/1535)
 
+* The `qml.ResetError` is now supported for `default.mixed` device. 
+  [(#1541)](https://github.com/PennyLaneAI/pennylane/pull/1541)
 
 <h3>Breaking changes</h3>
 
@@ -155,7 +157,8 @@ and requirements-ci.txt (unpinned). This latter would be used by the CI.
 
 This release contains contributions from (in alphabetical order):
 
-Thomas Bromley, Josh Izaac, Prateek Jain, Johannes Jakob Meyer, Akash Narayanan, Maria Schuld, Ingrid Strandberg.
+Thomas Bromley, Josh Izaac, Prateek Jain, Johannes Jakob Meyer, Akash Narayanan, Maria Schuld,
+Ingrid Strandberg, Vincent Wong.
 
 # Release 0.17.0 (current release)
 
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index 523e50c7f..cc21e1283 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -136,6 +136,7 @@ Noisy channels
     ~pennylane.DepolarizingChannel
     ~pennylane.BitFlip
     ~pennylane.PhaseFlip
+    ~pennylane.ResetError
     ~pennylane.QubitChannel
 
 :html:`</div>`
diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py
index bda839806..aca85c10e 100644
--- a/pennylane/devices/default_mixed.py
+++ b/pennylane/devices/default_mixed.py
@@ -91,6 +91,7 @@ class DefaultMixed(QubitDevice):
         "DepolarizingChannel",
         "BitFlip",
         "PhaseFlip",
+        "ResetError",
         "QubitChannel",
         "QFT",
         "SingleExcitation",


Test patch:
diff --git a/tests/devices/test_default_mixed.py b/tests/devices/test_default_mixed.py
index a0de1eeb2..1fc43d484 100644
--- a/tests/devices/test_default_mixed.py
+++ b/tests/devices/test_default_mixed.py
@@ -19,7 +19,16 @@ import pytest
 import pennylane as qml
 from pennylane import QubitStateVector, BasisState, DeviceError
 from pennylane.devices import DefaultMixed
-from pennylane.ops import PauliZ, CZ, PauliX, Hadamard, CNOT, AmplitudeDamping, DepolarizingChannel
+from pennylane.ops import (
+    PauliZ,
+    CZ,
+    PauliX,
+    Hadamard,
+    CNOT,
+    AmplitudeDamping,
+    DepolarizingChannel,
+    ResetError,
+)
 from pennylane.wires import Wires
 
 import numpy as np
@@ -99,11 +108,18 @@ class TestState:
 
         assert np.allclose(dev.state, current_state, atol=tol, rtol=0)
 
-    @pytest.mark.parametrize("op", [AmplitudeDamping, DepolarizingChannel])
+    @pytest.mark.parametrize(
+        "op",
+        [
+            AmplitudeDamping(0.5, wires=0),
+            DepolarizingChannel(0.5, wires=0),
+            ResetError(0.1, 0.5, wires=0),
+        ],
+    )
     def test_state_after_channel(self, nr_wires, op, tol):
         """Tests that state is correctly retrieved after applying a channel on the first wires"""
         dev = qml.device("default.mixed", wires=nr_wires)
-        dev.apply([op(0.5, wires=0)])
+        dev.apply([op])
         current_state = np.reshape(dev._state, (2 ** nr_wires, 2 ** nr_wires))
 
         assert np.allclose(dev.state, current_state, atol=tol, rtol=0)
@@ -139,12 +155,19 @@ class TestReset:
 
         assert np.allclose(dev._state, dev._create_basis_state(0), atol=tol, rtol=0)
 
-    @pytest.mark.parametrize("op", [AmplitudeDamping, DepolarizingChannel])
+    @pytest.mark.parametrize(
+        "op",
+        [
+            AmplitudeDamping(0.5, wires=[0]),
+            DepolarizingChannel(0.5, wires=[0]),
+            ResetError(0.1, 0.5, wires=[0]),
+        ],
+    )
     def test_reset_after_channel(self, nr_wires, op, tol):
         """Tests that state is correctly reset after applying a channel on the first
         wires"""
         dev = qml.device("default.mixed", wires=nr_wires)
-        dev.apply([op(0.5, wires=[0])])
+        dev.apply([op])
         dev.reset()
 
         assert np.allclose(dev._state, dev._create_basis_state(0), atol=tol, rtol=0)
@@ -242,17 +265,32 @@ class TestKrausOps:
         assert np.allclose(dev._get_kraus(ops[0]), ops[1], atol=tol, rtol=0)
 
     p = 0.5
-    K0 = np.sqrt(1 - p) * np.eye(2)
-    K1 = np.sqrt(p / 3) * np.array([[0, 1], [1, 0]])
-    K2 = np.sqrt(p / 3) * np.array([[0, -1j], [1j, 0]])
-    K3 = np.sqrt(p / 3) * np.array([[1, 0], [0, -1]])
+    p_0, p_1 = 0.1, 0.5
 
     channel_ops = [
         (
             AmplitudeDamping(p, wires=0),
             [np.diag([1, np.sqrt(1 - p)]), np.sqrt(p) * np.array([[0, 1], [0, 0]])],
         ),
-        (DepolarizingChannel(p, wires=0), [K0, K1, K2, K3]),
+        (
+            DepolarizingChannel(p, wires=0),
+            [
+                np.sqrt(1 - p) * np.eye(2),
+                np.sqrt(p / 3) * np.array([[0, 1], [1, 0]]),
+                np.sqrt(p / 3) * np.array([[0, -1j], [1j, 0]]),
+                np.sqrt(p / 3) * np.array([[1, 0], [0, -1]]),
+            ],
+        ),
+        (
+            ResetError(p_0, p_1, wires=0),
+            [
+                np.sqrt(1 - p_0 - p_1) * np.eye(2),
+                np.sqrt(p_0) * np.array([[1, 0], [0, 0]]),
+                np.sqrt(p_0) * np.array([[0, 1], [0, 0]]),
+                np.sqrt(p_1) * np.array([[0, 0], [1, 0]]),
+                np.sqrt(p_1) * np.array([[0, 0], [0, 1]]),
+            ],
+        ),
     ]
 
     @pytest.mark.parametrize("ops", channel_ops)
@@ -276,6 +314,11 @@ class TestApplyChannel:
             DepolarizingChannel(0.5, wires=0),
             np.array([[2 / 3 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 1 / 3 + 0.0j]]),
         ],
+        [
+            1,
+            ResetError(0.1, 0.5, wires=0),
+            np.array([[0.5 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.5 + 0.0j]]),
+        ],
     ]
 
     @pytest.mark.parametrize("x", x_apply_channel_init)
@@ -289,7 +332,6 @@ class TestApplyChannel:
         if op == CNOT:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
@@ -308,6 +350,11 @@ class TestApplyChannel:
             DepolarizingChannel(0.5, wires=0),
             np.array([[0.5 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.5 + 0.0j]]),
         ],
+        [
+            1,
+            ResetError(0.1, 0.5, wires=0),
+            np.array([[0.3 + 0.0j, 0.0 + 0.0j], [0.0 + 0.0j, 0.7 + 0.0j]]),
+        ],
     ]
 
     @pytest.mark.parametrize("x", x_apply_channel_mixed)
@@ -323,7 +370,6 @@ class TestApplyChannel:
         if op == CNOT:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
@@ -353,6 +399,11 @@ class TestApplyChannel:
             DepolarizingChannel(0.5, wires=0),
             np.array([[0.5 + 0.0j, -1 / 6 + 0.0j], [-1 / 6 + 0.0j, 0.5 + 0.0j]]),
         ],
+        [
+            1,
+            ResetError(0.1, 0.5, wires=0),
+            np.array([[0.3 + 0.0j, -0.2 + 0.0j], [-0.2 + 0.0j, 0.7 + 0.0j]]),
+        ],
     ]
 
     @pytest.mark.parametrize("x", x_apply_channel_root)
@@ -368,7 +419,6 @@ class TestApplyChannel:
         if op == CNOT:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
@@ -390,7 +440,6 @@ class TestApplyDiagonal:
         if op == CZ:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
@@ -410,7 +459,6 @@ class TestApplyDiagonal:
         if op == CZ:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
@@ -444,7 +492,6 @@ class TestApplyDiagonal:
         if op == CZ:
             dev._apply_channel(kraus, wires=Wires([0, 1]))
         else:
-            kraus = dev._get_kraus(op)
             dev._apply_channel(kraus, wires=Wires(0))
 
         assert np.allclose(dev._state, target_state, atol=tol, rtol=0)
diff --git a/tests/ops/test_channel_ops.py b/tests/ops/test_channel_ops.py
index 66255586f..022bb2d7f 100644
--- a/tests/ops/test_channel_ops.py
+++ b/tests/ops/test_channel_ops.py
@@ -279,6 +279,40 @@ class TestResetError:
         with pytest.raises(ValueError, match="must be between"):
             channel.ResetError(1.0, 1.0, wires=0).kraus_matrices
 
+    @pytest.mark.parametrize("angle", np.linspace(0, 2 * np.pi, 7))
+    def test_grad_reset_error(self, angle, tol):
+        """Test that gradient is computed correctly for different states. Channel
+        grad recipes are independent of channel parameter"""
+
+        dev = qml.device("default.mixed", wires=1)
+        p_0, p_1 = 0.0, 0.5
+
+        @qml.qnode(dev)
+        def circuit(p_0, p_1):
+            qml.RX(angle, wires=0)
+            qml.ResetError(p_0, p_1, wires=0)
+            return qml.expval(qml.PauliZ(0))
+
+        gradient = np.squeeze(qml.grad(circuit)(p_0, p_1))
+        assert np.allclose(
+            gradient,
+            np.array(
+                [
+                    (1 / 0.1) * (circuit(0.1, p_1) - circuit(0.0, p_1)),
+                    (1 / 0.1) * (circuit(p_0, 0.1) - circuit(p_0, 0.0)),
+                ]
+            ),
+        )
+        assert np.allclose(
+            gradient,
+            np.array(
+                [
+                    (2 * np.sin(angle / 2) * np.sin(angle / 2)),
+                    (-2 * np.cos(angle / 2) * np.cos(angle / 2)),
+                ]
+            ),
+        )
+
 
 class TestQubitChannel:
     """Tests for the quantum channel QubitChannel"""