Repository: PennyLaneAI/pennylane
Pull request #1425 (2021-06-22 15:01:40)

`IsingYY` differentiability
**Context:**

This PR makes `IsingYY` differentiable for the different devices Jax, TensorFlow and Autograd.

**Description of the Change:**

1. I added `IsingYY` operation to `jax_ops.py`, `autograd_ops.py`, and `tf_ops.py`.
2. I modified the `DefaultQubitAutograd`, `DefaultQubitJAX`, and `DefaultQubitTF` devices to support `IsingYY`.
3. Tests the gradient for all three devices and for different `diff-method`.


codecov[bot]: # [Codecov](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#1425](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (87cffb6) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/25284ed58880fa6658676d2184eff5dd4503d699?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (25284ed) will **increase** coverage by `1.02%`.
> The diff coverage is `100.00%`.

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

```diff
@@            Coverage Diff             @@
##           master    #1425      +/-   ##
==========================================
+ Coverage   98.23%   99.26%   +1.02%     
==========================================
  Files         160      155       -5     
  Lines       11964    11448     -516     
==========================================
- Hits        11753    11364     -389     
+ Misses        211       84     -127     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425?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\_autograd.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF9hdXRvZ3JhZC5weQ==) | `100.00% <ø> (ø)` | |
| [pennylane/devices/default\_qubit\_jax.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF9qYXgucHk=) | `95.71% <ø> (+1.34%)` | :arrow_up: |
| [pennylane/devices/default\_qubit\_tf.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvZGVmYXVsdF9xdWJpdF90Zi5weQ==) | `93.15% <ø> (+2.48%)` | :arrow_up: |
| [pennylane/devices/autograd\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvYXV0b2dyYWRfb3BzLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/devices/jax\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvamF4X29wcy5weQ==) | `100.00% <100.00%> (ø)` | |
| [pennylane/devices/tf\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL2RldmljZXMvdGZfb3BzLnB5) | `100.00% <100.00%> (ø)` | |
| [pennylane/ops/qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC5weQ==) | `98.80% <100.00%> (+0.09%)` | :arrow_up: |
| [pennylane/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL19faW5pdF9fLnB5) | `98.48% <0.00%> (-0.09%)` | :arrow_down: |
| [pennylane/devices/default\_qubit.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/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% <0.00%> (ø)` | |
| ... and [32 more](https://codecov.io/gh/PennyLaneAI/pennylane/pull/1425/diff?src=pr&el=tree-more&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) | |

------

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

josh146: @rmoyard is this review ready? Just checking, as no-one has been tagged for review :)
rmoyard: @josh146 Yes indeed it is ready! I forgot to tag someone.
josh146: Hi @rmoyard, is this PR ready to be merged?

Patch:
diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md
index ec66a218a..b2545a8e1 100644
--- a/.github/CHANGELOG.md
+++ b/.github/CHANGELOG.md
@@ -38,6 +38,9 @@
 
 <h3>Bug fixes</h3>
 
