From 8914a0b24d943ce068c30dad239427a112c09995 Mon Sep 17 00:00:00 2001 From: Shelly Garion <46566946+ShellyGarion@users.noreply.github.com> Date: Tue, 1 Oct 2024 10:41:07 +0300 Subject: [PATCH] Add some more gates to CollectCliffords pass (#13214) * add matrix_based option to CollectClifford pass Co-authored-by: Samantha Barron * add a test to collect matrix_based gates * add release notes * minor update following review --- .../passes/optimization/collect_cliffords.py | 22 ++++- ...-to-collect-clifford-af88dd8f7a2a4bf9.yaml | 8 ++ .../python/transpiler/test_clifford_passes.py | 96 +++++++++++++++++-- 3 files changed, 115 insertions(+), 11 deletions(-) create mode 100644 releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml diff --git a/qiskit/transpiler/passes/optimization/collect_cliffords.py b/qiskit/transpiler/passes/optimization/collect_cliffords.py index 40acd21c6855..5ee75af98000 100644 --- a/qiskit/transpiler/passes/optimization/collect_cliffords.py +++ b/qiskit/transpiler/passes/optimization/collect_cliffords.py @@ -15,6 +15,7 @@ from functools import partial +from qiskit.exceptions import QiskitError from qiskit.transpiler.passes.optimization.collect_and_collapse import ( CollectAndCollapse, collect_using_filter_function, @@ -37,6 +38,7 @@ def __init__( min_block_size=2, split_layers=False, collect_from_back=False, + matrix_based=False, ): """CollectCliffords initializer. @@ -51,11 +53,13 @@ def __init__( over disjoint qubit subsets. collect_from_back (bool): specifies if blocks should be collected started from the end of the circuit. + matrix_based (bool): specifies whether to collect unitary gates + which are Clifford gates only for certain parameters (based on their unitary matrix). """ collect_function = partial( collect_using_filter_function, - filter_function=_is_clifford_gate, + filter_function=partial(_is_clifford_gate, matrix_based=matrix_based), split_blocks=split_blocks, min_block_size=min_block_size, split_layers=split_layers, @@ -77,9 +81,21 @@ def __init__( ) -def _is_clifford_gate(node): +def _is_clifford_gate(node, matrix_based=False): """Specifies whether a node holds a clifford gate.""" - return node.op.name in clifford_gate_names and getattr(node.op, "condition", None) is None + if getattr(node.op, "condition", None) is not None: + return False + if node.op.name in clifford_gate_names: + return True + + if not matrix_based: + return False + + try: + Clifford(node.op) + return True + except QiskitError: + return False def _collapse_to_clifford(circuit): diff --git a/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml b/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml new file mode 100644 index 000000000000..05de0af70528 --- /dev/null +++ b/releasenotes/notes/add-gates-to-collect-clifford-af88dd8f7a2a4bf9.yaml @@ -0,0 +1,8 @@ +--- +features_transpiler: + - | + Add an argument ``matrix_based`` to the :class:`.CollectCliffords()` transpiler pass. + If the new parameter ``matrix_based=True``, the :class:`.CollectCliffords()` transpiler pass + can collect :class:`.RZGate(np.pi/2)` gates and other unitary gates that are :class:`.Clifford()` + gates for certain parameters. + diff --git a/test/python/transpiler/test_clifford_passes.py b/test/python/transpiler/test_clifford_passes.py index ff8be63ffbcd..66a7fd15ad10 100644 --- a/test/python/transpiler/test_clifford_passes.py +++ b/test/python/transpiler/test_clifford_passes.py @@ -27,6 +27,39 @@ from qiskit.compiler.transpiler import transpile from test import QiskitTestCase # pylint: disable=wrong-import-order +from qiskit.circuit.library import ( + CPhaseGate, + CRXGate, + CRYGate, + CRZGate, + CXGate, + CYGate, + CZGate, + DCXGate, + ECRGate, + HGate, + IGate, + iSwapGate, + RXGate, + RYGate, + RZGate, + RXXGate, + RYYGate, + RZZGate, + RZXGate, + SGate, + SdgGate, + SXGate, + SXdgGate, + SwapGate, + UGate, + XGate, + XXMinusYYGate, + XXPlusYYGate, + YGate, + ZGate, +) + class TestCliffordPasses(QiskitTestCase): """Tests to verify correctness of transpiler passes and @@ -381,7 +414,7 @@ def test_collect_cliffords_multiple_blocks(self): qc.cx(0, 1) qc.sdg(2) qc.swap(2, 1) - qc.rx(np.pi / 2, 1) + qc.t(1) qc.cz(0, 1) qc.z(0) qc.y(1) @@ -389,7 +422,7 @@ def test_collect_cliffords_multiple_blocks(self): # We should end up with two Cliffords and one "rx" gate qct = PassManager(CollectCliffords()).run(qc) self.assertEqual(qct.size(), 3) - self.assertIn("rx", qct.count_ops().keys()) + self.assertIn("t", qct.count_ops().keys()) self.assertEqual(qct.count_ops()["clifford"], 2) self.assertIsInstance(qct.data[0].operation, Clifford) @@ -479,9 +512,9 @@ def test_collect_cliffords_options_multiple_blocks(self): qc.x(2) qc.cx(2, 0) - qc.rx(np.pi / 2, 0) - qc.rx(np.pi / 2, 1) - qc.rx(np.pi / 2, 2) + qc.rx(np.pi / 4, 0) + qc.rx(np.pi / 4, 1) + qc.rx(np.pi / 4, 2) qc.cz(0, 1) qc.z(0) @@ -524,7 +557,7 @@ def test_collect_from_back_corectness(self): qc.h(0) qc.x(1) qc.h(1) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.y(0) qc.h(1) @@ -541,7 +574,7 @@ def test_collect_from_back_as_expected(self): qc.h(0) qc.x(1) qc.h(1) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.y(0) qc.h(1) @@ -728,7 +761,7 @@ def test_collect_with_all_types(self): qc.cy(0, 1) # not a clifford gate (separating the circuit) - qc.rx(np.pi / 2, 0) + qc.t(0) qc.append(pauli_gate2, [0, 2, 1]) qc.append(lf2, [2, 1, 0]) @@ -749,6 +782,53 @@ def test_collect_with_all_types(self): op2 = Operator(qct) self.assertTrue(op1.equiv(op2)) + def test_collect_all_clifford_gates(self): + """Assert that CollectClifford collects all basis gates + (including certain rotation gates with pi/2 angles)""" + gates_1q = [ + XGate(), + YGate(), + ZGate(), + IGate(), + HGate(), + SGate(), + SdgGate(), + SXGate(), + SXdgGate(), + RXGate(theta=np.pi / 2), + RYGate(theta=np.pi / 2), + RZGate(phi=np.pi / 2), + UGate(np.pi / 2, np.pi / 2, np.pi / 2), + ] + gates_2q = [ + CXGate(), + CYGate(), + CZGate(), + DCXGate(), + ECRGate(), + SwapGate(), + iSwapGate(), + CPhaseGate(theta=np.pi), + CRXGate(theta=np.pi), + CRYGate(theta=np.pi), + CRZGate(theta=np.pi), + RXXGate(theta=np.pi / 2), + RYYGate(theta=np.pi / 2), + RZZGate(theta=np.pi / 2), + RZXGate(theta=np.pi / 2), + XXMinusYYGate(theta=np.pi), + XXPlusYYGate(theta=-np.pi), + ] + + qc = QuantumCircuit(2) + for gate in gates_1q: + qc.append(gate, [0]) + for gate in gates_2q: + qc.append(gate, [0, 1]) + + qct = PassManager(CollectCliffords(matrix_based=True)).run(qc) + self.assertEqual(qct.count_ops()["clifford"], 1) + if __name__ == "__main__": unittest.main()