Repository: PennyLaneAI/pennylane
Pull request #3380 (2022-11-17 16:52:29)

Add `FermionicSWAP` gate operation
**Context:**
For adding a swap network template, we require a fermionic SWAP gate which is not currently there.

**Description of the Change:**
Adds a `fermionicSWAP` gate operation from [arXiv:1711.04789](https://arxiv.org/abs/1711.04789).

**Benefits:**

**Possible Drawbacks:**

**Related GitHub Issues:**


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/3380?src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) Report
> Merging [#3380](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3380?src=pr&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (2023ff7) into [master](https://codecov.io/gh/PennyLaneAI/pennylane/commit/f1bf5441a5b704da412a01a5b26cdf42279fee3e?el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI) (f1bf544) will **decrease** coverage by `0.00%`.
> The diff coverage is `95.34%`.

```diff
@@            Coverage Diff             @@
##           master    #3380      +/-   ##
==========================================
- Coverage   99.70%   99.69%   -0.01%     
==========================================
  Files         299      299              
  Lines       26199    26242      +43     
==========================================
+ Hits        26121    26162      +41     
- Misses         78       80       +2     
```


| [Impacted Files](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3380?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/3380/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/ops/qubit/\_\_init\_\_.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3380/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/qubit/attributes.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3380/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9hdHRyaWJ1dGVzLnB5) | `100.00% <ø> (ø)` | |
| [pennylane/ops/qubit/qchem\_ops.py](https://codecov.io/gh/PennyLaneAI/pennylane/pull/3380/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI#diff-cGVubnlsYW5lL29wcy9xdWJpdC9xY2hlbV9vcHMucHk=) | `99.36% <95.34%> (-0.64%)` | :arrow_down: |

Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=PennyLaneAI)


Patch:
diff --git a/doc/introduction/operations.rst b/doc/introduction/operations.rst
index bc8ce84ea..3b69e7a04 100644
--- a/doc/introduction/operations.rst
+++ b/doc/introduction/operations.rst
@@ -210,6 +210,7 @@ Quantum chemistry gates
     ~pennylane.DoubleExcitationPlus
     ~pennylane.DoubleExcitationMinus
     ~pennylane.OrbitalRotation
+    ~pennylane.FermionicSWAP
 
 :html:`</div>`
 
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index cf0c956c5..1dcec12c0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -93,6 +93,29 @@
 * New basis sets, `6-311g` and `CC-PVDZ`, are added to the qchem basis set repo.
   [#3279](https://github.com/PennyLaneAI/pennylane/pull/3279)
 
+* Added a new gate operation `FermionicSWAP`, which implements the exchange of spin orbitals
+  representing fermionic-modes while maintaining proper anti-symmetrization.
+  [(#3380)](https://github.com/PennyLaneAI/pennylane/pull/3380)
+
+  An example circuit that uses `FermionicSWAP` operation is:
+
+  ```python
+  dev = qml.device('default.qubit', wires=2)
+
+  @qml.qnode(dev)
+  def circuit(phi):
+      qml.BasisState(np.array([0, 1]), wires=[0, 1])
+      qml.FermionicSWAP(phi, wires=[0, 1])
+      return qml.state()
+  ```
+
+  If we run this circuit, we will get the following output
+
+  ```pycon
+  >>> circuit(0.1)
+  array([0.+0.j, 0.9975+0.04992j, 0.0025-0.04992j, 0.+0.j])
+  ```
+
 * New parametric qubit ops `qml.CPhaseShift00`, `qml.CPhaseShift01` and `qml.CPhaseShift10` which perform a phaseshift, similar to `qml.ControlledPhaseShift` but on different positions of the state vector.
   [(#2715)](https://github.com/PennyLaneAI/pennylane/pull/2715)
 
diff --git a/pennylane/devices/default_mixed.py b/pennylane/devices/default_mixed.py
index bd1f3b666..ceb4049c2 100644
--- a/pennylane/devices/default_mixed.py
+++ b/pennylane/devices/default_mixed.py
@@ -124,6 +124,7 @@ class DefaultMixed(QubitDevice):
         "QubitCarry",
         "QubitSum",
         "OrbitalRotation",
+        "FermionicSWAP",
         "QFT",
         "ThermalRelaxationError",
         "ECR",
diff --git a/pennylane/ops/qubit/__init__.py b/pennylane/ops/qubit/__init__.py
index f37bbc768..6e427471f 100644
--- a/pennylane/ops/qubit/__init__.py
+++ b/pennylane/ops/qubit/__init__.py
@@ -102,6 +102,7 @@ ops = {
     "QubitCarry",
     "QubitSum",
     "OrbitalRotation",
+    "FermionicSWAP",
     "Barrier",
     "WireCut",
 }
diff --git a/pennylane/ops/qubit/attributes.py b/pennylane/ops/qubit/attributes.py
index 77f285869..780ae1ce5 100644
--- a/pennylane/ops/qubit/attributes.py
+++ b/pennylane/ops/qubit/attributes.py
@@ -141,6 +141,7 @@ has_unitary_generator = Attribute(
         "DoubleExcitationMinus",
         "DoubleExcitationPlus",
         "OrbitalRotation",
+        "FermionicSWAP",
     ]
 )
 """Attribute: Operations that are generated by a unitary operator.
@@ -234,6 +235,7 @@ supports_broadcasting = Attribute(
         "DoubleExcitationPlus",
         "DoubleExcitationMinus",
         "OrbitalRotation",
+        "FermionicSWAP",
         "QubitStateVector",
         "AmplitudeEmbedding",
         "AngleEmbedding",
diff --git a/pennylane/ops/qubit/qchem_ops.py b/pennylane/ops/qubit/qchem_ops.py
index 27fcad911..46867162f 100644
--- a/pennylane/ops/qubit/qchem_ops.py
+++ b/pennylane/ops/qubit/qchem_ops.py
@@ -1055,3 +1055,192 @@ class OrbitalRotation(Operation):
     def adjoint(self):
         (phi,) = self.parameters
         return OrbitalRotation(-phi, wires=self.wires)
+
+
+class FermionicSWAP(Operation):
+    r"""Fermionic SWAP rotation.
+
+    .. math:: U(\phi) = \begin{bmatrix}
+                1 & 0 & 0 & 0 \\
+                0 & e^{i \phi/2} \cos(\phi/2) & -ie^{i \phi/2} \sin(\phi/2) & 0 \\
+                0 & -ie^{i \phi/2} \sin(\phi/2) & e^{i \phi/2} \cos(\phi/2) & 0 \\
+                0 & 0 & 0 & e^{i \phi}
+            \end{bmatrix}.
+
+    This operation performs a rotation in the adjacent fermionic modes under the Jordan-Wigner mapping,
+    and is realized by the following transformation of basis states:
+
+    .. math::
+        &|00\rangle \mapsto |00\rangle\\
+        &|01\rangle \mapsto e^{i \phi/2} \cos(\phi/2)|01\rangle - ie^{i \phi/2} \sin(\phi/2)|10\rangle\\
+        &|10\rangle \mapsto -ie^{i \phi/2} \sin(\phi/2)|01\rangle + e^{i \phi/2} \cos(\phi/2)|10\rangle\\
+        &|11\rangle \mapsto e^{i \phi}|11\rangle,
+
+    where qubits in :math:`|0\rangle` and :math:`|1\rangle` states represent a hole and a fermion in
+    the orbital, respectively. It preserves anti-symmetrization of orbitals by applying a phase factor
+    of :math:`e^{i \phi/2}` to the state for each qubit initially in :math:`|1\rangle` state. Consequently,
+    for :math:`\phi=\pi`, the given rotation will essentially perform a SWAP operation on the qubits while
+    applying a global phase of :math:`-1`, if both qubits are :math:`|1\rangle`.
+
+    **Details:**
+
+    * Number of wires: 2
+    * Number of parameters: 1
+    * Number of dimensions per parameter: (0,)
+    * Gradient recipe: :math:`\frac{d}{d\phi}f(U(\phi)) = \frac{1}{2}\left[f(U(\phi+\pi/2)) - f(U(\phi-\pi/2))\right]`
+      where :math:`f` is an expectation value depending on :math:`U(\phi)`
+
+    Args:
+        phi (float): rotation angle :math:`\phi`
+        wires (Sequence[int]): the wires the operation acts on
+        do_queue (bool): Indicates whether the operator should be
+            immediately pushed into the Operator queue (optional)
+        id (str or None): String representing the operation (optional)
+
+    **Example**
+
+    The following circuit performs the transformation: :math:`|01\rangle \mapsto e^{i \phi/2}
+    \cos(\phi/2)|01\rangle - ie^{i \phi/2} \sin(\phi/2)|10\rangle`, where :math:`\phi=0.1`:
+
+    .. code-block::
+
+        >>> dev = qml.device('default.qubit', wires=2)
+        >>> @qml.qnode(dev)
+        ... def circuit(phi):
+        ...     qml.PauliX(wires=1)
+        ...     qml.FermionicSWAP(phi, wires=[0, 1])
+        ...     return qml.state()
+        >>> circuit(0.1)
+        array([0.+0.j, 0.9975+0.04992j, 0.0025-0.04992j, 0.+0.j])
+    """
+
+    num_wires = 2
+    """int: Number of wires that the operator acts on."""
+
+    num_params = 1
+    """int: Number of trainable parameters that the operator depends on."""
+
+    ndim_params = (0,)
+    """tuple[int]: Number of dimensions per trainable parameter that the operator depends on."""
+
+    grad_method = "A"
+    """Gradient computation method."""
+
+    parameter_frequencies = [(1,)]
+    """Frequencies of the operation parameter with respect to an expectation value."""
+
+    def generator(self):
+        w1, w2 = self.wires
+        return 0.5 * qml.Identity(w1) @ qml.Identity(w2) - 0.25 * (
+            qml.Identity(w1) @ qml.PauliZ(w2)
+            + qml.PauliZ(w1) @ qml.Identity(w2)
+            + qml.PauliX(w1) @ qml.PauliX(w2)
+            + qml.PauliY(w1) @ qml.PauliY(w2)
+        )
+
+    def __init__(self, phi, wires, do_queue=True, id=None):
+        super().__init__(phi, wires=wires, do_queue=do_queue, id=id)
+
+    @staticmethod
+    def compute_matrix(phi):  # pylint: disable=arguments-differ
+        r"""Representation of the operator as a canonical matrix in the computational basis (static method).
+
+        The canonical matrix is the textbook matrix representation that does not consider wires.
+        Implicitly, this assumes that the wires of the operator correspond to the global wire order.
+
+        .. seealso:: :meth:`~.FermionicSWAP.matrix`
+
+
+        Args:
+          phi (tensor_like or float): rotation angle
+
+        Returns:
+          tensor_like: canonical matrix
+
+        **Example**
+
+        >>> qml.FermionicSWAP.compute_matrix(torch.tensor(0.5))
+        tensor([1.   +0.j, 0.   +0.j  , 0.   +0.j  , 0.   +0.j   ],
+               [0.   +0.j, 0.939+0.24j, 0.061-0.24j, 0.   +0.j   ],
+               [0.   +0.j, 0.061-0.24j, 0.939+0.24j, 0.   +0.j   ],
+               [0.   +0.j, 0.   +0.j  , 0.   +0.j  , 0.878+0.479j]])
+        """
+
+        if qml.math.get_interface(phi) == "tensorflow":
+            phi = qml.math.cast_like(phi, 1j)
+
+        c = qml.math.cast_like(qml.math.cos(phi / 2), 1j)
+        s = qml.math.cast_like(qml.math.sin(phi / 2), 1j)
+        g = qml.math.cast_like(qml.math.exp(1j * phi / 2), 1j)
+        p = qml.math.cast_like(qml.math.exp(1j * phi), 1j)
+
+        zeros = qml.math.zeros_like(phi)
+        ones = qml.math.ones_like(phi)
+        rows = [
+            [ones, zeros, zeros, zeros],
+            [zeros, g * c, -1j * g * s, zeros],
+            [zeros, -1j * g * s, g * c, zeros],
+            [zeros, zeros, zeros, p],
+        ]
+        return qml.math.stack([stack_last(row) for row in rows], axis=-2)
+
+    @staticmethod
+    def compute_decomposition(phi, wires):
+        r"""Representation of the operator as a product of other operators (static method). :
+
+        .. math:: O = O_1 O_2 \dots O_n.
+
+
+        .. seealso:: :meth:`~.FermionicSWAP.decomposition`.
+
+        Args:
+            phi (float): rotation angle :math:`\phi`
+            wires (Iterable, Wires): wires that the operator acts on
+
+        Returns:
+            list[Operator]: decomposition into lower level operations
+
+        **Example:**
+
+        >>> qml.FermionicSWAP.compute_decomposition(0.2, wires=(0, 1))
+        [Hadamard(wires=[0]),
+         Hadamard(wires=[1]),
+         MultiRZ(0.1, wires=[0, 1]),
+         Hadamard(wires=[0]),
+         Hadamard(wires=[1]),
+         RX(1.5707963267948966, wires=[0]),
+         RX(1.5707963267948966, wires=[1]),
+         MultiRZ(0.1, wires=[0, 1]),
+         RX(-1.5707963267948966, wires=[0]),
+         RX(-1.5707963267948966, wires=[1]),
+         RZ(0.1, wires=[0]),
+         RZ(0.1, wires=[1]),
+         Exp(0.1j Identity)]
+        """
+        decomp_ops = [
+            qml.Hadamard(wires=wires[0]),
+            qml.Hadamard(wires=wires[1]),
+            qml.MultiRZ(phi / 2, wires=[wires[0], wires[1]]),
+            qml.Hadamard(wires=wires[0]),
+            qml.Hadamard(wires=wires[1]),
+            qml.RX(np.pi / 2, wires=wires[0]),
+            qml.RX(np.pi / 2, wires=wires[1]),
+            qml.MultiRZ(phi / 2, wires=[wires[0], wires[1]]),
+            qml.RX(-np.pi / 2, wires=wires[0]),
+            qml.RX(-np.pi / 2, wires=wires[1]),
+            qml.RZ(phi / 2, wires=wires[0]),
+            qml.RZ(phi / 2, wires=wires[1]),
+            # for correcting global phase
+            qml.exp(qml.Identity(wires=[wires[0], wires[1]]), 0.5j * phi),
+        ]
+        return decomp_ops
+
+    def adjoint(self):
+        (phi,) = self.parameters
+        return FermionicSWAP(-phi, wires=self.wires)
+
+    def pow(self, z):
+        return [FermionicSWAP(self.data[0] * z, wires=self.wires)]
+
+    def label(self, decimals=None, base_label=None, cache=None):
+        return super().label(decimals=decimals, base_label=base_label or "fSWAP", cache=cache)


Test patch:
diff --git a/pennylane/devices/tests/test_gates.py b/pennylane/devices/tests/test_gates.py
index 9bc136c7d..939a11b4c 100755
--- a/pennylane/devices/tests/test_gates.py
+++ b/pennylane/devices/tests/test_gates.py
@@ -105,6 +105,7 @@ ops = {
     "SISWAP": qml.SISWAP(wires=[0, 1]),
     "Adjoint(SISWAP)": qml.adjoint(qml.SISWAP(wires=[0, 1])),
     "OrbitalRotation": qml.OrbitalRotation(0, wires=[0, 1, 2, 3]),
+    "FermionicSWAP": qml.FermionicSWAP(0, wires=[0, 1]),
 }
 
 all_ops = ops.keys()
diff --git a/tests/devices/test_default_qubit.py b/tests/devices/test_default_qubit.py
index d6a6703bb..7f0c3d463 100644
--- a/tests/devices/test_default_qubit.py
+++ b/tests/devices/test_default_qubit.py
@@ -1912,6 +1912,7 @@ class TestDtypePreserved:
             qml.DoubleExcitationPlus,
             qml.DoubleExcitationMinus,
             qml.OrbitalRotation,
+            qml.FermionicSWAP,
             qml.QubitSum,
             qml.QubitCarry,
         ],
diff --git a/tests/devices/test_default_qubit_broadcasting.py b/tests/devices/test_default_qubit_broadcasting.py
index b0074ee1c..a8e5d972f 100644
--- a/tests/devices/test_default_qubit_broadcasting.py
+++ b/tests/devices/test_default_qubit_broadcasting.py
@@ -1598,6 +1598,7 @@ class TestDtypePreservedBroadcasted:
             # qml.DoubleExcitationPlus,
             # qml.DoubleExcitationMinus,
             # qml.OrbitalRotation,
+            # qml.FermionicSWAP,
             qml.QubitSum,
             qml.QubitCarry,
         ],
diff --git a/tests/devices/test_default_qubit_tf.py b/tests/devices/test_default_qubit_tf.py
index e4caa3248..89d2e3ce7 100644
--- a/tests/devices/test_default_qubit_tf.py
+++ b/tests/devices/test_default_qubit_tf.py
@@ -54,6 +54,7 @@ from gate_data import (
     MultiRZ2,
     ControlledPhaseShift,
     OrbitalRotation,
+    FermionicSWAP,
 )
 
 np.random.seed(42)
@@ -100,6 +101,7 @@ two_qubit_param = [
     (qml.CRZ, CRotz),
     (qml.MultiRZ, MultiRZ2),
     (qml.ControlledPhaseShift, ControlledPhaseShift),
+    (qml.FermionicSWAP, FermionicSWAP),
 ]
 three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP), (qml.CCZ, CCZ)]
 four_qubit_param = [(qml.OrbitalRotation, OrbitalRotation)]
diff --git a/tests/devices/test_default_qubit_torch.py b/tests/devices/test_default_qubit_torch.py
index 1be78ff00..7784c95d2 100644
--- a/tests/devices/test_default_qubit_torch.py
+++ b/tests/devices/test_default_qubit_torch.py
@@ -50,6 +50,7 @@ from gate_data import (
     MultiRZ1,
     MultiRZ2,
     OrbitalRotation,
+    FermionicSWAP,
     Rot3,
     Rotx,
     Roty,
@@ -123,6 +124,7 @@ two_qubit_param = [
     (qml.SingleExcitation, SingleExcitation),
     (qml.SingleExcitationPlus, SingleExcitationPlus),
     (qml.SingleExcitationMinus, SingleExcitationMinus),
+    (qml.FermionicSWAP, FermionicSWAP),
 ]
 three_qubit = [(qml.Toffoli, Toffoli), (qml.CSWAP, CSWAP), (qml.CCZ, CCZ)]
 four_qubit_param = [
diff --git a/tests/gate_data.py b/tests/gate_data.py
index 21f7dce47..e409b979d 100644
--- a/tests/gate_data.py
+++ b/tests/gate_data.py
@@ -700,3 +700,20 @@ def PSWAP(phi):
     """
     e = math.exp(1j * phi)
     return math.array([[1, 0, 0, 0], [0, 0, e, 0], [0, e, 0, 0], [0, 0, 0, 1]])
+
+
+def FermionicSWAP(phi):
+    r"""Fermionic SWAP rotation gate.
+
+    Args:
+        phi (float): rotation angle :math:`\phi`
+    Returns:
+        array[complex]: unitary 4x4 rotation matrix
+    """
+    c = math.cos(phi / 2)
+    s = math.sin(phi / 2)
+    g = math.exp(1j * phi / 2)
+    p = math.exp(1j * phi)
+    return math.array(
+        [[1, 0, 0, 0], [0, g * c, -1j * g * s, 0], [0, -1j * g * s, g * c, 0], [0, 0, 0, p]]
+    )
diff --git a/tests/ops/qubit/test_attributes.py b/tests/ops/qubit/test_attributes.py
index b6061995e..f99825ee0 100644
--- a/tests/ops/qubit/test_attributes.py
+++ b/tests/ops/qubit/test_attributes.py
@@ -108,6 +108,7 @@ single_scalar_multi_wire_ops = [
     "DoubleExcitationPlus",
     "DoubleExcitationMinus",
     "OrbitalRotation",
+    "FermionicSWAP",
 ]
 
 two_scalar_single_wire_ops = [
diff --git a/tests/ops/qubit/test_qchem_ops.py b/tests/ops/qubit/test_qchem_ops.py
index 78f6a8876..94378159e 100644
--- a/tests/ops/qubit/test_qchem_ops.py
+++ b/tests/ops/qubit/test_qchem_ops.py
@@ -33,6 +33,7 @@ from gate_data import (
     DoubleExcitationPlus,
     DoubleExcitationMinus,
     OrbitalRotation,
+    FermionicSWAP,
 )
 
 
@@ -44,6 +45,7 @@ PARAMETRIZED_QCHEM_OPERATIONS = [
     qml.DoubleExcitationMinus(0.14, wires=[0, 1, 2, 3]),
     qml.DoubleExcitationPlus(0.14, wires=[0, 1, 2, 3]),
     qml.OrbitalRotation(0.14, wires=[0, 1, 2, 3]),
+    qml.FermionicSWAP(0.14, wires=[0, 1]),
 ]
 
 
@@ -1056,6 +1058,201 @@ class TestOrbitalRotation:
         assert np.allclose(phi_t.grad, self.expected_grad_fn(phi))
 
 
+class TestFermionicSWAP:
+    @pytest.mark.parametrize("phi", [-0.1, 0.2, np.pi / 4])
+    def test_fermionic_swap_matrix(self, phi):
+        """Tests that the FermionicSWAP operation calculates the correct matrix"""
+        op = qml.FermionicSWAP(phi, wires=[0, 1])
+        res_dynamic = op.matrix()
+        res_static = qml.FermionicSWAP.compute_matrix(phi)
+        exp = FermionicSWAP(phi)
+        assert np.allclose(res_dynamic, exp)
+        assert np.allclose(res_static, exp)
+
+    def test_fermionic_swap_broadcasted(self):
+        """Tests that the FermionicSWAP operation calculates the correct broadcasted matrix"""
+        phi = np.array([-0.1, 0.2, np.pi / 4])
+        op = qml.FermionicSWAP(phi, wires=[0, 1])
+        res_dynamic = op.matrix()
+        res_static = qml.FermionicSWAP.compute_matrix(phi)
+        exp = np.stack([FermionicSWAP(_phi) for _phi in phi])
+        assert np.allclose(res_dynamic, exp)
+        assert np.allclose(res_static, exp)
+
+    @pytest.mark.parametrize("phi", [-0.1, 0.2, np.pi / 4])
+    def test_fermionic_swap_decomp(self, phi):
+        """Tests that the FermionicSWAP operation calculates the correct decomposition"""
+        decomp1 = qml.FermionicSWAP(phi, wires=[0, 1]).decomposition()
+        decomp2 = qml.FermionicSWAP.compute_decomposition(phi, wires=[0, 1])
+
+        for decomp in [decomp1, decomp2]:
+            mats = []
+            for i in reversed(decomp):
+                mats.append(i.matrix(wire_order=[0, 1]))
+
+            decomposed_matrix = np.linalg.multi_dot(mats)
+            exp = FermionicSWAP(phi)
+
+            assert np.allclose(decomposed_matrix, exp)
+
+    @pytest.mark.parametrize("phi", [-0.1, 0.2, np.pi / 4])
+    def test_fermionic_swap_generator(self, phi):
+        """Tests that the FermionicSWAP operation calculates the correct generator"""
+        op = qml.FermionicSWAP(phi, wires=[0, 1])
+        g = qml.matrix(qml.generator(op, format="observable"))
+        res = expm(1j * g * phi)
+        exp = FermionicSWAP(phi)
+        assert np.allclose(res, exp)
+
+    @pytest.mark.parametrize("n", (2, -2, 1.3, -0.6))
+    def test_fermionic_swap_pow(self, n):
+
+        op = qml.FermionicSWAP(1.234, wires=(0, 1))
+
+        pow_ops = op.pow(n)
+        assert len(pow_ops) == 1
+        assert pow_ops[0].__class__ is qml.FermionicSWAP
+
+        mat = qml.matrix(op)
+        pow_mat = qml.matrix(op.pow)(n)
+
+        mat_then_pow = fractional_matrix_power(mat, n)
+        assert qml.math.allclose(pow_mat, mat_then_pow)
+
+    def test_adjoint(self):
+        """Test adjoint method for adjoint op decomposition"""
+
+        phi = 1.234
+        wires = (0, 1)
+        op = qml.FermionicSWAP(phi, wires=wires)
+        adj_op = qml.adjoint(op, lazy=False)
+        assert qml.equal(adj_op, qml.FermionicSWAP(-phi, wires=wires))
+
+    def test_adjoint_integration(self):
+        """Test that the adjoint correctly inverts the fermionic swap operation"""
+
+        dev = qml.device("default.qubit", wires=2)
+
+        @qml.qnode(dev)
+        def circuit(phi):
+            qml.PauliX(wires=0)
+            qml.FermionicSWAP(phi, wires=[0, 1])
+            qml.adjoint(qml.FermionicSWAP)(phi, wires=[0, 1])
+            qml.PauliX(wires=0)
+            return qml.state()
+
+        res = circuit(0.1)
+
+        expected = np.zeros(4)
+        expected[0] = 1.0
+
+        assert np.allclose(res, expected)
+
+    @pytest.mark.autograd
+    def test_autograd(self):
+        """Tests that operations are computed correctly using the
+        autograd interface"""
+
+        dev = qml.device("default.qubit.autograd", wires=2)
+        state = np.array(
+            [
+                0,
+                -1j * np.exp(1j * np.pi / 4) * np.sin(np.pi / 4),
+                np.exp(1j * np.pi / 4) * np.cos(np.pi / 4),
+                0,
+            ]
+        )
+
+        @qml.qnode(dev)
+        def circuit(phi):
+            qml.PauliX(wires=0)
+            qml.FermionicSWAP(phi, wires=[0, 1])
+            return qml.state()
+
+        assert np.allclose(state, circuit(np.pi / 2))
+
+    @pytest.mark.autograd
+    @pytest.mark.parametrize("diff_method", ["parameter-shift", "backprop"])
+    @pytest.mark.parametrize(
+        ("phi"),
+        [
+            pnp.array(-0.1, requires_grad=True),
+            pnp.array(0.2, requires_grad=True),
+            pnp.array(np.pi / 4, requires_grad=True),
+        ],
+    )
+    def test_autograd_grad(self, diff_method, phi):
+        """Tests that gradients are computed correctly using the
+        autograd interface"""
+
+        dev = qml.device("default.qubit.autograd", wires=2)
+
+        @qml.qnode(dev)
+        def circuit(phi):
+            qml.PauliX(wires=0)
+            qml.FermionicSWAP(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
+
+        assert np.allclose(qml.grad(circuit)(phi), np.sin(phi))
+
+    @pytest.mark.tf
+    @pytest.mark.parametrize("diff_method", ["parameter-shift", "backprop"])
+    @pytest.mark.parametrize(
+        ("phi"),
+        [
+            -0.1,
+            0.2,
+            np.pi / 4,
+        ],
+    )
+    def test_tf(self, phi, diff_method):
+        """Tests that gradients and operations are computed correctly using the
+        tensorflow interface"""
+
+        import tensorflow as tf
+
+        dev = qml.device("default.qubit.tf", wires=2)
+
+        @qml.qnode(dev, interface="tf", diff_method=diff_method)
+        def circuit(phi):
+            qml.PauliX(wires=0)
+            qml.FermionicSWAP(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
+
+        phi_t = tf.Variable(phi, dtype=tf.float64)
+        with tf.GradientTape() as tape:
+            res = circuit(phi_t)
+
+        grad = tape.gradient(res, phi_t)
+        assert np.allclose(grad, np.sin(phi))
+
+    @pytest.mark.jax
+    @pytest.mark.parametrize("diff_method", ["parameter-shift", "backprop"])
+    @pytest.mark.parametrize(
+        ("phi"),
+        [
+            -0.1,
+            0.2,
+            np.pi / 4,
+        ],
+    )
+    def test_jax(self, phi, diff_method):
+        """Tests that gradients and operations are computed correctly using the
+        jax interface"""
+
+        import jax
+
+        dev = qml.device("default.qubit.jax", wires=2)
+
+        @qml.qnode(dev, interface="jax", diff_method=diff_method)
+        def circuit(phi):
+            qml.PauliX(wires=0)
+            qml.FermionicSWAP(phi, wires=[0, 1])
+            return qml.expval(qml.PauliZ(0))
+
+        assert np.allclose(jax.grad(circuit)(phi), np.sin(phi))
+
+
 label_data = [
     (qml.SingleExcitation(1.2345, wires=(0, 1)), "G", "G\n(1.23)", "G\n(1)"),
     (qml.SingleExcitationMinus(1.2345, wires=(0, 1)), "G₋", "G₋\n(1.23)", "G₋\n(1)"),
@@ -1069,6 +1266,7 @@ label_data = [
         "OrbitalRotation\n(2.35)",
         "OrbitalRotation\n(2)",
     ),
+    (qml.FermionicSWAP(1.2345, wires=(0, 1)), "fSWAP", "fSWAP\n(1.23)", "fSWAP\n(1)"),
 ]