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 2 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
4 changes: 3 additions & 1 deletion qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -919,7 +919,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
185 changes: 185 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,185 @@
# 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.circuit.library.standard_gates import RZZGate, RZGate, XGate
from qiskit.circuit.parameterexpression import ParameterExpression
from qiskit.circuit import Qubit
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.

This pass preserves the number of Rzz gates, but it may add
extra single qubit gate operations.
These gates might be reduced by the following
single qubit optimization passes.
"""

def run(self, dag: DAGCircuit) -> DAGCircuit:
# Currently control flow ops and Rzz cannot live in the same circuit.
# Once it's supported, we need to recursively check subroutines.
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
for node in dag.op_nodes():
if not isinstance(node.op, RZZGate) or isinstance(
angle := node.op.params[0], ParameterExpression
):
continue
wrap_angle = np.angle(np.exp(1j * angle))
if -pi <= wrap_angle <= -pi / 2:
replace = _quad3(wrap_angle, node.qargs)
elif -pi / 2 < wrap_angle < 0:
replace = _quad4(wrap_angle, node.qargs)
elif pi / 2 < wrap_angle <= pi:
replace = _quad2(wrap_angle, node.qargs)
else:
if angle != wrap_angle:
dag.substitute_node(
node,
RZZGate(wrap_angle),
inplace=True,
)
continue
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
dag.substitute_node_with_dag(node, replace)
return dag


def _quad2(angle: float, qubits: Tuple[Qubit, ...]) -> DAGCircuit:
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
"""Handle angle between (pi/2, pi].

Circuit is transformed into following form:
wshanks marked this conversation as resolved.
Show resolved Hide resolved

┌───────┐┌───┐ ┌───┐
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(
RZGate(pi),
qargs=(qubits[0],),
cargs=(),
check=False,
)
new_dag.apply_operation_back(
RZGate(pi),
qargs=(qubits[1],),
cargs=(),
check=False,
)
if not np.isclose(new_angle := (pi - angle), 0.0):
new_dag.apply_operation_back(
XGate(),
qargs=(qubits[0],),
cargs=(),
check=False,
)
new_dag.apply_operation_back(
RZZGate(new_angle),
qargs=qubits,
cargs=(),
check=False,
)
new_dag.apply_operation_back(
XGate(),
qargs=(qubits[0],),
cargs=(),
check=False,
)
return new_dag


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(
RZGate(pi),
qargs=(qubits[0],),
cargs=(),
check=False,
)
new_dag.apply_operation_back(
RZGate(pi),
qargs=(qubits[1],),
cargs=(),
check=False,
)
if not np.isclose(new_angle := (pi - np.abs(angle)), 0.0):
new_dag.apply_operation_back(
RZZGate(new_angle),
qargs=qubits,
cargs=(),
check=False,
)
return new_dag


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],),
cargs=(),
check=False,
)
new_dag.apply_operation_back(
RZZGate(abs(angle)),
qargs=qubits,
cargs=(),
check=False,
)
new_dag.apply_operation_back(
XGate(),
qargs=(qubits[0],),
cargs=(),
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 system with fractional gate support.
wshanks marked this conversation as resolved.
Show resolved Hide resolved

Currently coexistense of fractional gate operations and
wshanks marked this conversation as resolved.
Show resolved Hide resolved
dynamic circuit is not assumed.
wshanks marked this conversation as resolved.
Show resolved Hide resolved
"""

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 @@
Add new transpiler translation plugin :class:`IBMFractionalTranslationPlugin`
wshanks marked this conversation as resolved.
Show resolved Hide resolved
and a pass :class:`FoldRzzAngle`.
This plugin is automatically appled for backends
wshanks marked this conversation as resolved.
Show resolved Hide resolved
retrieved with the `use_fractional_gates` optin,
and the folding pass is added when the backend target includes the `RZZ` gate.

New pass modifies the input quantum circuit so that all `RZZ` gates in the
wshanks marked this conversation as resolved.
Show resolved Hide resolved
circuit has an angle parameter within [0, pi/2] which is supported
wshanks marked this conversation as resolved.
Show resolved Hide resolved
by IBM Quantum processors.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@
"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",
]
},
extras_require={"visualization": ["plotly>=5.23.0"]},
Expand Down
63 changes: 63 additions & 0 deletions test/unit/transpiler/passes/basis/test_fold_rzz_angle.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# 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.

"""Test folding Rzz angle into calibrated range."""

from math import pi
from ddt import ddt, named_data

from qiskit.circuit import QuantumCircuit
from qiskit.quantum_info import Operator
from qiskit.transpiler.passmanager import PassManager
from qiskit.circuit.parameter import Parameter

from qiskit_ibm_runtime.transpiler.passes.basis import FoldRzzAngle
from .....ibm_test_case import IBMTestCase


@ddt
class TestFoldRzzAngle(IBMTestCase):
"""Test FoldRzzAngle pass"""
yaelbh marked this conversation as resolved.
Show resolved Hide resolved

@named_data(
("large_positive_number", 12345),
("large_negative_number", -12345),
("pi/2_pos", pi / 2),
("pi/2_neg", -pi / 2),
("pi_pos", pi),
("pi_neg", -pi),
("quad1", 0.1),
("quad2", pi / 2 + 0.1),
("quad3", -pi + 0.1),
("quad4", -0.1),
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
)
def test_folding_rzz_angles(self, angle):
"""Test folding gate angle into calibrated range."""
qc = QuantumCircuit(2)
qc.rzz(angle, 0, 1)
pm = PassManager([FoldRzzAngle()])
isa = pm.run(qc)

self.assertTrue(Operator.from_circuit(qc).equiv(Operator.from_circuit(isa)))
for inst_data in isa.data:
if inst_data.operation.name == "rzz":
yaelbh marked this conversation as resolved.
Show resolved Hide resolved
fold_angle = inst_data.operation.params[0]
self.assertGreaterEqual(fold_angle, 0.0)
self.assertLessEqual(fold_angle, pi / 2)
yaelbh marked this conversation as resolved.
Show resolved Hide resolved

def test_folding_rzz_angle_unbound(self):
"""Test skip folding unbound gate angle."""
qc = QuantumCircuit(2)
qc.rzz(Parameter("θ"), 0, 1)
pm = PassManager([FoldRzzAngle()])
isa = pm.run(qc)
self.assertEqual(qc, isa)