Repository: PennyLaneAI/pennylane
Pull request #1563 (2021-08-19 21:25:18)

[Code Together] Adding SISWAP operation
Hey @antalszava 
I've just added the SISWAP class, but I am having trouble exposing it as `qml.SISWAP` . I tried following the [PR for ISWAP](https://github.com/PennyLaneAI/pennylane/pull/1298/files). But I think the code structure has changed a bit since then? Could you please help me out on this end?

**Related issues**

Closes https://github.com/PennyLaneAI/pennylane/issues/1527

antalszava: Hi @charmerDark, after checking out your branch I could access `qml.SISWAP` :thinking: Was there a particular place in the codebase where it was not working?

The operations have indeed been split across files. If a new op is added to one of the existing files, it should be imported by in the top level `__init__.py` file.
codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1563](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (d62290b) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/e44a3274a30b4ec6597b198703e306d25d8f7886?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (e44a327) will **increase** coverage by `0.00%`.
> The diff coverage is `100.00%`.

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

```diff
@@           Coverage Diff           @@
##           master    #1563   +/-   ##
=======================================
  Coverage   99.15%   99.15%           
=======================================
  Files         189      189           
  Lines       13510    13528   +18     
=======================================
+ Hits        13396    13414   +18     
  Misses        114      114           
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | Coverage Δ | |
|---|---|---|
| [pennylane/devices/default\_qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/ops/qubit/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9fX2luaXRfXy5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/ops/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9fX2luaXRfXy5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/qubit/non\_parametric\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1563/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9ub25fcGFyYW1ldHJpY19vcHMucHk=) | `96.58% <100.00%> (+0.15%)` | :arrow_up: |

------

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

charmerDark: Hey @antalszava !
I hadn't added any tests for SQISW because it redirects to SISWAP class. Also should I mention SQISW in the documentation line for SISWAP?
antalszava: Hi @charmerDark, just for extra safety, cases could be added for `qml.SQISW` too, similar to how it's the case for `CPhase`:
* https://github.com/PennyLaneAI/pennylane/blob/e44a3274a30b4ec6597b198703e306d25d8f7886/tests/ops/test_qubit_ops.py#L431
* https://github.com/PennyLaneAI/pennylane/blob/e44a3274a30b4ec6597b198703e306d25d8f7886/tests/ops/test_qubit_ops.py#L1556

It's mostly a matter of adding the same test case as for `qml.SISWAP`, just calling `qml.SQISW`.

> Also should I mention SQISW in the documentation line for SISWAP?

Yep, it could also be added to `doc/introduction/operations.rst`, just is the case with `CPhase`.
charmerDark: Hey @antalszava
Could you see if its all right now? Thanks in advance

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index 96613c882..dfb90a6ac 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -2,6 +2,9 @@
 
 <h3>New features since last release</h3>
 
+* Added a new `SISWAP` operation and a `SQISW` alias with support to the `default_qubit` device.
+  [#1563](https://github.com/PennyLaneAI/pennylane/pull/1563)
+
 * The `RotosolveOptimizer` now can tackle general parametrized circuits, and is no longer
   restricted to single-qubit Pauli rotations.
   [(#1489)](https://github.com/PennyLaneAI/pennylane/pull/1489)
@@ -262,7 +265,7 @@ and requirements-ci.txt (unpinned). This latter would be used by the CI.
 This release contains contributions from (in alphabetical order):
 
 
-Akash Narayanan B, Thomas Bromley, Tanya Garg, Josh Izaac, Prateek Jain, Johannes Jakob Meyer, Maria Schuld,
+Vishnu Ajith, Akash Narayanan B, Thomas Bromley, Tanya Garg, Josh Izaac, Prateek Jain, Johannes Jakob Meyer, Maria Schuld,
 Ingrid Strandberg, David Wierichs, Vincent Wong.
 
 
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index 8a1a63894..0e08d3433 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -73,6 +73,8 @@ Qubit gates
     ~pennylane.CY
     ~pennylane.SWAP
     ~pennylane.ISWAP
+    ~pennylane.SISWAP
+    ~pennylane.SQISW
     ~pennylane.IsingXX
     ~pennylane.IsingYY
     ~pennylane.IsingZZ
diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py
index ea8a60acf..404e6c3d6 100644
--- a/pennylane/devices/default_qubit.py
+++ b/pennylane/devices/default_qubit.py
@@ -108,6 +108,8 @@ class DefaultQubit(QubitDevice):
         "CNOT",
         "SWAP",
         "ISWAP",
+        "SISWAP",
+        "SQISW",
         "CSWAP",
         "Toffoli",
         "CY",
diff --git a/pennylane/ops/qubit/__init__.py b/pennylane/ops/qubit/__init__.py
index e9a23e95e..729338fb1 100644
--- a/pennylane/ops/qubit/__init__.py
+++ b/pennylane/ops/qubit/__init__.py
@@ -52,6 +52,8 @@ ops = {
     "CY",
     "SWAP",
     "ISWAP",
+    "SISWAP",
+    "SQISW",
     "CSWAP",
     "Toffoli",
     "RX",
diff --git a/pennylane/ops/qubit/non_parametric_ops.py b/pennylane/ops/qubit/non_parametric_ops.py
index 24fd38452..eb019773e 100644
--- a/pennylane/ops/qubit/non_parametric_ops.py
+++ b/pennylane/ops/qubit/non_parametric_ops.py
@@ -646,6 +646,69 @@ class ISWAP(Operation):
         return ISWAP(wires=self.wires).inv()
 
 
+class SISWAP(Operation):
+    r"""SISWAP(wires)
+    The square root of i-swap operator. Can also be accessed as ``qml.SQISW``
+
+    .. math:: SISWAP = \begin{bmatrix}
+            1 & 0 & 0 & 0 \\
+            0 & 1/ \sqrt{2} & i/\sqrt{2} & 0\\
+            0 & i/ \sqrt{2} & 1/ \sqrt{2} & 0\\
+            0 & 0 & 0 & 1
+        \end{bmatrix}.
+
+    **Details:**
+
+    * Number of wires: 2
+    * Number of parameters: 0
+
+    Args:
+        wires (Sequence[int]): the wires the operation acts on
+    """
+    num_params = 0
+    num_wires = 2
+    par_domain = None
+
+    @classmethod
+    def _matrix(cls, *params):
+        return np.array(
+            [
+                [1, 0, 0, 0],
+                [0, INV_SQRT2, INV_SQRT2 * 1j, 0],
+                [0, INV_SQRT2 * 1j, INV_SQRT2, 0],
+                [0, 0, 0, 1],
+            ]
+        )
+
+    @classmethod
+    def _eigvals(cls, *params):
+        return np.array([INV_SQRT2 * (1 + 1j), INV_SQRT2 * (1 - 1j), 1, 1])
+
+    @staticmethod
+    def decomposition(wires):
+        decomp_ops = [
+            SX(wires=wires[0]),
+            qml.RZ(np.pi / 2, wires=wires[0]),
+            CNOT(wires=[wires[0], wires[1]]),
+            SX(wires=wires[0]),
+            qml.RZ(7 * np.pi / 4, wires=wires[0]),
+            SX(wires=wires[0]),
+            qml.RZ(np.pi / 2, wires=wires[0]),
+            SX(wires=wires[1]),
+            qml.RZ(7 * np.pi / 4, wires=wires[1]),
+            CNOT(wires=[wires[0], wires[1]]),
+            SX(wires=wires[0]),
+            SX(wires=wires[1]),
+        ]
+        return decomp_ops
+
+    def adjoint(self):
+        return SISWAP(wires=self.wires).inv()
+
+
+SQISW = SISWAP
+
+
 class CSWAP(Operation):
     r"""CSWAP(wires)
     The controlled-swap operator


Test patch:
diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py
index c5602efb1..8498dc82d 100644
--- a/tests/devices/test_default_qubit.py
+++ b/tests/devices/test_default_qubit.py
@@ -224,7 +224,57 @@ class TestApply:
         ),
     ]
 
-    all_two_wires_no_parameters = test_data_two_wires_no_parameters + test_data_iswap
+    test_data_siswap = [
+        (qml.SISWAP, [1, 0, 0, 0], [1, 0, 0, 0]),
+        (qml.SISWAP, [0, 1, 0, 0], [0, 1 / math.sqrt(2), 1 / math.sqrt(2) * 1j, 0]),
+        (
+            qml.SISWAP,
+            [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0],
+            [1 / math.sqrt(2), 0.5, 0.5 * 1j, 0],
+        ),
+    ]
+
+    test_data_sqisw = [
+        (qml.SQISW, [1, 0, 0, 0], [1, 0, 0, 0]),
+        (qml.SQISW, [0, 1, 0, 0], [0, 1 / math.sqrt(2), 1 / math.sqrt(2) * 1j, 0]),
+        (
+            qml.SQISW,
+            [1 / math.sqrt(2), 1 / math.sqrt(2), 0, 0],
+            [1 / math.sqrt(2), 0.5, 0.5 * 1j, 0],
+        ),
+    ]
+
+    test_data_siswap_inv = [
+        (
+            qml.SISWAP,
+            [1 / math.sqrt(2), 0, 1 / math.sqrt(2), 0],
+            [1 / math.sqrt(2), -0.5 * 1j, 0.5, 0],
+        ),
+        (qml.SISWAP, [0, 0, 1, 0], [0, -1 / math.sqrt(2) * 1j, 1 / math.sqrt(2), 0]),
+        (
+            qml.SISWAP,
+            [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0],
+            [1 / math.sqrt(2), 0.5 * 1j, -0.5, 0],
+        ),
+    ]
+
+    test_data_sqisw_inv = [
+        (
+            qml.SQISW,
+            [1 / math.sqrt(2), 0, 1 / math.sqrt(2), 0],
+            [1 / math.sqrt(2), -0.5 * 1j, 0.5, 0],
+        ),
+        (qml.SQISW, [0, 0, 1, 0], [0, -1 / math.sqrt(2) * 1j, 1 / math.sqrt(2), 0]),
+        (
+            qml.SQISW,
+            [1 / math.sqrt(2), 0, -1 / math.sqrt(2), 0],
+            [1 / math.sqrt(2), 0.5 * 1j, -0.5, 0],
+        ),
+    ]
+
+    all_two_wires_no_parameters = (
+        test_data_two_wires_no_parameters + test_data_iswap + test_data_siswap + test_data_sqisw
+    )
 
     @pytest.mark.parametrize("operation,input,expected_output", all_two_wires_no_parameters)
     def test_apply_operation_two_wires_no_parameters(
@@ -240,7 +290,12 @@ class TestApply:
             qubit_device_2_wires._state.flatten(), np.array(expected_output), atol=tol, rtol=0
         )
 
-    all_two_wires_no_parameters_inv = test_data_two_wires_no_parameters + test_data_iswap_inv
+    all_two_wires_no_parameters_inv = (
+        test_data_two_wires_no_parameters
+        + test_data_iswap_inv
+        + test_data_siswap_inv
+        + test_data_sqisw_inv
+    )
 
     @pytest.mark.parametrize("operation,input,expected_output", all_two_wires_no_parameters_inv)
     def test_apply_operation_two_wires_no_parameters_inverse(
diff --git a/tests/gate_data.py b/tests/gate_data.py
index dfae7c13f..9000f9757 100644
--- a/tests/gate_data.py
+++ b/tests/gate_data.py
@@ -23,6 +23,14 @@ StateOneProjector = np.array([[0, 0], [0, 1]])
 CNOT = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]])  #: CNOT gate
 SWAP = np.array([[1, 0, 0, 0], [0, 0, 1, 0], [0, 1, 0, 0], [0, 0, 0, 1]])  #: SWAP gate
 ISWAP = np.array([[1, 0, 0, 0], [0, 0, 1j, 0], [0, 1j, 0, 0], [0, 0, 0, 1]])  #: ISWAP gate
+SISWAP = np.array(
+    [
+        [1, 0, 0, 0],
+        [0, 1 / math.sqrt(2), 1 / math.sqrt(2) * 1j, 0],
+        [0, 1 / math.sqrt(2) * 1j, 1 / math.sqrt(2), 0],
+        [0, 0, 0, 1],
+    ]
+)
 CZ = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]])  #: CZ gate
 S = np.array([[1, 0], [0, 1j]])  #: Phase Gate
 T = np.array([[1, 0], [0, cmath.exp(1j * np.pi / 4)]])  #: T Gate
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
index d71d47400..5f2df5830 100644
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -39,6 +39,7 @@ from gate_data import (
     CNOT,
     SWAP,
     ISWAP,
+    SISWAP,
     CZ,
     S,
     T,
@@ -400,6 +401,7 @@ NON_PARAMETRIZED_OPERATIONS = [
     (qml.CNOT, CNOT),
     (qml.SWAP, SWAP),
     (qml.ISWAP, ISWAP),
+    (qml.SISWAP, SISWAP),
     (qml.CZ, CZ),
     (qml.S, S),
     (qml.T, T),
@@ -419,6 +421,8 @@ PARAMETRIZED_OPERATIONS = [
     qml.CY(wires=[0, 1]),
     qml.SWAP(wires=[0, 1]),
     qml.ISWAP(wires=[0, 1]),
+    qml.SISWAP(wires=[0, 1]),
+    qml.SQISW(wires=[0, 1]),
     qml.CSWAP(wires=[0, 1, 2]),
     qml.PauliRot(0.123, "Y", wires=0),
     qml.IsingXX(0.123, wires=[0, 1]),
@@ -772,6 +776,55 @@ class TestOperations:
 
         assert np.allclose(decomposed_matrix, op.matrix, atol=tol, rtol=0)
 
+    @pytest.mark.parametrize("siswap_op", [qml.SISWAP, qml.SQISW])
+    def test_SISWAP_decomposition(self, siswap_op, tol):
+        """Tests that the decomposition of the SISWAP gate and its SQISW alias gate is correct"""
+        op = siswap_op(wires=[0, 1])
+        res = op.decomposition(op.wires)
+
+        assert len(res) == 12
+
+        assert res[0].wires == Wires([0])
+        assert res[1].wires == Wires([0])
+        assert res[2].wires == Wires([0, 1])
+        assert res[3].wires == Wires([0])
+        assert res[4].wires == Wires([0])
+        assert res[5].wires == Wires([0])
+        assert res[6].wires == Wires([0])
+        assert res[7].wires == Wires([1])
+        assert res[8].wires == Wires([1])
+        assert res[9].wires == Wires([0, 1])
+        assert res[10].wires == Wires([0])
+        assert res[11].wires == Wires([1])
+
+        assert res[0].name == "SX"
+        assert res[1].name == "RZ"
+        assert res[2].name == "CNOT"
+        assert res[3].name == "SX"
+        assert res[4].name == "RZ"
+        assert res[5].name == "SX"
+        assert res[6].name == "RZ"
+        assert res[7].name == "SX"
+        assert res[8].name == "RZ"
+        assert res[9].name == "CNOT"
+        assert res[10].name == "SX"
+        assert res[11].name == "SX"
+
+        mats = []
+        for i in reversed(res):
+            if i.wires == Wires([1]):
+                mats.append(np.kron(np.eye(2), i.matrix))
+            elif i.wires == Wires([0]):
+                mats.append(np.kron(i.matrix, np.eye(2)))
+            elif i.wires == Wires([1, 0]) and i.name == "CNOT":
+                mats.append(np.array([[1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0], [0, 1, 0, 0]]))
+            else:
+                mats.append(i.matrix)
+
+        decomposed_matrix = np.linalg.multi_dot(mats)
+
+        assert np.allclose(decomposed_matrix, op.matrix, atol=tol, rtol=0)
+
     def test_isingxx_decomposition(self, tol):
         """Tests that the decomposition of the IsingXX gate is correct"""
         param = 0.1234
@@ -1536,6 +1589,14 @@ class TestOperations:
         res = op.eigvals
         assert np.allclose(res, exp)
 
+    @pytest.mark.parametrize("siswap_op", [qml.SISWAP, qml.SQISW])
+    def test_siswap_eigenval(self, siswap_op):
+        """Tests that the ISWAP eigenvalue matches the numpy eigenvalues of the ISWAP matrix"""
+        op = siswap_op(wires=[0, 1])
+        exp = np.linalg.eigvals(op.matrix)
+        res = op.eigvals
+        assert np.allclose(res, exp)
+
     def test_swap_decomposition(self):
         """Tests the swap operator produces the correct output"""
         opr = qml.SWAP(wires=[0, 1])