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 <ewinston@us.ibm.com>
This commit is contained in:
Raynel Sanchez 2023-07-20 18:35:25 -04:00 committed by GitHub
parent ac29776243
commit 2b225d3713
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 98 additions and 7 deletions

View File

@ -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
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

View File

@ -32,3 +32,4 @@ from .minimum_point import MinimumPoint
# Utility functions
from . import control_flow
from .block_to_matrix import _block_to_matrix

View File

@ -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

View File

@ -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 <https://github.com/Qiskit/qiskit-terra/issues/8779>`__ for details.