Repository: PennyLaneAI/pennylane
Pull request #1278 (2021-05-05 20:00:03)

Add decompositions for single excitation operations
**Context:** Currently `SingleExcitation` and `DoubleExcitation` are decomposed into components (`SingleExcitationPlus`, `SingleExcitationMinus`, and similarly for `Double`) that have no further decomposition into elementary gates, which are necessary to run on some devices.

**Description of the Change:** Adds decompositions for `SingleExcitationPlus` and `SingleExcitationMinus`. Also fixes some mathematical errors in the `DoubleExcitation` docstrings.

**Benefits:** Single excitation operations can now be run on all devices.

**Possible Drawbacks:** None

**Related GitHub Issues:** #1275 


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.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1278?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1278](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1278?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (007c08f) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/6e8efa853ffa05738dc529c3463d4496f68a6ecb?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (6e8efa8) will **decrease** coverage by `0.03%`.
> The diff coverage is `50.00%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1278      +/-   ##
==========================================
- Coverage   98.14%   98.11%   -0.04%     
==========================================
  Files         147      147              
  Lines       11242    11250       +8     
==========================================
+ Hits        11034    11038       +4     
- Misses        208      212       +4     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1278?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/ops/qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1278/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC5weQ==) | `97.92% <50.00%> (-0.43%)` | :arrow_down: |

------

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


Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 855ca26b0..1eb926d46 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -109,6 +109,10 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 
 <h3>Improvements</h3>
 
+* The `qml.SingleExcitationPlus` and `qml.SingleExcitationMinus` operations now
+  have decompositions over elementary gates.
+  [(#1278)](https://github.com/PennyLaneAI/pennylane/pull/1278)
+
 * The `Device` class now uses caching when mapping wires.
   [(#1270)](https://github.com/PennyLaneAI/pennylane/pull/1270)
 
@@ -137,6 +141,9 @@ random_mat2 = rng.standard_normal(3, requires_grad=False)
 
 <h3>Documentation</h3>
 
+* Fixed typos in the mathematical expressions in documentation of `qml.DoubleExcitation`.
+  [(#1278)](https://github.com/PennyLaneAI/pennylane/pull/1278)
+
 * Remove unsupported `None` option from the `qml.QNode` docstrings.
   [(#1271)](https://github.com/PennyLaneAI/pennylane/pull/1271)
 
diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py
index 2c930598c..9a5500918 100644
--- a/pennylane/ops/qubit.py
+++ b/pennylane/ops/qubit.py
@@ -1798,6 +1798,21 @@ class SingleExcitationMinus(Operation):
 
         return np.array([[e, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, e]])
 
+    @staticmethod
+    def decomposition(theta, wires):
+        decomp_ops = [
+            qml.PauliX(wires=wires[0]),
+            qml.PauliX(wires=wires[1]),
+            qml.ControlledPhaseShift(-theta / 2, wires=[wires[1], wires[0]]),
+            qml.PauliX(wires=wires[0]),
+            qml.PauliX(wires=wires[1]),
+            qml.ControlledPhaseShift(-theta / 2, wires=[wires[0], wires[1]]),
+            qml.CNOT(wires=[wires[0], wires[1]]),
+            qml.CRY(theta, wires=[wires[1], wires[0]]),
+            qml.CNOT(wires=[wires[0], wires[1]]),
+        ]
+        return decomp_ops
+
     def adjoint(self):
         (phi,) = self.parameters
         return SingleExcitationMinus(-phi, wires=self.wires)
@@ -1841,6 +1856,21 @@ class SingleExcitationPlus(Operation):
 
         return np.array([[e, 0, 0, 0], [0, c, -s, 0], [0, s, c, 0], [0, 0, 0, e]])
 
+    @staticmethod
+    def decomposition(theta, wires):
+        decomp_ops = [
+            qml.PauliX(wires=wires[0]),
+            qml.PauliX(wires=wires[1]),
+            qml.ControlledPhaseShift(theta / 2, wires=[wires[1], wires[0]]),
+            qml.PauliX(wires=wires[0]),
+            qml.PauliX(wires=wires[1]),
+            qml.ControlledPhaseShift(theta / 2, wires=[wires[0], wires[1]]),
+            qml.CNOT(wires=[wires[0], wires[1]]),
+            qml.CRY(theta, wires=[wires[1], wires[0]]),
+            qml.CNOT(wires=[wires[0], wires[1]]),
+        ]
+        return decomp_ops
+
     def adjoint(self):
         (phi,) = self.parameters
         return SingleExcitationPlus(-phi, wires=self.wires)
@@ -2198,8 +2228,8 @@ class DoubleExcitation(Operation):
 
     .. math::
 
-        &|0011\rangle \rightarrow \cos(\phi) |0011\rangle - \sin(\phi) |1100\rangle\\
-        &|1100\rangle \rightarrow \cos(\phi) |1100\rangle + \sin(\phi) |0011\rangle,
+        &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\
+        &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle,
 
     while leaving all other basis states unchanged.
 
@@ -2221,7 +2251,7 @@ class DoubleExcitation(Operation):
     **Example**
 
     The following circuit performs the transformation :math:`|1100\rangle\rightarrow \cos(
-    \phi/2)|1100\rangle -\sin(\phi/2)|0011\rangle)`:
+    \phi/2)|1100\rangle +\sin(\phi/2)|0011\rangle)`:
 
     .. code-block::
 
