From aef5a389e38aabe5643a4e03df891237df7bb7ed Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Thu, 2 May 2024 09:38:48 -0400 Subject: [PATCH] Add equivalence library entry for swap to ECR or CZ (#12312) * Add equivalence library entry for swap to ECR or CZ This commit adds two new equivalence library entries to cover the conversion from a SWAP gate to either ecr or cz directly. These are common 2q basis gates and without these entries in the equivalence library the path found from a lookup ends up with a much less efficient translation. This commit adds the two new entries so that the BasisTranslator will use a more efficient decomposition from the start when targeting these basis. This will hopefully result in less work for the optimization stage as the output will already be optimal and not require simplification. Testing for this PR is handled automatically by the built-in testing harness in test_gate_definitions.py that evaluates all the entries in the standard equivalence library for unitary equivalence. * Add name to annotated gate circuit in qpy backwards compat tests * Fix equivalence library tests As fallout from the addition of SingletonGate and SingletonControlledGate we were accidentally not running large portions of the unit tests which validate the default session equivalence library. This test dynamically runs based on all members of the standard gate library by looking at all defined subclasses of Gate and ControlledGate. But with the introduction of SingletonGate and SingletonControlledGate all the unparameterized gates in the library were not being run through the tests. This commit fixes this to catch that the swap definition added in the previous commit on this PR branch used an incorrect definition of SwapGate using ECRGate. The definition will be fixed in a follow up PR. * Use a more efficient and actually correct circuit for ECR target The previous definition of a swap gate using ECR rz and sx was incorrect and also not as efficient as possible. This was missed because the tests were accidently broken since #10314 which was fixed in the previous commit. This commit updates the definition to use one that is actually correct and also more efficient with fewer 1 qubit gates. Co-authored-by: Alexander Ivrii * Update ECR circuit diagram in comment * Simplify cz equivalent circuit * Simplify cz circuit even more Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Co-authored-by: Alexander Ivrii --------- Co-authored-by: Alexander Ivrii Co-authored-by: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> --- .../standard_gates/equivalence_library.py | 50 +++++++++++++++++++ test/python/circuit/test_gate_definitions.py | 14 +++++- test/qpy_compat/test_qpy.py | 2 +- 3 files changed, 63 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index 9793dc0202a8..c4619ca27858 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -850,6 +850,56 @@ def _cnot_rxx_decompose(plus_ry: bool = True, plus_rxx: bool = True): def_swap.append(inst, qargs, cargs) _sel.add_equivalence(SwapGate(), def_swap) +# SwapGate +# +# q_0: ─X─ +# │ ≡ +# q_1: ─X─ +# +# ┌──────────┐┌──────┐ ┌────┐ ┌──────┐┌──────────┐┌──────┐ +# q_0: ┤ Rz(-π/2) ├┤0 ├───┤ √X ├───┤1 ├┤ Rz(-π/2) ├┤0 ├ +# └──┬────┬──┘│ Ecr │┌──┴────┴──┐│ Ecr │└──┬────┬──┘│ Ecr │ +# q_1: ───┤ √X ├───┤1 ├┤ Rz(-π/2) ├┤0 ├───┤ √X ├───┤1 ├ +# └────┘ └──────┘└──────────┘└──────┘ └────┘ └──────┘ +# +q = QuantumRegister(2, "q") +def_swap_ecr = QuantumCircuit(q) +def_swap_ecr.rz(-pi / 2, 0) +def_swap_ecr.sx(1) +def_swap_ecr.ecr(0, 1) +def_swap_ecr.rz(-pi / 2, 1) +def_swap_ecr.sx(0) +def_swap_ecr.ecr(1, 0) +def_swap_ecr.rz(-pi / 2, 0) +def_swap_ecr.sx(1) +def_swap_ecr.ecr(0, 1) +_sel.add_equivalence(SwapGate(), def_swap_ecr) + +# SwapGate +# +# q_0: ─X─ +# │ ≡ +# q_1: ─X─ +# +# global phase: 3π/2 +# ┌────┐ ┌────┐ ┌────┐ +# q_0: ┤ √X ├─■─┤ √X ├─■─┤ √X ├─■─ +# ├────┤ │ ├────┤ │ ├────┤ │ +# q_1: ┤ √X ├─■─┤ √X ├─■─┤ √X ├─■─ +# └────┘ └────┘ └────┘ +q = QuantumRegister(2, "q") +def_swap_cz = QuantumCircuit(q, global_phase=-pi / 2) +def_swap_cz.sx(0) +def_swap_cz.sx(1) +def_swap_cz.cz(0, 1) +def_swap_cz.sx(0) +def_swap_cz.sx(1) +def_swap_cz.cz(0, 1) +def_swap_cz.sx(0) +def_swap_cz.sx(1) +def_swap_cz.cz(0, 1) +_sel.add_equivalence(SwapGate(), def_swap_cz) + # iSwapGate # # ┌────────┐ ┌───┐┌───┐ ┌───┐ diff --git a/test/python/circuit/test_gate_definitions.py b/test/python/circuit/test_gate_definitions.py index 8e608a163900..38bf7046cae5 100644 --- a/test/python/circuit/test_gate_definitions.py +++ b/test/python/circuit/test_gate_definitions.py @@ -21,6 +21,7 @@ from qiskit import QuantumCircuit, QuantumRegister from qiskit.quantum_info import Operator from qiskit.circuit import ParameterVector, Gate, ControlledGate +from qiskit.circuit.singleton import SingletonGate, SingletonControlledGate from qiskit.circuit.library import standard_gates from qiskit.circuit.library import ( HGate, @@ -260,7 +261,12 @@ class TestGateEquivalenceEqual(QiskitTestCase): """Test the decomposition of a gate in terms of other gates yields the same matrix as the hardcoded matrix definition.""" - class_list = Gate.__subclasses__() + ControlledGate.__subclasses__() + class_list = ( + SingletonGate.__subclasses__() + + SingletonControlledGate.__subclasses__() + + Gate.__subclasses__() + + ControlledGate.__subclasses__() + ) exclude = { "ControlledGate", "DiagonalGate", @@ -313,7 +319,11 @@ def test_equivalence_phase(self, gate_class): with self.subTest(msg=gate.name + "_" + str(ieq)): op1 = Operator(gate) op2 = Operator(equivalency) - self.assertEqual(op1, op2) + msg = ( + f"Equivalence entry from '{gate.name}' to:\n" + f"{str(equivalency.draw('text'))}\nfailed" + ) + self.assertEqual(op1, op2, msg) @ddt diff --git a/test/qpy_compat/test_qpy.py b/test/qpy_compat/test_qpy.py index 3a404672946a..f2ce2bee1086 100755 --- a/test/qpy_compat/test_qpy.py +++ b/test/qpy_compat/test_qpy.py @@ -661,7 +661,7 @@ def generate_annotated_circuits(): CXGate(), [InverseModifier(), ControlModifier(1), PowerModifier(1.4), InverseModifier()] ) op2 = AnnotatedOperation(XGate(), InverseModifier()) - qc = QuantumCircuit(6, 1) + qc = QuantumCircuit(6, 1, name="Annotated circuits") qc.cx(0, 1) qc.append(op1, [0, 1, 2]) qc.h(4)