Source code for qiskit.transpiler.passes.calibration.rx_builder

# This code is part of Qiskit.
#
# (C) Copyright IBM 2023.
#
# 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.

"""Add single-pulse RX calibrations that are bootstrapped from the SX calibration."""

from typing import Union
from functools import lru_cache
import numpy as np

from qiskit.circuit import Instruction
from qiskit.pulse import Schedule, ScheduleBlock, builder, ScalableSymbolicPulse
from qiskit.pulse.channels import Channel
from qiskit.pulse.library.symbolic_pulses import Drag
from qiskit.transpiler.passes.calibration.base_builder import CalibrationBuilder
from qiskit.transpiler import Target
from qiskit.circuit.library.standard_gates import RXGate
from qiskit.exceptions import QiskitError


[docs]class RXCalibrationBuilder(CalibrationBuilder): """Add single-pulse RX calibrations that are bootstrapped from the SX calibration. .. note:: Requirement: NormalizeRXAngles pass (one of the optimization passes). It is recommended to place this pass in the post-optimization stage of a passmanager. A simple demo: .. code-block:: python from qiskit.providers.fake_provider import FakeBelemV2 from qiskit.transpiler import PassManager, PassManagerConfig from qiskit.transpiler.preset_passmanagers import level_1_pass_manager from qiskit.circuit import Parameter from qiskit.circuit.library import QuantumVolume from qiskit.circuit.library.standard_gates import RXGate from calibration.rx_builder import RXCalibrationBuilder qv = QuantumVolume(4, 4, seed=1004) # Transpiling with single pulse RX gates enabled backend_with_single_pulse_rx = FakeBelemV2() rx_inst_props = {} for i in range(backend_with_single_pulse_rx.num_qubits): rx_inst_props[(i,)] = None backend_with_single_pulse_rx.target.add_instruction(RXGate(Parameter("theta")), rx_inst_props) config_with_rx = PassManagerConfig.from_backend(backend=backend_with_single_pulse_rx) pm_with_rx = level_1_pass_manager(pass_manager_config=config_with_rx) rx_builder = RXCalibrationBuilder(target=backend_with_single_pulse_rx.target) pm_with_rx.post_optimization = PassManager([rx_builder]) transpiled_circ_with_single_pulse_rx = pm_with_rx.run(qv) transpiled_circ_with_single_pulse_rx.count_ops() # Conventional transpilation: each RX gate is decomposed into a sequence with two SX gates original_backend = FakeBelemV2() original_config = PassManagerConfig.from_backend(backend=original_backend) original_pm = level_1_pass_manager(pass_manager_config=original_config) original_transpiled_circ = original_pm.run(qv) original_transpiled_circ.count_ops() References * [1]: Gokhale et al. (2020), Optimized Quantum Compilation for Near-Term Algorithms with OpenPulse. `arXiv:2004.11205 <https://arxiv.org/abs/2004.11205>` """ def __init__( self, target: Target = None, ): """Bootstrap single-pulse RX gate calibrations from the (hardware-calibrated) SX gate calibration. Args: target (Target): Should contain a SX calibration that will be used for bootstrapping RX calibrations. """ from qiskit.transpiler.passes.optimization import NormalizeRXAngle super().__init__() self.target = target self.already_generated = {} self.requires = [NormalizeRXAngle(self.target)]
[docs] def supported(self, node_op: Instruction, qubits: list) -> bool: """ Check if the calibration for SX gate exists and it's a single DRAG pulse. """ return ( isinstance(node_op, RXGate) and self.target.has_calibration("sx", tuple(qubits)) and (len(self.target.get_calibration("sx", tuple(qubits)).instructions) == 1) and isinstance( self.target.get_calibration("sx", tuple(qubits)).instructions[0][1].pulse, ScalableSymbolicPulse, ) and self.target.get_calibration("sx", tuple(qubits)).instructions[0][1].pulse.pulse_type == "Drag" )
[docs] def get_calibration(self, node_op: Instruction, qubits: list) -> Union[Schedule, ScheduleBlock]: """ Generate RX calibration for the rotation angle specified in node_op. """ # already within [0, pi] by NormalizeRXAngles pass angle = node_op.params[0] try: angle = float(angle) except TypeError as ex: raise QiskitError("Target rotation angle is not assigned.") from ex params = ( self.target.get_calibration("sx", tuple(qubits)) .instructions[0][1] .pulse.parameters.copy() ) new_rx_sched = _create_rx_sched( rx_angle=angle, channel=self.target.get_calibration("sx", tuple(qubits)).channels[0], duration=params["duration"], amp=params["amp"], sigma=params["sigma"], beta=params["beta"], ) return new_rx_sched
@lru_cache def _create_rx_sched( rx_angle: float, duration: int, amp: float, sigma: float, beta: float, channel: Channel, ): """Generates (and caches) pulse calibrations for RX gates. Assumes that the rotation angle is in [0, pi]. """ new_amp = rx_angle / (np.pi / 2) * amp with builder.build() as new_rx_sched: builder.play( Drag(duration=duration, amp=new_amp, sigma=sigma, beta=beta, angle=0), channel=channel, ) return new_rx_sched