Repository: Qiskit/qiskit

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.


Discussion:
At the Qiskit dev meeting we decided that this gate is appropriate to add. @ajavadia do you have any thoughts about the name? Again, my suggestion is PhasedXXMinusYYGate.
About the naming: I don't mind which direction we're going into but it would be great if we could be consistent. So if we'd call this one something like XXMinusYY then the current XYGate should also be called XXPlusYY. Or, if we keep XYGate, then this should probably be called BSwap or whatever makes most sense from the paper 😄 
@Cryoris bSwap refers to this gate with the specific parameter settings `theta = pi, beta = 0`, the same parameters that make XYGate equal to iSwap. Since iSwap is a separate gate in Qiskit, to be consistent, bSwap would also be a separate gate. This issue is not about adding bSwap, but rather the analogue of XYGate. The paper does not actually suggest any name for this general gate.

So I think your first suggestion is the only way to be systematic here, and in the future. Specifically:
- Name this gate PhasedXXMinusYYGate
- Rename XYGate to PhasedXXPlusYYGate
This sounds good to me.
Actually, do we need the "Phased" prefix? `XXPlusYYGate` would be shorter (and that's already pretty long!). Also, `XXpYYGate` or `XXmYYGate` would be even other shorter variants which would be consistent with our current naming structure where one letter stands for one gate characteristic (e.g. we have `SX` and not `SqrtX`). 
Omitting the "Phased" prefix is misleading because `XXPlusYYGate` sounds like a single-parameter gate
```
exp(-i theta / 2 (XX+YY / 2))
```

I agree that shortening `plus` to `p` for `XXpYYGate` is more consistent with other names in Qiskit, but I also think the resulting name is pretty obscure (I find `SX` to be obscure too).

@Cryoris Do you feel strongly about either of these? If so, I will defer to you.
@Cryoris I went ahead and opened #7786 . I took the first of your suggestions and used the name `XXPlusYYGate`.

Resolving patch:
diff --git a/qiskit/circuit/library/standard_gates/__init__.py b/qiskit/circuit/library/standard_gates/__init__.py
--- a/qiskit/circuit/library/standard_gates/__init__.py
+++ b/qiskit/circuit/library/standard_gates/__init__.py
@@ -49,6 +49,7 @@
    RZGate
    RZZGate
    RZXGate
+   XXMinusYYGate
    XXPlusYYGate
    ECRGate
    SGate
@@ -80,6 +81,7 @@
 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
--- /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,
+        )


Test patch:
diff --git a/test/python/circuit/test_extensions_standard.py b/test/python/circuit/test_extensions_standard.py
--- a/test/python/circuit/test_extensions_standard.py
+++ b/test/python/circuit/test_extensions_standard.py
@@ -16,13 +16,27 @@
 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 @@ def test_z_reg_inv(self):
         self.assertEqual(instruction_set.instructions[2].params, [])
 
 
+@ddt
 class TestStandard2Q(QiskitTestCase):
     """Standard Extension Test. Gates with two Qubits"""
 
@@ -1325,6 +1340,67 @@ def test_swap_reg_reg_inv(self):
         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
--- a/test/python/circuit/test_gate_definitions.py
+++ b/test/python/circuit/test_gate_definitions.py
@@ -62,6 +62,7 @@
     SXdgGate,
     CSXGate,
     RVGate,
+    XXMinusYYGate,
 )
 
 from qiskit.circuit.library.standard_gates.equivalence_library import (
@@ -173,6 +174,16 @@ def test_rv_zero(self):
         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):