Skip to content

Commit bf0136c

Browse files
Add XXMinusYYGate (Qiskit#7799)
* add XXMinusYYGate * update docs and release note * move tests to existing files * update docs and release note Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
1 parent 95c2aa2 commit bf0136c

File tree

5 files changed

+267
-1
lines changed

5 files changed

+267
-1
lines changed

qiskit/circuit/library/standard_gates/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@
4949
RZGate
5050
RZZGate
5151
RZXGate
52+
XXMinusYYGate
5253
XXPlusYYGate
5354
ECRGate
5455
SGate
@@ -80,6 +81,7 @@
8081
from .rz import RZGate, CRZGate
8182
from .rzz import RZZGate
8283
from .rzx import RZXGate
84+
from .xx_minus_yy import XXMinusYYGate
8385
from .xx_plus_yy import XXPlusYYGate
8486
from .ecr import ECRGate
8587
from .s import SGate, SdgGate
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# This code is part of Qiskit.
2+
#
3+
# (C) Copyright IBM 2021.
4+
#
5+
# This code is licensed under the Apache License, Version 2.0. You may
6+
# obtain a copy of this license in the LICENSE.txt file in the root directory
7+
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
8+
#
9+
# Any modifications or derivative works of this code must retain this
10+
# copyright notice, and modified files need to carry a notice indicating
11+
# that they have been altered from the originals.
12+
13+
"""Two-qubit XX-YY gate."""
14+
15+
from typing import Optional
16+
17+
import numpy as np
18+
19+
from qiskit.circuit.gate import Gate
20+
from qiskit.circuit.library.standard_gates.ry import RYGate
21+
from qiskit.circuit.library.standard_gates.rz import RZGate
22+
from qiskit.circuit.library.standard_gates.s import SdgGate, SGate
23+
from qiskit.circuit.library.standard_gates.sx import SXdgGate, SXGate
24+
from qiskit.circuit.library.standard_gates.x import CXGate
25+
from qiskit.circuit.parameterexpression import ParameterValueType
26+
from qiskit.circuit.quantumcircuit import QuantumCircuit
27+
from qiskit.circuit.quantumregister import QuantumRegister
28+
from qiskit.qasm import pi
29+
30+
31+
class XXMinusYYGate(Gate):
32+
r"""XX-YY interaction gate.
33+
34+
A 2-qubit parameterized XX-YY interaction. Its action is to induce
35+
a coherent rotation by some angle between :math:`|00\rangle` and :math:`|11\rangle`.
36+
37+
**Circuit Symbol:**
38+
39+
.. parsed-literal::
40+
41+
┌───────────────┐
42+
q_0: ┤0 ├
43+
│ {XX-YY}(θ,β) │
44+
q_1: ┤1 ├
45+
└───────────────┘
46+
47+
**Matrix Representation:**
48+
49+
.. math::
50+
51+
\newcommand{\th}{\frac{\theta}{2}}
52+
53+
R_{XX-YY}(\theta, \beta) q_0, q_1 =
54+
RZ_1(\beta) \cdot exp(-i \frac{\theta}{2} \frac{XX-YY}{2}) \cdot RZ_1(-\beta) =
55+
\begin{pmatrix}
56+
\cos(\th) & 0 & 0 & -i\sin(\th)e^{-i\beta} \\
57+
0 & 1 & 0 & 0 \\
58+
0 & 0 & 1 & 0 \\
59+
-i\sin(\th)e^{i\beta} & 0 & 0 & \cos(\th)
60+
\end{pmatrix}
61+
62+
.. note::
63+
64+
In Qiskit's convention, higher qubit indices are more significant
65+
(little endian convention). In the above example we apply the gate
66+
on (q_0, q_1) which results in adding the (optional) phase defined
67+
by :math:`beta` on q_1. Instead, if we apply it on (q_1, q_0), the
68+
phase is added on q_0. If :math:`beta` is set to its default value
69+
of :math:`0`, the gate is equivalent in big and little endian.
70+
71+
.. parsed-literal::
72+
73+
┌───────────────┐
74+
q_0: ┤1 ├
75+
│ {XX-YY}(θ,β) │
76+
q_1: ┤0 ├
77+
└───────────────┘
78+
79+
.. math::
80+
81+
\newcommand{\th}{\frac{\theta}{2}}
82+
83+
R_{XX-YY}(\theta, \beta) q_1, q_0 =
84+
RZ_0(\beta) \cdot exp(-i \frac{\theta}{2} \frac{XX-YY}{2}) \cdot RZ_0(-\beta) =
85+
\begin{pmatrix}
86+
\cos(\th) & 0 & 0 & -i\sin(\th)e^{i\beta} \\
87+
0 & 1 & 0 & 0 \\
88+
0 & 0 & 1 & 0 \\
89+
-i\sin(\th)e^{-i\beta} & 0 & 0 & \cos(\th)
90+
\end{pmatrix}
91+
"""
92+
93+
def __init__(
94+
self,
95+
theta: ParameterValueType,
96+
beta: ParameterValueType = 0,
97+
label: Optional[str] = "{XX-YY}",
98+
):
99+
"""Create new XX-YY gate.
100+
101+
Args:
102+
theta: The rotation angle.
103+
beta: The phase angle.
104+
label: The label of the gate.
105+
"""
106+
super().__init__("xx_minus_yy", 2, [theta, beta], label=label)
107+
108+
def _define(self):
109+
"""
110+
gate xx_minus_yy(theta, beta) a, b {
111+
rz(-beta) b;
112+
rz(-pi/2) a;
113+
sx a;
114+
rz(pi/2) a;
115+
s b;
116+
cx a, b;
117+
ry(theta/2) a;
118+
ry(-theta/2) b;
119+
cx a, b;
120+
sdg b;
121+
rz(-pi/2) a;
122+
sxdg a;
123+
rz(pi/2) a;
124+
rz(beta) b;
125+
}
126+
"""
127+
theta, beta = self.params
128+
register = QuantumRegister(2, "q")
129+
circuit = QuantumCircuit(register, name=self.name)
130+
a, b = register
131+
rules = [
132+
(RZGate(-beta), [b], []),
133+
(RZGate(-pi / 2), [a], []),
134+
(SXGate(), [a], []),
135+
(RZGate(pi / 2), [a], []),
136+
(SGate(), [b], []),
137+
(CXGate(), [a, b], []),
138+
(RYGate(theta / 2), [a], []),
139+
(RYGate(-theta / 2), [b], []),
140+
(CXGate(), [a, b], []),
141+
(SdgGate(), [b], []),
142+
(RZGate(-pi / 2), [a], []),
143+
(SXdgGate(), [a], []),
144+
(RZGate(pi / 2), [a], []),
145+
(RZGate(beta), [b], []),
146+
]
147+
for instr, qargs, cargs in rules:
148+
circuit._append(instr, qargs, cargs)
149+
150+
self.definition = circuit
151+
152+
def inverse(self):
153+
"""Inverse gate."""
154+
theta, beta = self.params
155+
return XXMinusYYGate(-theta, beta)
156+
157+
def __array__(self, dtype=None):
158+
"""Gate matrix."""
159+
theta, beta = self.params
160+
cos = np.cos(theta / 2)
161+
sin = np.sin(theta / 2)
162+
return np.array(
163+
[
164+
[cos, 0, 0, -1j * sin * np.exp(-1j * beta)],
165+
[0, 1, 0, 0],
166+
[0, 0, 1, 0],
167+
[-1j * sin * np.exp(1j * beta), 0, 0, cos],
168+
],
169+
dtype=dtype,
170+
)
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
features:
3+
- |
4+
Adds a gate for the XX-YY interaction, accessible via
5+
:class:`qiskit.circuit.library.XXMinusYYGate`. This gate can be used
6+
to implement the bSwap gate and its powers. It also arises
7+
in the simulation of superconducting fermionic models.

test/python/circuit/test_extensions_standard.py

Lines changed: 77 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,27 @@
1616
from inspect import signature
1717
import warnings
1818

19+
import numpy as np
20+
from scipy.linalg import expm
21+
from ddt import data, ddt, unpack
22+
1923
from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, execute
2024
from qiskit.qasm import pi
2125
from qiskit.exceptions import QiskitError
2226
from qiskit.circuit.exceptions import CircuitError
2327
from qiskit.test import QiskitTestCase
2428
from qiskit.circuit import Gate, ControlledGate
25-
from qiskit.circuit.library import U1Gate, U2Gate, U3Gate, CU1Gate, CU3Gate
29+
from qiskit.circuit.library import (
30+
U1Gate,
31+
U2Gate,
32+
U3Gate,
33+
CU1Gate,
34+
CU3Gate,
35+
XXMinusYYGate,
36+
RZGate,
37+
XGate,
38+
YGate,
39+
)
2640
from qiskit import BasicAer
2741
from qiskit.quantum_info import Pauli
2842
from qiskit.quantum_info.operators.predicates import matrix_equal, is_unitary_matrix
@@ -978,6 +992,7 @@ def test_z_reg_inv(self):
978992
self.assertEqual(instruction_set.instructions[2].params, [])
979993

980994

995+
@ddt
981996
class TestStandard2Q(QiskitTestCase):
982997
"""Standard Extension Test. Gates with two Qubits"""
983998

@@ -1325,6 +1340,67 @@ def test_swap_reg_reg_inv(self):
13251340
self.assertEqual(instruction_set.qargs[1], [self.qr[1], self.qr2[1]])
13261341
self.assertEqual(instruction_set.instructions[2].params, [])
13271342

1343+
@unpack
1344+
@data(
1345+
(0, 0, np.eye(4)),
1346+
(
1347+
np.pi / 2,
1348+
np.pi / 2,
1349+
np.array(
1350+
[
1351+
[np.sqrt(2) / 2, 0, 0, -np.sqrt(2) / 2],
1352+
[0, 1, 0, 0],
1353+
[0, 0, 1, 0],
1354+
[np.sqrt(2) / 2, 0, 0, np.sqrt(2) / 2],
1355+
]
1356+
),
1357+
),
1358+
(
1359+
np.pi,
1360+
np.pi / 2,
1361+
np.array([[0, 0, 0, -1], [0, 1, 0, 0], [0, 0, 1, 0], [1, 0, 0, 0]]),
1362+
),
1363+
(
1364+
2 * np.pi,
1365+
np.pi / 2,
1366+
np.array([[-1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, -1]]),
1367+
),
1368+
(
1369+
np.pi / 2,
1370+
np.pi,
1371+
np.array(
1372+
[
1373+
[np.sqrt(2) / 2, 0, 0, 1j * np.sqrt(2) / 2],
1374+
[0, 1, 0, 0],
1375+
[0, 0, 1, 0],
1376+
[1j * np.sqrt(2) / 2, 0, 0, np.sqrt(2) / 2],
1377+
]
1378+
),
1379+
),
1380+
(4 * np.pi, 0, np.eye(4)),
1381+
)
1382+
def test_xx_minus_yy_matrix(self, theta: float, beta: float, expected: np.ndarray):
1383+
"""Test XX-YY matrix."""
1384+
gate = XXMinusYYGate(theta, beta)
1385+
np.testing.assert_allclose(np.array(gate), expected, atol=1e-7)
1386+
1387+
def test_xx_minus_yy_exponential_formula(self):
1388+
"""Test XX-YY exponential formula."""
1389+
theta, beta = np.random.uniform(-10, 10, size=2)
1390+
theta = np.pi / 2
1391+
beta = 0.0
1392+
gate = XXMinusYYGate(theta, beta)
1393+
x = np.array(XGate())
1394+
y = np.array(YGate())
1395+
xx = np.kron(x, x)
1396+
yy = np.kron(y, y)
1397+
rz1 = np.kron(np.array(RZGate(beta)), np.eye(2))
1398+
np.testing.assert_allclose(
1399+
np.array(gate),
1400+
rz1 @ expm(-0.25j * theta * (xx - yy)) @ rz1.T.conj(),
1401+
atol=1e-7,
1402+
)
1403+
13281404

13291405
class TestStandard3Q(QiskitTestCase):
13301406
"""Standard Extension Test. Gates with three Qubits"""

test/python/circuit/test_gate_definitions.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
SXdgGate,
6363
CSXGate,
6464
RVGate,
65+
XXMinusYYGate,
6566
)
6667

6768
from qiskit.circuit.library.standard_gates.equivalence_library import (
@@ -173,6 +174,16 @@ def test_rv_zero(self):
173174
rv = RVGate(0, 0, 0)
174175
self.assertTrue(np.array_equal(rv.to_matrix(), np.array([[1, 0], [0, 1]])))
175176

177+
def test_xx_minus_yy_definition(self):
178+
"""Test XX-YY gate decomposition."""
179+
theta, beta = np.random.uniform(-10, 10, size=2)
180+
gate = XXMinusYYGate(theta, beta)
181+
circuit = QuantumCircuit(2)
182+
circuit.append(gate, [0, 1])
183+
decomposed_circuit = circuit.decompose()
184+
self.assertTrue(len(decomposed_circuit) > len(circuit))
185+
self.assertTrue(Operator(circuit).equiv(Operator(decomposed_circuit), atol=1e-7))
186+
176187

177188
@ddt
178189
class TestStandardGates(QiskitTestCase):

0 commit comments

Comments
 (0)