+* Fixes the differentiability of the operation`IsingYY` for Autograd, Jax and Tensorflow.
+  [(#1425)](https://github.com/PennyLaneAI/pennylane/pull/1425)
+  
 * Fixed a bug in the `torch` interface that prevented gradients from being
   computed on a GPU [(#1426)](https://github.com/PennyLaneAI/pennylane/pull/1426).
 
@@ -56,7 +59,7 @@
 
 This release contains contributions from (in alphabetical order):
 
-Olivia Di Matteo, Josh Izaac, Ashish Panigrahi.
+Olivia Di Matteo, Josh Izaac, Romain Moyard, Ashish Panigrahi.
 
 
 # Release 0.16.0 (current release)
diff --git a/pennylane/devices/autograd_ops.py b/pennylane/devices/autograd_ops.py
index ee4b1e0f8..33272f237 100644
--- a/pennylane/devices/autograd_ops.py
+++ b/pennylane/devices/autograd_ops.py
@@ -29,6 +29,7 @@ Z = np.array([[1, 0], [0, -1]], dtype=C_DTYPE)
 II = np.eye(4, dtype=C_DTYPE)
 ZZ = np.array(kron(Z, Z), dtype=C_DTYPE)
 XX = np.array(kron(X, X), dtype=C_DTYPE)
+YY = np.array(kron(Y, Y), dtype=C_DTYPE)
 
 IX = np.array(kron(I, X), dtype=C_DTYPE)
 IY = np.array(kron(I, Y), dtype=C_DTYPE)
@@ -201,6 +202,24 @@ def IsingXX(phi):
     return np.cos(phi / 2) * II - 1j * np.sin(phi / 2) * XX
 
 
+def IsingYY(phi):
+    r"""Ising YY coupling gate.
+
+    .. math:: YY(\phi) = \begin{bmatrix}
+        \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\
+        0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+        0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+        i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+        \end{bmatrix}.
+
+    Args:
+        phi (float): rotation angle :math:`\phi`
+    Returns:
+        array[complex]: unitary 4x4 rotation matrix
+    """
+    return np.cos(phi / 2) * II - 1j * np.sin(phi / 2) * YY
+
+
 def IsingZZ(phi):
     r"""Ising ZZ coupling gate
 
diff --git a/pennylane/devices/default_qubit_autograd.py b/pennylane/devices/default_qubit_autograd.py
index d06d383f4..67283744e 100644
--- a/pennylane/devices/default_qubit_autograd.py
+++ b/pennylane/devices/default_qubit_autograd.py
@@ -95,6 +95,7 @@ class DefaultQubitAutograd(DefaultQubit):
         "CRot": autograd_ops.CRot,
         "MultiRZ": autograd_ops.MultiRZ,
         "IsingXX": autograd_ops.IsingXX,
+        "IsingYY": autograd_ops.IsingYY,
         "IsingZZ": autograd_ops.IsingZZ,
         "SingleExcitation": autograd_ops.SingleExcitation,
         "SingleExcitationPlus": autograd_ops.SingleExcitationPlus,
diff --git a/pennylane/devices/default_qubit_jax.py b/pennylane/devices/default_qubit_jax.py
index 67913669a..a69c34f69 100644
--- a/pennylane/devices/default_qubit_jax.py
+++ b/pennylane/devices/default_qubit_jax.py
@@ -148,6 +148,7 @@ class DefaultQubitJax(DefaultQubit):
         "CRot": jax_ops.CRot,
         "MultiRZ": jax_ops.MultiRZ,
         "IsingXX": jax_ops.IsingXX,
+        "IsingYY": jax_ops.IsingYY,
         "IsingZZ": jax_ops.IsingZZ,
         "SingleExcitation": jax_ops.SingleExcitation,
         "SingleExcitationPlus": jax_ops.SingleExcitationPlus,
diff --git a/pennylane/devices/default_qubit_tf.py b/pennylane/devices/default_qubit_tf.py
index df0dba8dd..2dc774e73 100644
--- a/pennylane/devices/default_qubit_tf.py
+++ b/pennylane/devices/default_qubit_tf.py
@@ -142,6 +142,7 @@ class DefaultQubitTF(DefaultQubit):
         "CRZ": tf_ops.CRZ,
         "CRot": tf_ops.CRot,
         "IsingXX": tf_ops.IsingXX,
+        "IsingYY": tf_ops.IsingYY,
         "IsingZZ": tf_ops.IsingZZ,
         "SingleExcitation": tf_ops.SingleExcitation,
         "SingleExcitationPlus": tf_ops.SingleExcitationPlus,
diff --git a/pennylane/devices/jax_ops.py b/pennylane/devices/jax_ops.py
index 9d9555ae7..c95a45b94 100644
--- a/pennylane/devices/jax_ops.py
+++ b/pennylane/devices/jax_ops.py
@@ -29,6 +29,7 @@ Z = jnp.array([[1, 0], [0, -1]], dtype=C_DTYPE)
 II = jnp.eye(4, dtype=C_DTYPE)
 ZZ = jnp.array(jnp.kron(Z, Z), dtype=C_DTYPE)
 XX = jnp.array(jnp.kron(X, X), dtype=C_DTYPE)
+YY = jnp.array(jnp.kron(Y, Y), dtype=C_DTYPE)
 
 IX = jnp.array(jnp.kron(I, X), dtype=C_DTYPE)
 IY = jnp.array(jnp.kron(I, Y), dtype=C_DTYPE)
@@ -201,6 +202,24 @@ def IsingXX(phi):
     return jnp.cos(phi / 2) * II - 1j * jnp.sin(phi / 2) * XX
 
 
+def IsingYY(phi):
+    r"""Ising YY coupling gate.
+
+    .. math:: YY(\phi) = \begin{bmatrix}
+        \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\
+        0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+        0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+        i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+        \end{bmatrix}.
+
+    Args:
+        phi (float): rotation angle :math:`\phi`
+    Returns:
+        array[complex]: unitary 4x4 rotation matrix
+    """
+    return jnp.cos(phi / 2) * II - 1j * jnp.sin(phi / 2) * YY
+
+
 def IsingZZ(phi):
     r"""Ising ZZ coupling gate
 
diff --git a/pennylane/devices/tf_ops.py b/pennylane/devices/tf_ops.py
index febaa6c8d..c644eec7a 100644
--- a/pennylane/devices/tf_ops.py
+++ b/pennylane/devices/tf_ops.py
@@ -29,6 +29,7 @@ Z = tf.constant([[1, 0], [0, -1]], dtype=C_DTYPE)
 II = tf.eye(4, dtype=C_DTYPE)
 ZZ = tf.constant(kron(Z, Z), dtype=C_DTYPE)
 XX = tf.constant(kron(X, X), dtype=C_DTYPE)
+YY = tf.constant(kron(Y, Y), dtype=C_DTYPE)
 
 IX = tf.constant(kron(I, X), dtype=C_DTYPE)
 IY = tf.constant(kron(I, Y), dtype=C_DTYPE)
@@ -213,6 +214,25 @@ def IsingXX(phi):
     return tf.cos(phi / 2) * II - 1j * tf.sin(phi / 2) * XX
 
 
+def IsingYY(phi):
+    r"""Ising YY coupling gate
+
+    .. math:: YY(\phi) = \begin{bmatrix}
+        \cos(\phi / 2) & 0 & 0 & i \sin(\phi / 2) \\
+        0 & \cos(\phi / 2) & -i \sin(\phi / 2) & 0 \\
+        0 & -i \sin(\phi / 2) & \cos(\phi / 2) & 0 \\
+        i \sin(\phi / 2) & 0 & 0 & \cos(\phi / 2)
+        \end{bmatrix}.
+
+    Args:
+        phi (float): rotation angle :math:`\phi`
+    Returns:
+        tf.Tensor[complex]: unitary 4x4 rotation matrix
+    """
+    phi = tf.cast(phi, dtype=C_DTYPE)
+    return tf.cos(phi / 2) * II - 1j * tf.sin(phi / 2) * YY
+
+
 def IsingZZ(phi):
     r"""Ising ZZ coupling gate
 
diff --git a/pennylane/ops/qubit.py b/pennylane/ops/qubit.py
index cc1975097..c3db03e5b 100644
--- a/pennylane/ops/qubit.py
+++ b/pennylane/ops/qubit.py
@@ -1850,6 +1850,7 @@ class IsingYY(Operation):
     num_wires = 2
     par_domain = "R"
     grad_method = "A"
+    generator = [np.array([[0, 0, 0, -1], [0, 0, 1, 0], [0, 1, 0, 0], [-1, 0, 0, 0]]), -1 / 2]
 
     @staticmethod
     def decomposition(phi, wires):


Test patch:
diff --git a/tests/ops/test_qubit_ops.py b/tests/ops/test_qubit_ops.py
index 8446595a0..c37cce5b5 100644
--- a/tests/ops/test_qubit_ops.py
+++ b/tests/ops/test_qubit_ops.py
@@ -864,7 +864,40 @@ class TestOperations:
             qml.IsingXX(phi, wires=[0, 1])
             return qml.expval(qml.PauliZ(0))
 
-        phi = npp.array(0.1, requires_grad=True)
+        expected = (
+            0.5
+            * (1 / norm ** 2)
+            * (
+                -np.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2)
+                + 2
+                * np.sin(phi / 2)
+                * np.cos(phi / 2)
+                * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2)
+            )
+        )
+
+        res = qml.grad(circuit)(phi)
+        assert np.allclose(res, expected, atol=tol, rtol=0)
+
+    @pytest.mark.parametrize("dev_name,diff_method,phi", configuration)
+    def test_isingyy_autograd_grad(self, tol, dev_name, diff_method, phi):
+        """Test the gradient for the gate IsingYY."""
+        dev = qml.device(dev_name, wires=2)
+
+        psi_0 = 0.1
+        psi_1 = 0.2
+        psi_2 = 0.3
+        psi_3 = 0.4
+
+        init_state = npp.array([psi_0, psi_1, psi_2, psi_3], requires_grad=False)
+        norm = np.linalg.norm(init_state)
+        init_state /= norm
+
+        @qml.qnode(dev, diff_method=diff_method, interface="autograd")
+        def circuit(phi):
+            qml.QubitStateVector(init_state, wires=[0, 1])
+            qml.IsingYY(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
 
         expected = (
             0.5
@@ -955,6 +988,53 @@ class TestOperations:
         res = jax.grad(circuit, argnums=0)(phi)
         assert np.allclose(res, expected, atol=tol, rtol=0)
 
+    @pytest.mark.parametrize("dev_name,diff_method,phi", configuration)
+    def test_isingyy_jax_grad(self, tol, dev_name, diff_method, phi):
+        """Test the gradient for the gate IsingYY."""
+
+        if diff_method in {"finite-diff"}:
+            pytest.skip("Test does not support finite-diff")
+
+        if diff_method in {"parameter-shift"}:
+            pytest.skip("Test does not support parameter-shift")
+
+        jax = pytest.importorskip("jax")
+        jnp = pytest.importorskip("jax.numpy")
+
+        dev = qml.device(dev_name, wires=2)
+
+        psi_0 = 0.1
+        psi_1 = 0.2
+        psi_2 = 0.3
+        psi_3 = 0.4
+
+        init_state = jnp.array([psi_0, psi_1, psi_2, psi_3])
+        norm = jnp.linalg.norm(init_state)
+        init_state = init_state / norm
+
+        @qml.qnode(dev, diff_method=diff_method, interface="jax")
+        def circuit(phi):
+            qml.QubitStateVector(init_state, wires=[0, 1])
+            qml.IsingYY(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
+
+        phi = jnp.array(0.1)
+
+        expected = (
+            0.5
+            * (1 / norm ** 2)
+            * (
+                -np.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2)
+                + 2
+                * np.sin(phi / 2)
+                * np.cos(phi / 2)
+                * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2)
+            )
+        )
+
+        res = jax.grad(circuit, argnums=0)(phi)
+        assert np.allclose(res, expected, atol=tol, rtol=0)
+
     @pytest.mark.parametrize("dev_name,diff_method,phi", configuration)
     def test_isingzz_jax_grad(self, tol, dev_name, diff_method, phi):
         """Test the gradient for the gate IsingZZ."""
@@ -1033,6 +1113,47 @@ class TestOperations:
         res = tape.gradient(result, phi)
         assert np.allclose(res, expected, atol=tol, rtol=0)
 
+    @pytest.mark.parametrize("dev_name,diff_method,phi", configuration)
+    def test_isingyy_tf_grad(self, tol, dev_name, diff_method, phi):
+        """Test the gradient for the gate IsingYY."""
+        tf = pytest.importorskip("tensorflow", minversion="2.1")
+
+        dev = qml.device(dev_name, wires=2)
+
+        psi_0 = tf.Variable(0.1, dtype=tf.complex128)
+        psi_1 = tf.Variable(0.2, dtype=tf.complex128)
+        psi_2 = tf.Variable(0.3, dtype=tf.complex128)
+        psi_3 = tf.Variable(0.4, dtype=tf.complex128)
+
+        init_state = tf.Variable([psi_0, psi_1, psi_2, psi_3], dtype=tf.complex128)
+        norm = tf.norm(init_state)
+        init_state = init_state / norm
+
+        @qml.qnode(dev, interface="tf", diff_method=diff_method)
+        def circuit(phi):
+            qml.QubitStateVector(init_state, wires=[0, 1])
+            qml.IsingYY(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
+
+        phi = tf.Variable(0.1, dtype=tf.complex128)
+
+        expected = (
+            0.5
+            * (1 / norm ** 2)
+            * (
+                -tf.sin(phi) * (psi_0 ** 2 + psi_1 ** 2 - psi_2 ** 2 - psi_3 ** 2)
+                + 2
+                * tf.sin(phi / 2)
+                * tf.cos(phi / 2)
+                * (-(psi_0 ** 2) - psi_1 ** 2 + psi_2 ** 2 + psi_3 ** 2)
+            )
+        )
+
+        with tf.GradientTape() as tape:
+            result = circuit(phi)
+        res = tape.gradient(result, phi)
+        assert np.allclose(res, expected, atol=tol, rtol=0)
+
     @pytest.mark.parametrize("dev_name,diff_method,phi", configuration)
     def test_isingzz_tf_grad(self, tol, dev_name, diff_method, phi):
         """Test the gradient for the gate IsingZZ."""