From b45afea03662bf4a2905251e26cbc008bdb3c0e1 Mon Sep 17 00:00:00 2001 From: Jake Lishman Date: Fri, 28 Oct 2022 13:58:11 +0100 Subject: [PATCH] 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> --- .../standard_gates/equivalence_library.py | 72 ++++++++++++++ ...add-xxyy-equivalence-a941c9b9bc60747b.yaml | 5 + test/python/circuit/test_gate_definitions.py | 96 +++++++++---------- 3 files changed, 122 insertions(+), 51 deletions(-) create mode 100644 releasenotes/notes/add-xxyy-equivalence-a941c9b9bc60747b.yaml diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index f847d9a62e..94bb8bb06b 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -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) diff --git a/releasenotes/notes/add-xxyy-equivalence-a941c9b9bc60747b.yaml b/releasenotes/notes/add-xxyy-equivalence-a941c9b9bc60747b.yaml new file mode 100644 index 0000000000..16f460f2ff --- /dev/null +++ b/releasenotes/notes/add-xxyy-equivalence-a941c9b9bc60747b.yaml @@ -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. diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index bd1bad3658..cac5e3f338 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -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