-
Notifications
You must be signed in to change notification settings - Fork 160
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add plugin pass to support non-calibrated rzz angles (#2043)
* add plugin pass to support non-calibrated rzz angles * add release note * update phase check logic * add more document * add more angle test * support control flow * Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py Co-authored-by: Yael Ben-Haim <[email protected]> * Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py Co-authored-by: Yael Ben-Haim <[email protected]> * Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py Co-authored-by: Yael Ben-Haim <[email protected]> * Update documentation wording Co-authored-by: Yael Ben-Haim <[email protected]> * update test to require the same phase * fix quad2 no wrap * fix quad3 no wrap * apply a global phase of pi for a 2pi angle offset * black * bug fix * wrote test_fractional_plugin * made the _quad functions static methods of the class * Update release-notes/unreleased/2043.feat.rst Co-authored-by: Will Shanks <[email protected]> * deleted a test --------- Co-authored-by: Yael Ben-Haim <[email protected]> Co-authored-by: Will Shanks <[email protected]> Co-authored-by: Kevin Tian <[email protected]>
- Loading branch information
1 parent
7d5f3ac
commit d88028b
Showing
7 changed files
with
417 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
246 changes: 246 additions & 0 deletions
246
qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,246 @@ | ||
# This code is part of Qiskit. | ||
# | ||
# (C) Copyright IBM 2024. | ||
# | ||
# 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. | ||
|
||
"""Pass to wrap Rzz gate angle in calibrated range of 0-pi/2.""" | ||
|
||
from typing import Tuple | ||
from math import pi | ||
|
||
from qiskit.converters import dag_to_circuit, circuit_to_dag | ||
from qiskit.circuit.library.standard_gates import RZZGate, RZGate, XGate, GlobalPhaseGate | ||
from qiskit.circuit.parameterexpression import ParameterExpression | ||
from qiskit.circuit import Qubit, ControlFlowOp | ||
from qiskit.dagcircuit import DAGCircuit | ||
from qiskit.transpiler.basepasses import TransformationPass | ||
|
||
import numpy as np | ||
|
||
|
||
class FoldRzzAngle(TransformationPass): | ||
"""Fold Rzz gate angle into calibrated range of 0-pi/2 with | ||
local gate tweaks. | ||
In the IBM Quantum ISA, the instruction Rzz(theta) has | ||
valid "theta" value of [0, pi/2] and any instruction outside | ||
this range becomes a non-ISA operation for the quantum backend. | ||
The transpiler pass discovers such non-ISA Rzz gates | ||
and folds the gate angle into the calibrated range | ||
with addition of single qubit gates while preserving | ||
logical equivalency of the input quantum circuit. | ||
Added local gates might be efficiently merged into | ||
neighboring single qubit gates by the following single qubit | ||
optimization passes. | ||
This pass allows the Qiskit users to naively use the Rzz gates | ||
with angle of arbitrary real numbers. | ||
.. note:: | ||
This pass doesn't transform the circuit when the | ||
Rzz gate angle is an unbound parameter. | ||
In this case, the user must assign a gate angle before | ||
transpilation, or be responsible for choosing parameters | ||
from the calibrated range of [0, pi/2]. | ||
""" | ||
|
||
def run(self, dag: DAGCircuit) -> DAGCircuit: | ||
self._run_inner(dag) | ||
return dag | ||
|
||
def _run_inner(self, dag: DAGCircuit) -> bool: | ||
"""Mutate the input dag to fix non-ISA Rzz angles. | ||
Return true if the dag was modified.""" | ||
modified = False | ||
for node in dag.op_nodes(): | ||
if isinstance(node.op, ControlFlowOp): | ||
modified_blocks = False | ||
new_blocks = [] | ||
for block in node.op.blocks: | ||
block_dag = circuit_to_dag(block) | ||
if self._run_inner(block_dag): | ||
# Store circuit with Rzz modification | ||
new_blocks.append(dag_to_circuit(block_dag)) | ||
modified_blocks = True | ||
else: | ||
# Keep original circuit to save memory | ||
new_blocks.append(block) | ||
if modified_blocks: | ||
dag.substitute_node( | ||
node, | ||
node.op.replace_blocks(new_blocks), | ||
inplace=True, | ||
) | ||
modified = True | ||
continue | ||
|
||
if not isinstance(node.op, RZZGate): | ||
continue | ||
|
||
angle = node.op.params[0] | ||
if isinstance(angle, ParameterExpression) or 0 <= angle <= pi / 2: | ||
# Angle is an unbound parameter or a calibrated value. | ||
continue | ||
|
||
# Modify circuit around Rzz gate to address non-ISA angles. | ||
modified = True | ||
wrap_angle = np.angle(np.exp(1j * angle)) | ||
if 0 <= wrap_angle <= pi / 2: | ||
# In the first quadrant. | ||
replace = self._quad1(wrap_angle, node.qargs) | ||
elif pi / 2 < wrap_angle <= pi: | ||
# In the second quadrant. | ||
replace = self._quad2(wrap_angle, node.qargs) | ||
elif -pi <= wrap_angle <= -pi / 2: | ||
# In the third quadrant. | ||
replace = self._quad3(wrap_angle, node.qargs) | ||
elif -pi / 2 < wrap_angle < 0: | ||
# In the forth quadrant. | ||
replace = self._quad4(wrap_angle, node.qargs) | ||
else: | ||
raise RuntimeError("Unreacheable.") | ||
if pi < angle % (4 * pi) < 3 * pi: | ||
replace.apply_operation_back(GlobalPhaseGate(pi)) | ||
dag.substitute_node_with_dag(node, replace) | ||
return modified | ||
|
||
@staticmethod | ||
def _quad1(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: | ||
"""Handle angle between [0, pi/2]. | ||
Circuit is not transformed - the Rzz gate is calibrated for the angle. | ||
Returns: | ||
A new dag with the same Rzz gate. | ||
""" | ||
new_dag = DAGCircuit() | ||
new_dag.add_qubits(qubits=qubits) | ||
new_dag.apply_operation_back( | ||
RZZGate(angle), | ||
qargs=qubits, | ||
check=False, | ||
) | ||
return new_dag | ||
|
||
@staticmethod | ||
def _quad2(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: | ||
"""Handle angle between (pi/2, pi]. | ||
Circuit is transformed into the following form: | ||
┌───────┐┌───┐ ┌───┐ | ||
q_0: ┤ Rz(π) ├┤ X ├─■──────────┤ X ├ | ||
├───────┤└───┘ │ZZ(π - θ) └───┘ | ||
q_1: ┤ Rz(π) ├──────■─────────────── | ||
└───────┘ | ||
Returns: | ||
New dag to replace Rzz gate. | ||
""" | ||
new_dag = DAGCircuit() | ||
new_dag.add_qubits(qubits=qubits) | ||
new_dag.apply_operation_back(GlobalPhaseGate(pi / 2)) | ||
new_dag.apply_operation_back( | ||
RZGate(pi), | ||
qargs=(qubits[0],), | ||
cargs=(), | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
RZGate(pi), | ||
qargs=(qubits[1],), | ||
check=False, | ||
) | ||
if not np.isclose(new_angle := (pi - angle), 0.0): | ||
new_dag.apply_operation_back( | ||
XGate(), | ||
qargs=(qubits[0],), | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
RZZGate(new_angle), | ||
qargs=qubits, | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
XGate(), | ||
qargs=(qubits[0],), | ||
check=False, | ||
) | ||
return new_dag | ||
|
||
@staticmethod | ||
def _quad3(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: | ||
"""Handle angle between [-pi, -pi/2]. | ||
Circuit is transformed into following form: | ||
┌───────┐ | ||
q_0: ┤ Rz(π) ├─■─────────────── | ||
├───────┤ │ZZ(π - Abs(θ)) | ||
q_1: ┤ Rz(π) ├─■─────────────── | ||
└───────┘ | ||
Returns: | ||
New dag to replace Rzz gate. | ||
""" | ||
new_dag = DAGCircuit() | ||
new_dag.add_qubits(qubits=qubits) | ||
new_dag.apply_operation_back(GlobalPhaseGate(-pi / 2)) | ||
new_dag.apply_operation_back( | ||
RZGate(pi), | ||
qargs=(qubits[0],), | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
RZGate(pi), | ||
qargs=(qubits[1],), | ||
check=False, | ||
) | ||
if not np.isclose(new_angle := (pi - np.abs(angle)), 0.0): | ||
new_dag.apply_operation_back( | ||
RZZGate(new_angle), | ||
qargs=qubits, | ||
check=False, | ||
) | ||
return new_dag | ||
|
||
@staticmethod | ||
def _quad4(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit: | ||
"""Handle angle between (-pi/2, 0). | ||
Circuit is transformed into following form: | ||
┌───┐ ┌───┐ | ||
q_0: ┤ X ├─■───────────┤ X ├ | ||
└───┘ │ZZ(Abs(θ)) └───┘ | ||
q_1: ──────■──────────────── | ||
Returns: | ||
New dag to replace Rzz gate. | ||
""" | ||
new_dag = DAGCircuit() | ||
new_dag.add_qubits(qubits=qubits) | ||
new_dag.apply_operation_back( | ||
XGate(), | ||
qargs=(qubits[0],), | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
RZZGate(abs(angle)), | ||
qargs=qubits, | ||
check=False, | ||
) | ||
new_dag.apply_operation_back( | ||
XGate(), | ||
qargs=(qubits[0],), | ||
check=False, | ||
) | ||
return new_dag |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
Added a new transpiler translation plugin :class:`~.IBMFractionalTranslationPlugin` | ||
and a pass :class:`~.FoldRzzAngle`. | ||
This plugin is automatically applied for backends | ||
retrieved with the ``use_fractional_gates`` opt-in, | ||
and the folding pass is added when the backend target includes the ``RZZ`` gate. | ||
|
||
The new pass modifies the input quantum circuit, so that all ``RZZ`` gates in the | ||
circuit have an angle parameter within [0, pi/2] which is supported | ||
by IBM Quantum processors. |
Oops, something went wrong.