From 2b225d3713a8ef160a38e8fbfbc7d08a0b8a4682 Mon Sep 17 00:00:00 2001 From: Raynel Sanchez <87539502+raynelfss@users.noreply.github.com> Date: Thu, 20 Jul 2023 18:35:25 -0400 Subject: [PATCH] Optimize `ConsolidateBlocks` pass (#10365) * Initial: Introducing speedup by calculating matrix * Lint: Reformat using Black * Fix: Added `basis_count` and `outside_basis` * Fix: Use `Operator` if `to_matrix` is unavailable. * Fix: Default to Operator when necessary - Will default to previous method when blocks have more than 2 qubits. - This is a temporary measure. * Lint: Removed Cyclic import * Docs: Added release note. * Docs: Added reference to the issue. * Fix: Move `block_to_matrix` to ~.passes.utils * Lint: Fixed cyclical import and order * CI: Removed type checking * Add: Exceptions on `_block_to_matrix` * Docs: Fix release note. * Fix: Change import path for block_to_matrix * Update qiskit/transpiler/passes/utils/block_to_matrix.py --------- Co-authored-by: ewinston --- .../passes/optimization/consolidate_blocks.py | 20 ++++-- qiskit/transpiler/passes/utils/__init__.py | 1 + .../passes/utils/block_to_matrix.py | 70 +++++++++++++++++++ ...e-consolidate-blocks-3ea60c18bc546273.yaml | 14 ++++ 4 files changed, 98 insertions(+), 7 deletions(-) create mode 100644 qiskit/transpiler/passes/utils/block_to_matrix.py create mode 100644 releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml diff --git a/qiskit/transpiler/passes/optimization/consolidate_blocks.py b/qiskit/transpiler/passes/optimization/consolidate_blocks.py index fdc18e3cc1..b093b21b4c 100644 --- a/qiskit/transpiler/passes/optimization/consolidate_blocks.py +++ b/qiskit/transpiler/passes/optimization/consolidate_blocks.py @@ -26,6 +26,7 @@ from qiskit.transpiler.basepasses import TransformationPass from qiskit.circuit.controlflow import ControlFlowOp from qiskit.transpiler.passmanager import PassManager from qiskit.transpiler.passes.synthesis import unitary_synthesis +from qiskit.transpiler.passes.utils import _block_to_matrix from .collect_1q_runs import Collect1qRuns from .collect_2q_blocks import Collect2qBlocks @@ -110,19 +111,24 @@ class ConsolidateBlocks(TransformationPass): if isinstance(nd, DAGOpNode) and getattr(nd.op, "condition", None): block_cargs |= set(getattr(nd.op, "condition", None)[0]) all_block_gates.add(nd) - q = QuantumRegister(len(block_qargs)) - qc = QuantumCircuit(q) - if block_cargs: - c = ClassicalRegister(len(block_cargs)) - qc.add_register(c) block_index_map = self._block_qargs_to_indices(block_qargs, global_index_map) for nd in block: if nd.op.name == basis_gate_name: basis_count += 1 if self._check_not_in_basis(nd.op.name, nd.qargs, global_index_map): outside_basis = True - qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) - unitary = UnitaryGate(Operator(qc)) + if len(block_qargs) > 2: + q = QuantumRegister(len(block_qargs)) + qc = QuantumCircuit(q) + if block_cargs: + c = ClassicalRegister(len(block_cargs)) + qc.add_register(c) + for nd in block: + qc.append(nd.op, [q[block_index_map[i]] for i in nd.qargs]) + unitary = UnitaryGate(Operator(qc)) + else: + matrix = _block_to_matrix(block, block_index_map) + unitary = UnitaryGate(matrix) max_2q_depth = 20 # If depth > 20, there will be 1q gates to consolidate. if ( # pylint: disable=too-many-boolean-expressions diff --git a/qiskit/transpiler/passes/utils/__init__.py b/qiskit/transpiler/passes/utils/__init__.py index fd2d00bbc4..c974d292fb 100644 --- a/qiskit/transpiler/passes/utils/__init__.py +++ b/qiskit/transpiler/passes/utils/__init__.py @@ -32,3 +32,4 @@ from .minimum_point import MinimumPoint # Utility functions from . import control_flow +from .block_to_matrix import _block_to_matrix diff --git a/qiskit/transpiler/passes/utils/block_to_matrix.py b/qiskit/transpiler/passes/utils/block_to_matrix.py new file mode 100644 index 0000000000..c0720129e0 --- /dev/null +++ b/qiskit/transpiler/passes/utils/block_to_matrix.py @@ -0,0 +1,70 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Converts any block of 2 qubit gates into a matrix.""" + +from numpy import identity, kron +from qiskit.circuit.library import SwapGate +from qiskit.quantum_info import Operator +from qiskit.exceptions import QiskitError + + +SWAP_GATE = SwapGate() +SWAP_MATRIX = SWAP_GATE.to_matrix() +IDENTITY = identity(2, dtype=complex) + + +def _block_to_matrix(block, block_index_map): + """ + The function converts any sequence of operations between two qubits into a matrix + that can be utilized to create a gate or a unitary. + + Args: + block (List(DAGOpNode)): A block of operations on two qubits. + block_index_map (dict(Qubit, int)): The mapping of the qubit indices in the main circuit. + + Returns: + NDArray: Matrix representation of the block of operations. + """ + block_index_length = len(block_index_map) + if block_index_length != 2: + raise QiskitError( + "This function can only operate with blocks of 2 qubits." + + f"This block had {block_index_length}" + ) + matrix = identity(2**block_index_length, dtype=complex) + for node in block: + try: + current = node.op.to_matrix() + except QiskitError: + current = Operator(node.op).data + q_list = [block_index_map[qubit] for qubit in node.qargs] + if len(q_list) > 2: + raise QiskitError( + f"The operation {node.op.name} in this block has " + + f"{len(q_list)} qubits, only 2 max allowed." + ) + basis_change = False + if len(q_list) < block_index_length: + if q_list[0] == 1: + current = kron(current, IDENTITY) + else: + current = kron(IDENTITY, current) + else: + if q_list[0] > q_list[1]: + if node.op != SWAP_GATE: + basis_change = True + if basis_change: + matrix = (SWAP_MATRIX @ current) @ (SWAP_MATRIX @ matrix) + else: + matrix = current @ matrix + return matrix diff --git a/releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml b/releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml new file mode 100644 index 0000000000..9bb8216bd9 --- /dev/null +++ b/releasenotes/notes/optimize-consolidate-blocks-3ea60c18bc546273.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added utility function :func:`qiskit.transpiler.passes.utils._block_to_matrix` that can + generate a matrix based on a block of operations between two qubits. This function can + be used in transpiler passes that work on some decomposed circuits such as :class:`.ConsolidateBlocks`. +fixes: + - | + Reduced overhead of the :class:`.ConsolidateBlocks` pass by performing matrix operations + on all two-qubit blocks using :func:`qiskit.transpiler.passes.utils._block_to_matrix` + instead of creating an instance of :class:`.QuantumCircuit` and passing it to an :class:`.Operator`. + The speedup will only be applicable when consolidating two-qubit blocks. Anything higher + than that will still be handled by the :class:`.Operator` class. + Check `#8779 `__ for details.