Add missing equivalences for XX{Plus,Minus}YYGate (#9017)

* Add missing equivalences for XX{Plus,Minus}YYGate

These are just copied from the `_define` method on the classes.  The
equivalence-library tests are updated (since `subTest` doesn't isolate
correctly with `testtools`) to print out all the names of the tests
being run, so it's clear that the existing test now tests these
definitions.

* Use default parameters to beat the scoping weirdness

Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
Jake Lishman 2022-10-28 13:58:11 +01:00 committed by GitHub
parent 72ba2ee259
commit b45afea036
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 122 additions and 51 deletions

View File

@ -64,6 +64,8 @@ from . import (
ZGate,
CZGate,
CCZGate,
XXPlusYYGate,
XXMinusYYGate,
)
@ -1315,3 +1317,73 @@ h_to_rr.append(RGate(theta=pi / 2, phi=pi / 2), [q[0]])
h_to_rr.append(RGate(theta=pi, phi=0), [q[0]])
h_to_rr.global_phase = pi / 2
_sel.add_equivalence(HGate(), h_to_rr)
# XXPlusYYGate
# ┌───────────────┐
# ┤0 ├
# │ {XX+YY}(θ,β) │
# ┤1 ├
# └───────────────┘
# ┌───────┐ ┌───┐ ┌───┐┌────────────┐┌───┐ ┌─────┐ ┌────────────┐
# ─┤ Rz(β) ├──┤ S ├────────────┤ X ├┤ Ry(-0.5*θ) ├┤ X ├──┤ Sdg ├───┤ Rz(-1.0*β) ├───────────
# ≡ ┌┴───────┴─┐├───┴┐┌─────────┐└─┬─┘├────────────┤└─┬─┘┌─┴─────┴──┐└──┬──────┬──┘┌─────────┐
# ┤ Rz(-π/2) ├┤ √X ├┤ Rz(π/2) ├──■──┤ Ry(-0.5*θ) ├──■──┤ Rz(-π/2) ├───┤ √Xdg ├───┤ Rz(π/2) ├
# └──────────┘└────┘└─────────┘ └────────────┘ └──────────┘ └──────┘ └─────────┘
q = QuantumRegister(2, "q")
xxplusyy = QuantumCircuit(q)
beta = Parameter("beta")
theta = Parameter("theta")
rules = [
(RZGate(beta), [q[0]], []),
(RZGate(-pi / 2), [q[1]], []),
(SXGate(), [q[1]], []),
(RZGate(pi / 2), [q[1]], []),
(SGate(), [q[0]], []),
(CXGate(), [q[1], q[0]], []),
(RYGate(-theta / 2), [q[1]], []),
(RYGate(-theta / 2), [q[0]], []),
(CXGate(), [q[1], q[0]], []),
(SdgGate(), [q[0]], []),
(RZGate(-pi / 2), [q[1]], []),
(SXdgGate(), [q[1]], []),
(RZGate(pi / 2), [q[1]], []),
(RZGate(-beta), [q[0]], []),
]
for instr, qargs, cargs in rules:
xxplusyy._append(instr, qargs, cargs)
_sel.add_equivalence(XXPlusYYGate(theta, beta), xxplusyy)
# XXMinusYYGate
# ┌───────────────┐
# ┤0 ├
# │ {XX-YY}(θ,β) │
# ┤1 ├
# └───────────────┘
# ┌──────────┐ ┌────┐┌─────────┐ ┌─────────┐ ┌──────────┐ ┌──────┐┌─────────┐
# ─┤ Rz(-π/2) ├─┤ √X ├┤ Rz(π/2) ├──■───┤ Ry(θ/2) ├────■──┤ Rz(-π/2) ├─┤ √Xdg ├┤ Rz(π/2) ├
# ≡ ┌┴──────────┴┐├───┬┘└─────────┘┌─┴─┐┌┴─────────┴─┐┌─┴─┐└─┬─────┬──┘┌┴──────┤└─────────┘
# ┤ Rz(-1.0*β) ├┤ S ├────────────┤ X ├┤ Ry(-0.5*θ) ├┤ X ├──┤ Sdg ├───┤ Rz(β) ├───────────
# └────────────┘└───┘ └───┘└────────────┘└───┘ └─────┘ └───────┘
q = QuantumRegister(2, "q")
xxminusyy = QuantumCircuit(q)
beta = Parameter("beta")
theta = Parameter("theta")
rules = [
(RZGate(-beta), [q[1]], []),
(RZGate(-pi / 2), [q[0]], []),
(SXGate(), [q[0]], []),
(RZGate(pi / 2), [q[0]], []),
(SGate(), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(RYGate(theta / 2), [q[0]], []),
(RYGate(-theta / 2), [q[1]], []),
(CXGate(), [q[0], q[1]], []),
(SdgGate(), [q[1]], []),
(RZGate(-pi / 2), [q[0]], []),
(SXdgGate(), [q[0]], []),
(RZGate(pi / 2), [q[0]], []),
(RZGate(beta), [q[1]], []),
]
for instr, qargs, cargs in rules:
xxminusyy._append(instr, qargs, cargs)
_sel.add_equivalence(XXMinusYYGate(theta, beta), xxminusyy)

View File

@ -0,0 +1,5 @@
---
features:
- |
Equivalence rules for :class:`.XXPlusYYGate` and :class:`.XXMinusYYGate` are
now available in the standard equivalence library shipped with Qiskit Terra.

View File

@ -16,7 +16,7 @@
import inspect
import numpy as np
from ddt import ddt, data, unpack
from ddt import ddt, data, idata, unpack
from qiskit import QuantumCircuit, QuantumRegister
from qiskit.quantum_info import Operator
@ -256,63 +256,57 @@ class TestStandardGates(QiskitTestCase):
self.assertTrue(is_identity_matrix(Operator(gate).dot(gate.inverse().definition).data))
@ddt
class TestGateEquivalenceEqual(QiskitTestCase):
"""Test the decomposition of a gate in terms of other gates
yields the same matrix as the hardcoded matrix definition."""
@classmethod
def setUpClass(cls):
super().setUpClass()
class_list = Gate.__subclasses__() + ControlledGate.__subclasses__()
exclude = {
"ControlledGate",
"DiagonalGate",
"UCGate",
"MCGupDiag",
"MCU1Gate",
"UnitaryGate",
"HamiltonianGate",
"MCPhaseGate",
"UCPauliRotGate",
"SingleQubitUnitary",
"MCXGate",
"VariadicZeroParamGate",
"ClassicalFunction",
"ClassicalElement",
"StatePreparation",
"LinearFunction",
"Commuting2qBlock",
}
cls._gate_classes = []
for aclass in class_list:
if aclass.__name__ not in exclude:
cls._gate_classes.append(aclass)
def test_equivalence_phase(self):
class_list = Gate.__subclasses__() + ControlledGate.__subclasses__()
exclude = {
"ControlledGate",
"DiagonalGate",
"UCGate",
"MCGupDiag",
"MCU1Gate",
"UnitaryGate",
"HamiltonianGate",
"MCPhaseGate",
"UCPauliRotGate",
"SingleQubitUnitary",
"MCXGate",
"VariadicZeroParamGate",
"ClassicalFunction",
"ClassicalElement",
"StatePreparation",
"LinearFunction",
"Commuting2qBlock",
"PauliEvolutionGate",
}
# Amazingly, Python's scoping rules for class bodies means that this is the closest we can get
# to a "natural" comprehension or functional iterable definition:
# https://docs.python.org/3/reference/executionmodel.html#resolution-of-names
@idata(filter(lambda x, exclude=exclude: x.__name__ not in exclude, class_list))
def test_equivalence_phase(self, gate_class):
"""Test that the equivalent circuits from the equivalency_library
have equal matrix representations"""
for gate_class in self._gate_classes:
with self.subTest(i=gate_class):
n_params = len(_get_free_params(gate_class))
params = [0.1 * i for i in range(1, n_params + 1)]
if gate_class.__name__ == "RXXGate":
params = [np.pi / 2]
if gate_class.__name__ in ["MSGate"]:
params[0] = 2
if gate_class.__name__ in ["PauliGate"]:
params = ["IXYZ"]
if gate_class.__name__ in ["BooleanExpression"]:
params = ["x | y"]
if gate_class.__name__ in ["PauliEvolutionGate", "PauliEvolutionGate"]:
continue
n_params = len(_get_free_params(gate_class))
params = [0.1 * i for i in range(1, n_params + 1)]
if gate_class.__name__ == "RXXGate":
params = [np.pi / 2]
if gate_class.__name__ in ["MSGate"]:
params[0] = 2
if gate_class.__name__ in ["PauliGate"]:
params = ["IXYZ"]
if gate_class.__name__ in ["BooleanExpression"]:
params = ["x | y"]
gate = gate_class(*params)
equiv_lib_list = std_eqlib.get_entry(gate)
for ieq, equivalency in enumerate(equiv_lib_list):
with self.subTest(msg=gate.name + "_" + str(ieq)):
op1 = Operator(gate)
op2 = Operator(equivalency)
self.assertEqual(op1, op2)
gate = gate_class(*params)
equiv_lib_list = std_eqlib.get_entry(gate)
for ieq, equivalency in enumerate(equiv_lib_list):
with self.subTest(msg=gate.name + "_" + str(ieq)):
op1 = Operator(gate)
op2 = Operator(equivalency)
self.assertEqual(op1, op2)
@ddt