@@ -2284,9 +2314,9 @@ class DoubleExcitationPlus(Operation):
 
     .. math::
 
-        &|0011\rangle \rightarrow \cos(\phi) |0011\rangle - \sin(\phi) |1100\rangle\\
-        &|1100\rangle \rightarrow \cos(\phi) |1100\rangle + \sin(\phi) |0011\rangle\\
-        &|x\rangle \rightarrow e^{i\phi} |x\rangle,
+        &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\
+        &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\
+        &|x\rangle \rightarrow e^{i\phi/2} |x\rangle,
 
     for all other basis states :math:`|x\rangle`.
 
@@ -2344,9 +2374,9 @@ class DoubleExcitationMinus(Operation):
 
     .. math::
 
-        &|0011\rangle \rightarrow \cos(\phi) |0011\rangle - \sin(\phi) |1100\rangle\\
-        &|1100\rangle \rightarrow \cos(\phi) |1100\rangle + \sin(\phi) |0011\rangle\\
-        &|x\rangle \rightarrow e^{-i\phi} |x\rangle,
+        &|0011\rangle \rightarrow \cos(\phi/2) |0011\rangle - \sin(\phi/2) |1100\rangle\\
+        &|1100\rangle \rightarrow \cos(\phi/2) |1100\rangle + \sin(\phi/2) |0011\rangle\\
+        &|x\rangle \rightarrow e^{-i\phi/2} |x\rangle,
 
     for all other basis states :math:`|x\rangle`.
 


Test patch:
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
index bcf0b2ff3..165a485ae 100644
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -905,6 +905,76 @@ class TestOperations:
 
         assert np.allclose(decomposed_matrix, exp)
 
+    @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5])
+    def test_single_excitation_plus_decomp(self, phi):
+        """Tests that the SingleExcitationPlus operation calculates the correct decomposition.
+
+        Need to consider the matrix of CRY separately, as the control is wire 1
+        and the target is wire 0 in the decomposition. (Not applicable for
+        ControlledPhase as it has the same matrix representation regardless of the
+        control and target wires.)"""
+        decomp = qml.SingleExcitationPlus.decomposition(phi, wires=[0, 1])
+
+        mats = []
+        for i in reversed(decomp):
+            if i.wires.tolist() == [0]:
+                mats.append(np.kron(i.matrix, np.eye(2)))
+            elif i.wires.tolist() == [1]:
+                mats.append(np.kron(np.eye(2), i.matrix))
+            elif i.wires.tolist() == [1, 0] and isinstance(i, qml.CRY):
+                new_mat = np.array(
+                    [
+                        [1, 0, 0, 0],
+                        [0, np.cos(phi / 2), 0, -np.sin(phi / 2)],
+                        [0, 0, 1, 0],
+                        [0, np.sin(phi / 2), 0, np.cos(phi / 2)],
+                    ]
+                )
+
+                mats.append(new_mat)
+            else:
+                mats.append(i.matrix)
+
+        decomposed_matrix = np.linalg.multi_dot(mats)
+        exp = SingleExcitationPlus(phi)
+
+        assert np.allclose(decomposed_matrix, exp)
+
+    @pytest.mark.parametrize("phi", [-0.1, 0.2, 0.5])
+    def test_single_excitation_minus_decomp(self, phi):
+        """Tests that the SingleExcitationMinus operation calculates the correct decomposition.
+
+        Need to consider the matrix of CRY separately, as the control is wire 1
+        and the target is wire 0 in the decomposition. (Not applicable for
+        ControlledPhase as it has the same matrix representation regardless of the
+        control and target wires.)"""
+        decomp = qml.SingleExcitationMinus.decomposition(phi, wires=[0, 1])
+
+        mats = []
+        for i in reversed(decomp):
+            if i.wires.tolist() == [0]:
+                mats.append(np.kron(i.matrix, np.eye(2)))
+            elif i.wires.tolist() == [1]:
+                mats.append(np.kron(np.eye(2), i.matrix))
+            elif i.wires.tolist() == [1, 0] and isinstance(i, qml.CRY):
+                new_mat = np.array(
+                    [
+                        [1, 0, 0, 0],
+                        [0, np.cos(phi / 2), 0, -np.sin(phi / 2)],
+                        [0, 0, 1, 0],
+                        [0, np.sin(phi / 2), 0, np.cos(phi / 2)],
+                    ]
+                )
+
+                mats.append(new_mat)
+            else:
+                mats.append(i.matrix)
+
+        decomposed_matrix = np.linalg.multi_dot(mats)
+        exp = SingleExcitationMinus(phi)
+
+        assert np.allclose(decomposed_matrix, exp)
+
 
 class TestSingleExcitation:
     @pytest.mark.parametrize("phi", [-0.1, 0.2, np.pi / 4])