Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add plugin pass to support non-calibrated rzz angles #2043

Merged
merged 23 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
c3c6c3d
add plugin pass to support non-calibrated rzz angles
nkanazawa1989 Nov 13, 2024
5ce4cd2
add release note
nkanazawa1989 Nov 13, 2024
55668ed
update phase check logic
nkanazawa1989 Nov 14, 2024
a45251a
add more document
nkanazawa1989 Nov 14, 2024
0bc77b9
add more angle test
nkanazawa1989 Nov 14, 2024
013a6b4
support control flow
nkanazawa1989 Nov 14, 2024
c3c2af2
Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py
nkanazawa1989 Nov 14, 2024
8ad8947
Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py
nkanazawa1989 Nov 14, 2024
5285946
Update qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py
nkanazawa1989 Nov 14, 2024
75d86d6
Update documentation wording
wshanks Nov 25, 2024
ec478c4
update test to require the same phase
yaelbh Nov 27, 2024
13524bc
fix quad2 no wrap
yaelbh Nov 27, 2024
38a9d95
fix quad3 no wrap
yaelbh Nov 27, 2024
88c89ea
apply a global phase of pi for a 2pi angle offset
yaelbh Nov 27, 2024
c7e6cea
black
yaelbh Nov 27, 2024
7ec8a81
bug fix
yaelbh Dec 2, 2024
dc90bed
Merge branch 'main' into handle_fractional_constraint
kt474 Dec 2, 2024
bae9209
wrote test_fractional_plugin
yaelbh Dec 3, 2024
4b08e7e
made the _quad functions static methods of the class
yaelbh Dec 3, 2024
300fcc6
Merge branch 'main' of github.com:qiskit/qiskit-ibm-runtime into hand…
yaelbh Dec 3, 2024
9c88806
Update release-notes/unreleased/2043.feat.rst
yaelbh Dec 4, 2024
5c38a6e
deleted a test
yaelbh Dec 4, 2024
c0b8c0f
Merge branch 'main' into handle_fractional_constraint
yaelbh Dec 4, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ dependencies = [
[project.entry-points."qiskit.transpiler.translation"]
ibm_backend = "qiskit_ibm_runtime.transpiler.plugin:IBMTranslationPlugin"
ibm_dynamic_circuits = "qiskit_ibm_runtime.transpiler.plugin:IBMDynamicTranslationPlugin"
ibm_fractional = "qiskit_ibm_runtime.transpiler.plugin:IBMFractionalTranslationPlugin"

[project.urls]
documentation = "https://docs.quantum.ibm.com/"
Expand Down
4 changes: 3 additions & 1 deletion qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -911,7 +911,9 @@ def close_session(self) -> None:

def get_translation_stage_plugin(self) -> str:
"""Return the default translation stage plugin name for IBM backends."""
return "ibm_dynamic_circuits"
if not self.options.use_fractional_gates:
return "ibm_dynamic_circuits"
return "ibm_fractional"


class IBMRetiredBackend(IBMBackend):
Expand Down
1 change: 1 addition & 0 deletions qiskit_ibm_runtime/transpiler/passes/basis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@
"""Passes to layout circuits to IBM backend's instruction sets."""

from .convert_id_to_delay import ConvertIdToDelay
from .fold_rzz_angle import FoldRzzAngle
246 changes: 246 additions & 0 deletions qiskit_ibm_runtime/transpiler/passes/basis/fold_rzz_angle.py
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:
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
# 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
41 changes: 40 additions & 1 deletion qiskit_ibm_runtime/transpiler/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,9 @@
from qiskit.transpiler.preset_passmanagers import common
from qiskit.transpiler.passes import ConvertConditionsToIfOps

from qiskit_ibm_runtime.transpiler.passes.basis.convert_id_to_delay import (
from qiskit_ibm_runtime.transpiler.passes.basis import (
ConvertIdToDelay,
FoldRzzAngle,
)


Expand Down Expand Up @@ -96,3 +97,41 @@ def pass_manager(
plugin_passes += [ConvertConditionsToIfOps()]

return PassManager(plugin_passes) + translator_pm


class IBMFractionalTranslationPlugin(PassManagerStagePlugin):
"""A translation stage plugin for targeting Qiskit circuits
to IBM Quantum systems with fractional gate support.

Currently coexistence of fractional gate operations and
dynamic circuits is not assumed.
"""

def pass_manager(
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
self,
pass_manager_config: PassManagerConfig,
optimization_level: Optional[int] = None,
) -> PassManager:
"""Build IBMTranslationPlugin PassManager."""

translator_pm = common.generate_translation_passmanager(
target=pass_manager_config.target,
basis_gates=pass_manager_config.basis_gates,
approximation_degree=pass_manager_config.approximation_degree,
coupling_map=pass_manager_config.coupling_map,
backend_props=pass_manager_config.backend_properties,
unitary_synthesis_method=pass_manager_config.unitary_synthesis_method,
unitary_synthesis_plugin_config=pass_manager_config.unitary_synthesis_plugin_config,
hls_config=pass_manager_config.hls_config,
)
yaelbh marked this conversation as resolved.
Show resolved Hide resolved

instruction_durations = pass_manager_config.instruction_durations
pre_passes = []
post_passes = []
target = pass_manager_config.target or pass_manager_config.basis_gates
if instruction_durations and not "id" in target:
pre_passes.append(ConvertIdToDelay(instruction_durations))
if "rzz" in target:
# Apply this pass after SU4 is translated.
post_passes.append(FoldRzzAngle())
return PassManager(pre_passes) + translator_pm + PassManager(post_passes)
9 changes: 9 additions & 0 deletions release-notes/unreleased/2043.feat.rst
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` optin,
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.
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
Loading
Loading