Repository: Qiskit/qiskit-terra (python)

Issue:
Phased XX - YY interaction gate
### What should we add?

Gate for phased XX-YY interaction. This gate has the same exact definition as [XYGate](https://github.com/Qiskit/qiskit-terra/blob/517265c26e91baac44a690fafe1f473ad89d70b2/qiskit/circuit/library/standard_gates/xy.py) but with XX-YY instead of XX+YY. It is equivalent up to two single-qubit X rotations, but the reason to have it as a separate gate is that those extra single-qubit gates aren't necessary for the decomposition. The decomposition can be obtained from the XYGate decomposition by tweaking some signs in the gate angles.

An external reference for this gate is https://arxiv.org/abs/1208.1287.

I'm happy to contribute this gate if this proposal is accepted and the name agreed upon. I suggest PhasedXXMinusYYGate.

Pull request:
Add XXMinusYYGate
<!--
⚠️ If you do not respect this template, your pull request will be closed.
⚠️ Your pull request title should be short detailed and understandable for all.
⚠️ Also, please add a release note file using reno if the change needs to be
  documented in the release notes.
⚠️ If your pull request fixes an open issue, please link to the issue.

- [X] I have added the tests to cover my changes.
- [X] I have updated the documentation accordingly.
- [X] I have read the CONTRIBUTING document.
-->

### Summary
Fixes #7635.


### Details and comments
@Cryoris 

Patch:
diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py
index af1a76cc3..c3d464d66 100644
--- a/qiskit/circuit/library/standard_gates/__init__.py
+++ b/qiskit/circuit/library/standard_gates/__init__.py
@@ -49,6 +49,7 @@ Standard gates (:mod:`qiskit.circuit.library.standard_gates`)
    RZGate
    RZZGate
    RZXGate
+   XXMinusYYGate
    XXPlusYYGate
    ECRGate
    SGate
@@ -80,6 +81,7 @@ from .ryy import RYYGate
 from .rz import RZGate, CRZGate
 from .rzz import RZZGate
 from .rzx import RZXGate
+from .xx_minus_yy import XXMinusYYGate
 from .xx_plus_yy import XXPlusYYGate
 from .ecr import ECRGate
 from .s import SGate, SdgGate
diff --git a/qiskit/circuit/library/standard_gates/xx_minus_yy.py b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
new file mode 100644
index 000000000..a718ff59b
--- /dev/null
+++ b/qiskit/circuit/library/standard_gates/xx_minus_yy.py
@@ -0,0 +1,170 @@
+# This code is part of Qiskit.
+#
+# (C) Copyright IBM 2021.
+#
+# This code is licensed under the Apache License, Version 2.0. You may
+# obtain a copy of this license in the LICENSE.txt file in the root directory
+# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
+#
+# Any modifications or derivative works of this code must retain this
+# copyright notice, and modified files need to carry a notice indicating
+# that they have been altered from the originals.
+
+"""Two-qubit XX-YY gate."""
+
+from typing import Optional
+
+import numpy as np
+
+from qiskit.circuit.gate import Gate
+from qiskit.circuit.library.standard_gates.ry import RYGate
+from qiskit.circuit.library.standard_gates.rz import RZGate
+from qiskit.circuit.library.standard_gates.s import SdgGate, SGate
+from qiskit.circuit.library.standard_gates.sx import SXdgGate, SXGate
+from qiskit.circuit.library.standard_gates.x import CXGate
+from qiskit.circuit.parameterexpression import ParameterValueType
+from qiskit.circuit.quantumcircuit import QuantumCircuit
+from qiskit.circuit.quantumregister import QuantumRegister
+from qiskit.qasm import pi
+
+
+class XXMinusYYGate(Gate):
+    r"""XX-YY interaction gate.
+
+    A 2-qubit parameterized XX-YY interaction. Its action is to induce
+    a coherent rotation by some angle between :math:`|00\rangle` and :math:`|11\rangle`.
+
+    **Circuit Symbol:**
+
+    .. parsed-literal::
+
+             ┌───────────────┐
+        q_0: ┤0              ├
+             │  {XX-YY}(θ,β) │
+        q_1: ┤1              ├
+             └───────────────┘
+
+    **Matrix Representation:**
+
+    .. math::
+
+        \newcommand{\th}{\frac{\theta}{2}}
+
+        R_{XX-YY}(\theta, \beta) q_0, q_1 =
+          RZ_1(\beta) \cdot exp(-i \frac{\theta}{2} \frac{XX-YY}{2}) \cdot RZ_1(-\beta) =
+            \begin{pmatrix}
+                \cos(\th)             & 0 & 0 & -i\sin(\th)e^{-i\beta}  \\
+                0                     & 1 & 0 & 0  \\
+                0                     & 0 & 1 & 0  \\
+                -i\sin(\th)e^{i\beta} & 0 & 0 & \cos(\th)
+            \end{pmatrix}
+
+    .. note::
+
+        In Qiskit's convention, higher qubit indices are more significant
+        (little endian convention). In the above example we apply the gate
+        on (q_0, q_1) which results in adding the (optional) phase defined
+        by :math:`beta` on q_1. Instead, if we apply it on (q_1, q_0), the
+        phase is added on q_0. If :math:`beta` is set to its default value
+        of :math:`0`, the gate is equivalent in big and little endian.
+
+        .. parsed-literal::
+
+                 ┌───────────────┐
+            q_0: ┤1              ├
+                 │  {XX-YY}(θ,β) │
+            q_1: ┤0              ├
+                 └───────────────┘
+
+        .. math::
+
+            \newcommand{\th}{\frac{\theta}{2}}
+
+            R_{XX-YY}(\theta, \beta) q_1, q_0 =
+            RZ_0(\beta) \cdot exp(-i \frac{\theta}{2} \frac{XX-YY}{2}) \cdot RZ_0(-\beta) =
+                \begin{pmatrix}
+                    \cos(\th)             & 0 & 0 & -i\sin(\th)e^{i\beta}  \\
+                    0                     & 1 & 0 & 0  \\
+                    0                     & 0 & 1 & 0  \\
+                    -i\sin(\th)e^{-i\beta} & 0 & 0 & \cos(\th)
+                \end{pmatrix}
+    """
+
+    def __init__(
+        self,
+        theta: ParameterValueType,
+        beta: ParameterValueType = 0,
+        label: Optional[str] = "{XX-YY}",
+    ):
+        """Create new XX-YY gate.
+
+        Args:
+            theta: The rotation angle.
+            beta: The phase angle.
+            label: The label of the gate.
+        """
+        super().__init__("xx_minus_yy", 2, [theta, beta], label=label)
+
+    def _define(self):
+        """
+        gate xx_minus_yy(theta, beta) a, b {
+            rz(-beta) b;
+            rz(-pi/2) a;
+            sx a;
+            rz(pi/2) a;
+            s b;
+            cx a, b;
+            ry(theta/2) a;
+            ry(-theta/2) b;
+            cx a, b;
+            sdg b;
+            rz(-pi/2) a;
+            sxdg a;
+            rz(pi/2) a;
+            rz(beta) b;
+        }
+        """
+        theta, beta = self.params
+        register = QuantumRegister(2, "q")
+        circuit = QuantumCircuit(register, name=self.name)
+        a, b = register
+        rules = [
+            (RZGate(-beta), [b], []),
+            (RZGate(-pi / 2), [a], []),
+            (SXGate(), [a], []),
+            (RZGate(pi / 2), [a], []),
+            (SGate(), [b], []),
+            (CXGate(), [a, b], []),
+            (RYGate(theta / 2), [a], []),
+            (RYGate(-theta / 2), [b], []),
+            (CXGate(), [a, b], []),
+            (SdgGate(), [b], []),
+            (RZGate(-pi / 2), [a], []),
+            (SXdgGate(), [a], []),
+            (RZGate(pi / 2), [a], []),
+            (RZGate(beta), [b], []),
+        ]
+        for instr, qargs, cargs in rules:
+            circuit._append(instr, qargs, cargs)
+
+        self.definition = circuit
+
+    def inverse(self):
+        """Inverse gate."""
+        theta, beta = self.params
+        return XXMinusYYGate(-theta, beta)
+
+    def __array__(self, dtype=None):
+        """Gate matrix."""
+        theta, beta = self.params
+        cos = np.cos(theta / 2)
+        sin = np.sin(theta / 2)
+        return np.array(
+            [
+                [cos, 0, 0, -1j * sin * np.exp(-1j * beta)],
+                [0, 1, 0, 0],
+                [0, 0, 1, 0],
+                [-1j * sin * np.exp(1j * beta), 0, 0, cos],
+            ],
+            dtype=dtype,
+        )
diff --git a/releasenotes/notes/add-xxminusyy-gate-63e6530c23500de9.yaml b/releasenotes/notes/add-xxminusyy-gate-63e6530c23500de9.yaml
new file mode 100644
index 000000000..6516ba611
--- /dev/null
+++ b/releasenotes/notes/add-xxminusyy-gate-63e6530c23500de9.yaml
@@ -0,0 +1,7 @@
+---
+features:
+  - |
+    Adds a gate for the XX-YY interaction, accessible via
+    :class:`qiskit.circuit.library.XXMinusYYGate`. This gate can be used
+    to implement the bSwap gate and its powers. It also arises
+    in the simulation of superconducting fermionic models.
\ No newline at end of file


Test patch:
diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py
index f8d37e9a9..152b73e6b 100644
--- a/test/python/circuit/test_extensions_standard.py
+++ b/test/python/circuit/test_extensions_standard.py
@@ -16,13 +16,27 @@ import unittest
 from inspect import signature
 import warnings
 
+import numpy as np
+from scipy.linalg import expm
+from ddt import data, ddt, unpack
+
 from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, execute
 from qiskit.qasm import pi
 from qiskit.exceptions import QiskitError
 from qiskit.circuit.exceptions import CircuitError
 from qiskit.test import QiskitTestCase
 from qiskit.circuit import Gate, ControlledGate
-from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate
+from qiskit.circuit.library import (
+    U1Gate,
+    U2Gate,
+    U3Gate,
+    CU1Gate,
+    CU3Gate,
+    XXMinusYYGate,
+    RZGate,
+    XGate,
+    YGate,
+)
 from qiskit import BasicAer
 from qiskit.quantum_info import Pauli
 from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix
@@ -978,6 +992,7 @@ class TestStandard1Q(QiskitTestCase):
         self.assertEqual(instruction_set.instructions[2].params, [])
 
 
+@ddt
 class TestStandard2Q(QiskitTestCase):
     """Standard Extension Test. Gates with two Qubits"""
 
@@ -1325,6 +1340,67 @@ class TestStandard2Q(QiskitTestCase):
         self.assertEqual(instruction_set.qargs[1], [self.qr[1], self.qr2[1]])
         self.assertEqual(instruction_set.instructions[2].params, [])
 
+    @unpack
+    @data(
+        (0, 0, np.eye(4)),
+        (
+            np.pi / 2,
+            np.pi / 2,
+            np.array(
+                [
+                    [np.sqrt(2) / 2, 0, 0, -np.sqrt(2) / 2],
+                    [0, 1, 0, 0],
+                    [0, 0, 1, 0],
+                    [np.sqrt(2) / 2, 0, 0, np.sqrt(2) / 2],
+                ]
+            ),
+        ),
+        (
+            np.pi,
+            np.pi / 2,
+            np.array([[0, 0, 0, -1], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0]]),
+        ),
+        (
+            2 * np.pi,
+            np.pi / 2,
+            np.array([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]]),
+        ),
+        (
+            np.pi / 2,
+            np.pi,
+            np.array(
+                [
+                    [np.sqrt(2) / 2, 0, 0, 1j * np.sqrt(2) / 2],
+                    [0, 1, 0, 0],
+                    [0, 0, 1, 0],
+                    [1j * np.sqrt(2) / 2, 0, 0, np.sqrt(2) / 2],
+                ]
+            ),
+        ),
+        (4 * np.pi, 0, np.eye(4)),
+    )
+    def test_xx_minus_yy_matrix(self, theta: float, beta: float, expected: np.ndarray):
+        """Test XX-YY matrix."""
+        gate = XXMinusYYGate(theta, beta)
+        np.testing.assert_allclose(np.array(gate), expected, atol=1e-7)
+
+    def test_xx_minus_yy_exponential_formula(self):
+        """Test XX-YY exponential formula."""
+        theta, beta = np.random.uniform(-10, 10, size=2)
+        theta = np.pi / 2
+        beta = 0.0
+        gate = XXMinusYYGate(theta, beta)
+        x = np.array(XGate())
+        y = np.array(YGate())
+        xx = np.kron(x, x)
+        yy = np.kron(y, y)
+        rz1 = np.kron(np.array(RZGate(beta)), np.eye(2))
+        np.testing.assert_allclose(
+            np.array(gate),
+            rz1 @ expm(-0.25j * theta * (xx - yy)) @ rz1.T.conj(),
+            atol=1e-7,
+        )
+
 
 class TestStandard3Q(QiskitTestCase):
     """Standard Extension Test. Gates with three Qubits"""
diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py
index 2a46f59a2..cc584eacf 100644
--- a/test/python/circuit/test_gate_definitions.py
+++ b/test/python/circuit/test_gate_definitions.py
@@ -62,6 +62,7 @@ from qiskit.circuit.library import (
     SXdgGate,
     CSXGate,
     RVGate,
+    XXMinusYYGate,
 )
 
 from qiskit.circuit.library.standard_gates.equivalence_library import (
@@ -173,6 +174,16 @@ class TestGateDefinitions(QiskitTestCase):
         rv = RVGate(0, 0, 0)
         self.assertTrue(np.array_equal(rv.to_matrix(), np.array([[1, 0], [0, 1]])))
 
+    def test_xx_minus_yy_definition(self):
+        """Test XX-YY gate decomposition."""
+        theta, beta = np.random.uniform(-10, 10, size=2)
+        gate = XXMinusYYGate(theta, beta)
+        circuit = QuantumCircuit(2)
+        circuit.append(gate, [0, 1])
+        decomposed_circuit = circuit.decompose()
+        self.assertTrue(len(decomposed_circuit) > len(circuit))
+        self.assertTrue(Operator(circuit).equiv(Operator(decomposed_circuit), atol=1e-7))
+
 
 @ddt
 class TestStandardGates(QiskitTestCase):