mirror of https://github.com/Qiskit/qiskit.git
Avoid direct-CXs in RZX calibration pass (#8276)
* Step1. Add handling for direct CX in RZX calibration builder. PulseError is replaced with User warning not to crash pass manager execution. * Step2. Cleanup RZX builder. Unreachable errors are removed. Use inplace mode for schedule construction to avoid redundant deep copy. Deprecate unused argument. * Step3. Reorganize calibration module structure. Usually, in the transpile pass modules, single file contains a single (or similar) pass. RZX builder passes are moved to rzx_builder, Pulse gate pass is moved to pulse_gate and base class is moved to base_builder * Step4. Write release note * docs update and turn a cal validation into a separate function. calibration type enum is added for user-friendly error message. Co-authored-by: Thomas Alexander <talexander@ibm.com> Co-authored-by: Thomas Alexander <talexander@ibm.com> Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
This commit is contained in:
parent
47c7c27637
commit
36c91add58
|
@ -12,4 +12,5 @@
|
|||
|
||||
"""Module containing transpiler calibration passes."""
|
||||
|
||||
from .builders import RZXCalibrationBuilder, RZXCalibrationBuilderNoEcho, PulseGates
|
||||
from .pulse_gate import PulseGates
|
||||
from .rzx_builder import RZXCalibrationBuilder, RZXCalibrationBuilderNoEcho
|
||||
|
|
|
@ -0,0 +1,80 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2022.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Calibration builder base class."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import List, Union
|
||||
|
||||
from qiskit.circuit import Instruction as CircuitInst
|
||||
from qiskit.dagcircuit import DAGCircuit
|
||||
from qiskit.pulse import Schedule, ScheduleBlock
|
||||
from qiskit.pulse.instruction_schedule_map import CalibrationPublisher
|
||||
from qiskit.transpiler.basepasses import TransformationPass
|
||||
|
||||
from .exceptions import CalibrationNotAvailable
|
||||
|
||||
|
||||
class CalibrationBuilder(TransformationPass):
|
||||
"""Abstract base class to inject calibrations into circuits."""
|
||||
|
||||
@abstractmethod
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Gets the calibrated schedule for the given instruction and qubits.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return Schedule of target gate instruction.
|
||||
"""
|
||||
|
||||
def run(self, dag: DAGCircuit) -> DAGCircuit:
|
||||
"""Run the calibration adder pass on `dag`.
|
||||
|
||||
Args:
|
||||
dag: DAG to schedule.
|
||||
|
||||
Returns:
|
||||
A DAG with calibrations added to it.
|
||||
"""
|
||||
qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)}
|
||||
for node in dag.gate_nodes():
|
||||
qubits = [qubit_map[q] for q in node.qargs]
|
||||
|
||||
if self.supported(node.op, qubits) and not dag.has_calibration_for(node):
|
||||
# calibration can be provided and no user-defined calibration is already provided
|
||||
try:
|
||||
schedule = self.get_calibration(node.op, qubits)
|
||||
except CalibrationNotAvailable:
|
||||
# Fail in schedule generation. Just ignore.
|
||||
continue
|
||||
publisher = schedule.metadata.get("publisher", CalibrationPublisher.QISKIT)
|
||||
|
||||
# add calibration if it is not backend default
|
||||
if publisher != CalibrationPublisher.BACKEND_PROVIDER:
|
||||
dag.add_calibration(gate=node.op, qubits=qubits, schedule=schedule)
|
||||
|
||||
return dag
|
|
@ -12,469 +12,8 @@
|
|||
|
||||
"""Calibration creators."""
|
||||
|
||||
from abc import abstractmethod
|
||||
from typing import List, Union
|
||||
# TODO This import path will be deprecated.
|
||||
|
||||
import math
|
||||
import numpy as np
|
||||
|
||||
from qiskit.circuit import Instruction as CircuitInst
|
||||
from qiskit.circuit.library.standard_gates import RZXGate
|
||||
from qiskit.dagcircuit import DAGCircuit
|
||||
from qiskit.exceptions import QiskitError
|
||||
from qiskit.pulse import (
|
||||
Play,
|
||||
Delay,
|
||||
ShiftPhase,
|
||||
Schedule,
|
||||
ScheduleBlock,
|
||||
ControlChannel,
|
||||
DriveChannel,
|
||||
GaussianSquare,
|
||||
)
|
||||
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap, CalibrationPublisher
|
||||
from qiskit.pulse.instructions.instruction import Instruction as PulseInst
|
||||
from qiskit.transpiler.basepasses import TransformationPass
|
||||
|
||||
|
||||
class CalibrationBuilder(TransformationPass):
|
||||
"""Abstract base class to inject calibrations into circuits."""
|
||||
|
||||
@abstractmethod
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Gets the calibrated schedule for the given instruction and qubits.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return Schedule of target gate instruction.
|
||||
"""
|
||||
|
||||
def run(self, dag: DAGCircuit) -> DAGCircuit:
|
||||
"""Run the calibration adder pass on `dag`.
|
||||
|
||||
Args:
|
||||
dag: DAG to schedule.
|
||||
|
||||
Returns:
|
||||
A DAG with calibrations added to it.
|
||||
"""
|
||||
qubit_map = {qubit: i for i, qubit in enumerate(dag.qubits)}
|
||||
for node in dag.gate_nodes():
|
||||
qubits = [qubit_map[q] for q in node.qargs]
|
||||
|
||||
if self.supported(node.op, qubits) and not dag.has_calibration_for(node):
|
||||
# calibration can be provided and no user-defined calibration is already provided
|
||||
schedule = self.get_calibration(node.op, qubits)
|
||||
publisher = schedule.metadata.get("publisher", CalibrationPublisher.QISKIT)
|
||||
|
||||
# add calibration if it is not backend default
|
||||
if publisher != CalibrationPublisher.BACKEND_PROVIDER:
|
||||
dag.add_calibration(gate=node.op, qubits=qubits, schedule=schedule)
|
||||
|
||||
return dag
|
||||
|
||||
|
||||
class RZXCalibrationBuilder(CalibrationBuilder):
|
||||
"""
|
||||
Creates calibrations for RZXGate(theta) by stretching and compressing
|
||||
Gaussian square pulses in the CX gate. This is done by retrieving (for a given pair of
|
||||
qubits) the CX schedule in the instruction schedule map of the backend defaults.
|
||||
The CX schedule must be an echoed cross-resonance gate optionally with rotary tones.
|
||||
The cross-resonance drive tones and rotary pulses must be Gaussian square pulses.
|
||||
The width of the Gaussian square pulse is adjusted so as to match the desired rotation angle.
|
||||
If the rotation angle is small such that the width disappears then the amplitude of the
|
||||
zero width Gaussian square pulse (i.e. a Gaussian) is reduced to reach the target rotation
|
||||
angle. Additional details can be found in https://arxiv.org/abs/2012.11660.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instruction_schedule_map: InstructionScheduleMap = None,
|
||||
qubit_channel_mapping: List[List[str]] = None,
|
||||
):
|
||||
"""
|
||||
Initializes a RZXGate calibration builder.
|
||||
|
||||
Args:
|
||||
instruction_schedule_map: The :obj:`InstructionScheduleMap` object representing the
|
||||
default pulse calibrations for the target backend
|
||||
qubit_channel_mapping: The list mapping qubit indices to the list of
|
||||
channel names that apply on that qubit.
|
||||
|
||||
Raises:
|
||||
QiskitError: if open pulse is not supported by the backend.
|
||||
"""
|
||||
super().__init__()
|
||||
if instruction_schedule_map is None or qubit_channel_mapping is None:
|
||||
raise QiskitError("Calibrations can only be added to Pulse-enabled backends")
|
||||
|
||||
self._inst_map = instruction_schedule_map
|
||||
self._channel_map = qubit_channel_mapping
|
||||
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
return isinstance(node_op, RZXGate)
|
||||
|
||||
@staticmethod
|
||||
def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> Play:
|
||||
"""
|
||||
Args:
|
||||
instruction: The instruction from which to create a new shortened or lengthened pulse.
|
||||
theta: desired angle, pi/2 is assumed to be the angle that the pulse in the given
|
||||
play instruction implements.
|
||||
sample_mult: All pulses must be a multiple of sample_mult.
|
||||
|
||||
Returns:
|
||||
qiskit.pulse.Play: The play instruction with the stretched compressed
|
||||
GaussianSquare pulse.
|
||||
|
||||
Raises:
|
||||
QiskitError: if the pulses are not GaussianSquare.
|
||||
QiskitError: if rotation angle is not assigned.
|
||||
"""
|
||||
try:
|
||||
theta = float(theta)
|
||||
except TypeError as ex:
|
||||
raise QiskitError("Target rotation angle is not assigned.") from ex
|
||||
|
||||
pulse_ = instruction.pulse
|
||||
if isinstance(pulse_, GaussianSquare):
|
||||
amp = pulse_.amp
|
||||
width = pulse_.width
|
||||
sigma = pulse_.sigma
|
||||
n_sigmas = (pulse_.duration - width) / sigma
|
||||
|
||||
# The error function is used because the Gaussian may have chopped tails.
|
||||
gaussian_area = abs(amp) * sigma * np.sqrt(2 * np.pi) * math.erf(n_sigmas)
|
||||
area = gaussian_area + abs(amp) * width
|
||||
|
||||
target_area = abs(theta) / (np.pi / 2.0) * area
|
||||
sign = np.sign(theta)
|
||||
|
||||
if target_area > gaussian_area:
|
||||
width = (target_area - gaussian_area) / abs(amp)
|
||||
duration = round((width + n_sigmas * sigma) / sample_mult) * sample_mult
|
||||
return Play(
|
||||
GaussianSquare(amp=sign * amp, width=width, sigma=sigma, duration=duration),
|
||||
channel=instruction.channel,
|
||||
)
|
||||
else:
|
||||
amp_scale = sign * target_area / gaussian_area
|
||||
duration = round(n_sigmas * sigma / sample_mult) * sample_mult
|
||||
return Play(
|
||||
GaussianSquare(amp=amp * amp_scale, width=0, sigma=sigma, duration=duration),
|
||||
channel=instruction.channel,
|
||||
)
|
||||
else:
|
||||
raise QiskitError("RZXCalibrationBuilder only stretches/compresses GaussianSquare.")
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Builds the calibration schedule for the RZXGate(theta) with echos.
|
||||
|
||||
Args:
|
||||
node_op: Instruction of the RZXGate(theta). I.e. params[0] is theta.
|
||||
qubits: List of qubits for which to get the schedules. The first qubit is
|
||||
the control and the second is the target.
|
||||
|
||||
Returns:
|
||||
schedule: The calibration schedule for the RZXGate(theta).
|
||||
|
||||
Raises:
|
||||
QiskitError: if the control and target qubits cannot be identified or the backend
|
||||
does not support cx between the qubits.
|
||||
"""
|
||||
theta = node_op.params[0]
|
||||
q1, q2 = qubits[0], qubits[1]
|
||||
|
||||
if not self._inst_map.has("cx", qubits):
|
||||
raise QiskitError(
|
||||
"This transpilation pass requires the backend to support cx "
|
||||
"between qubits %i and %i." % (q1, q2)
|
||||
)
|
||||
|
||||
cx_sched = self._inst_map.get("cx", qubits=(q1, q2))
|
||||
rzx_theta = Schedule(name="rzx(%.3f)" % theta)
|
||||
rzx_theta.metadata["publisher"] = CalibrationPublisher.QISKIT
|
||||
|
||||
if theta == 0.0:
|
||||
return rzx_theta
|
||||
|
||||
crs, comp_tones = [], []
|
||||
control, target = None, None
|
||||
|
||||
for time, inst in cx_sched.instructions:
|
||||
|
||||
# Identify the CR pulses.
|
||||
if isinstance(inst, Play) and not isinstance(inst, ShiftPhase):
|
||||
if isinstance(inst.channel, ControlChannel):
|
||||
crs.append((time, inst))
|
||||
|
||||
# Identify the compensation tones.
|
||||
if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase):
|
||||
if isinstance(inst.pulse, GaussianSquare):
|
||||
comp_tones.append((time, inst))
|
||||
target = inst.channel.index
|
||||
control = q1 if target == q2 else q2
|
||||
|
||||
if control is None:
|
||||
raise QiskitError("Control qubit is None.")
|
||||
if target is None:
|
||||
raise QiskitError("Target qubit is None.")
|
||||
|
||||
echo_x = self._inst_map.get("x", qubits=control)
|
||||
|
||||
# Build the schedule
|
||||
|
||||
# Stretch/compress the CR gates and compensation tones
|
||||
cr1 = self.rescale_cr_inst(crs[0][1], theta)
|
||||
cr2 = self.rescale_cr_inst(crs[1][1], theta)
|
||||
|
||||
if len(comp_tones) == 0:
|
||||
comp1, comp2 = None, None
|
||||
elif len(comp_tones) == 2:
|
||||
comp1 = self.rescale_cr_inst(comp_tones[0][1], theta)
|
||||
comp2 = self.rescale_cr_inst(comp_tones[1][1], theta)
|
||||
else:
|
||||
raise QiskitError(
|
||||
"CX must have either 0 or 2 rotary tones between qubits %i and %i "
|
||||
"but %i were found." % (control, target, len(comp_tones))
|
||||
)
|
||||
|
||||
# Build the schedule for the RZXGate
|
||||
rzx_theta = rzx_theta.insert(0, cr1)
|
||||
|
||||
if comp1 is not None:
|
||||
rzx_theta = rzx_theta.insert(0, comp1)
|
||||
|
||||
rzx_theta = rzx_theta.insert(comp1.duration, echo_x)
|
||||
time = comp1.duration + echo_x.duration
|
||||
rzx_theta = rzx_theta.insert(time, cr2)
|
||||
|
||||
if comp2 is not None:
|
||||
rzx_theta = rzx_theta.insert(time, comp2)
|
||||
|
||||
time = 2 * comp1.duration + echo_x.duration
|
||||
rzx_theta = rzx_theta.insert(time, echo_x)
|
||||
|
||||
# Reverse direction of the ZX with Hadamard gates
|
||||
if control == qubits[0]:
|
||||
return rzx_theta
|
||||
else:
|
||||
rzc = self._inst_map.get("rz", [control], np.pi / 2)
|
||||
sxc = self._inst_map.get("sx", [control])
|
||||
rzt = self._inst_map.get("rz", [target], np.pi / 2)
|
||||
sxt = self._inst_map.get("sx", [target])
|
||||
h_sched = Schedule(name="hadamards")
|
||||
h_sched = h_sched.insert(0, rzc)
|
||||
h_sched = h_sched.insert(0, sxc)
|
||||
h_sched = h_sched.insert(sxc.duration, rzc)
|
||||
h_sched = h_sched.insert(0, rzt)
|
||||
h_sched = h_sched.insert(0, sxt)
|
||||
h_sched = h_sched.insert(sxc.duration, rzt)
|
||||
rzx_theta = h_sched.append(rzx_theta)
|
||||
return rzx_theta.append(h_sched)
|
||||
|
||||
|
||||
class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder):
|
||||
"""
|
||||
Creates calibrations for RZXGate(theta) by stretching and compressing
|
||||
Gaussian square pulses in the CX gate.
|
||||
|
||||
The ``RZXCalibrationBuilderNoEcho`` is a variation of the
|
||||
:class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` pass
|
||||
that creates calibrations for the cross-resonance pulses without inserting
|
||||
the echo pulses in the pulse schedule. This enables exposing the echo in
|
||||
the cross-resonance sequence as gates so that the transpiler can simplify them.
|
||||
The ``RZXCalibrationBuilderNoEcho`` only supports the hardware-native direction
|
||||
of the CX gate.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _filter_control(inst: (int, Union["Schedule", PulseInst])) -> bool:
|
||||
"""
|
||||
Looks for Gaussian square pulses applied to control channels.
|
||||
|
||||
Args:
|
||||
inst: Instructions to be filtered.
|
||||
|
||||
Returns:
|
||||
match: True if the instruction is a Play instruction with
|
||||
a Gaussian square pulse on the ControlChannel.
|
||||
"""
|
||||
if isinstance(inst[1], Play):
|
||||
if isinstance(inst[1].pulse, GaussianSquare) and isinstance(
|
||||
inst[1].channel, ControlChannel
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _filter_drive(inst: (int, Union["Schedule", PulseInst])) -> bool:
|
||||
"""
|
||||
Looks for Gaussian square pulses applied to drive channels.
|
||||
|
||||
Args:
|
||||
inst: Instructions to be filtered.
|
||||
|
||||
Returns:
|
||||
match: True if the instruction is a Play instruction with
|
||||
a Gaussian square pulse on the DriveChannel.
|
||||
"""
|
||||
if isinstance(inst[1], Play):
|
||||
if isinstance(inst[1].pulse, GaussianSquare) and isinstance(
|
||||
inst[1].channel, DriveChannel
|
||||
):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Builds the calibration schedule for the RZXGate(theta) without echos.
|
||||
|
||||
Args:
|
||||
node_op: Instruction of the RZXGate(theta). I.e. params[0] is theta.
|
||||
qubits: List of qubits for which to get the schedules. The first qubit is
|
||||
the control and the second is the target.
|
||||
|
||||
Returns:
|
||||
schedule: The calibration schedule for the RZXGate(theta).
|
||||
|
||||
Raises:
|
||||
QiskitError: If the control and target qubits cannot be identified, or the backend
|
||||
does not support a cx gate between the qubits, or the backend does not natively
|
||||
support the specified direction of the cx.
|
||||
"""
|
||||
theta = node_op.params[0]
|
||||
q1, q2 = qubits[0], qubits[1]
|
||||
|
||||
if not self._inst_map.has("cx", qubits):
|
||||
raise QiskitError(
|
||||
"This transpilation pass requires the backend to support cx "
|
||||
"between qubits %i and %i." % (q1, q2)
|
||||
)
|
||||
|
||||
cx_sched = self._inst_map.get("cx", qubits=(q1, q2))
|
||||
rzx_theta = Schedule(name="rzx(%.3f)" % theta)
|
||||
rzx_theta.metadata["publisher"] = CalibrationPublisher.QISKIT
|
||||
|
||||
if theta == 0.0:
|
||||
return rzx_theta
|
||||
|
||||
control, target = None, None
|
||||
|
||||
for _, inst in cx_sched.instructions:
|
||||
# Identify the compensation tones.
|
||||
if isinstance(inst.channel, DriveChannel) and isinstance(inst, Play):
|
||||
if isinstance(inst.pulse, GaussianSquare):
|
||||
target = inst.channel.index
|
||||
control = q1 if target == q2 else q2
|
||||
|
||||
if control is None:
|
||||
raise QiskitError("Control qubit is None.")
|
||||
if target is None:
|
||||
raise QiskitError("Target qubit is None.")
|
||||
|
||||
if control != qubits[0]:
|
||||
raise QiskitError(
|
||||
"RZXCalibrationBuilderNoEcho only supports hardware-native RZX gates."
|
||||
)
|
||||
|
||||
# Get the filtered Schedule instructions for the CR gates and compensation tones.
|
||||
crs = cx_sched.filter(*[self._filter_control]).instructions
|
||||
rotaries = cx_sched.filter(*[self._filter_drive]).instructions
|
||||
|
||||
# Stretch/compress the CR gates and compensation tones.
|
||||
cr = self.rescale_cr_inst(crs[0][1], 2 * theta)
|
||||
rot = self.rescale_cr_inst(rotaries[0][1], 2 * theta)
|
||||
|
||||
# Build the schedule for the RZXGate without the echos.
|
||||
rzx_theta = rzx_theta.insert(0, cr)
|
||||
rzx_theta = rzx_theta.insert(0, rot)
|
||||
rzx_theta = rzx_theta.insert(0, Delay(cr.duration, DriveChannel(control)))
|
||||
|
||||
return rzx_theta
|
||||
|
||||
|
||||
class PulseGates(CalibrationBuilder):
|
||||
"""Pulse gate adding pass.
|
||||
|
||||
This pass adds gate calibrations from the supplied ``InstructionScheduleMap``
|
||||
to a quantum circuit.
|
||||
|
||||
This pass checks each DAG circuit node and acquires a corresponding schedule from
|
||||
the instruction schedule map object that may be provided by the target backend.
|
||||
Because this map is a mutable object, the end-user can provide a configured backend to
|
||||
execute the circuit with customized gate implementations.
|
||||
|
||||
This mapping object returns a schedule with "publisher" metadata which is an integer Enum
|
||||
value representing who created the gate schedule.
|
||||
If the gate schedule is provided by end-users, this pass attaches the schedule to
|
||||
the DAG circuit as a calibration.
|
||||
|
||||
This pass allows users to easily override quantum circuit with custom gate definitions
|
||||
without directly dealing with those schedules.
|
||||
|
||||
References
|
||||
* [1] OpenQASM 3: A broader and deeper quantum assembly language
|
||||
https://arxiv.org/abs/2104.14722
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inst_map: InstructionScheduleMap,
|
||||
):
|
||||
"""Create new pass.
|
||||
|
||||
Args:
|
||||
inst_map: Instruction schedule map that user may override.
|
||||
"""
|
||||
super().__init__()
|
||||
self.inst_map = inst_map
|
||||
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
return self.inst_map.has(instruction=node_op.name, qubits=qubits)
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Gets the calibrated schedule for the given instruction and qubits.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return Schedule of target gate instruction.
|
||||
"""
|
||||
return self.inst_map.get(node_op.name, qubits, *node_op.params)
|
||||
# pylint: disable=unused-import
|
||||
from .pulse_gate import PulseGates
|
||||
from .rzx_builder import RZXCalibrationBuilder, RZXCalibrationBuilderNoEcho
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2022.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Exception for errors raised by the calibration pass module."""
|
||||
from qiskit.exceptions import QiskitError
|
||||
|
||||
|
||||
class CalibrationNotAvailable(QiskitError):
|
||||
"""Raised when calibration generation fails.
|
||||
|
||||
.. note::
|
||||
This error is meant to caught by CalibrationBuilder and ignored.
|
||||
"""
|
|
@ -0,0 +1,85 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2022.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""Instruction scheduel map reference pass."""
|
||||
|
||||
from typing import List, Union
|
||||
|
||||
from qiskit.circuit import Instruction as CircuitInst
|
||||
from qiskit.pulse import (
|
||||
Schedule,
|
||||
ScheduleBlock,
|
||||
)
|
||||
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap
|
||||
|
||||
from .base_builder import CalibrationBuilder
|
||||
|
||||
|
||||
class PulseGates(CalibrationBuilder):
|
||||
"""Pulse gate adding pass.
|
||||
|
||||
This pass adds gate calibrations from the supplied ``InstructionScheduleMap``
|
||||
to a quantum circuit.
|
||||
|
||||
This pass checks each DAG circuit node and acquires a corresponding schedule from
|
||||
the instruction schedule map object that may be provided by the target backend.
|
||||
Because this map is a mutable object, the end-user can provide a configured backend to
|
||||
execute the circuit with customized gate implementations.
|
||||
|
||||
This mapping object returns a schedule with "publisher" metadata which is an integer Enum
|
||||
value representing who created the gate schedule.
|
||||
If the gate schedule is provided by end-users, this pass attaches the schedule to
|
||||
the DAG circuit as a calibration.
|
||||
|
||||
This pass allows users to easily override quantum circuit with custom gate definitions
|
||||
without directly dealing with those schedules.
|
||||
|
||||
References
|
||||
* [1] OpenQASM 3: A broader and deeper quantum assembly language
|
||||
https://arxiv.org/abs/2104.14722
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
inst_map: InstructionScheduleMap,
|
||||
):
|
||||
"""Create new pass.
|
||||
|
||||
Args:
|
||||
inst_map: Instruction schedule map that user may override.
|
||||
"""
|
||||
super().__init__()
|
||||
self.inst_map = inst_map
|
||||
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
return self.inst_map.has(instruction=node_op.name, qubits=qubits)
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Gets the calibrated schedule for the given instruction and qubits.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return Schedule of target gate instruction.
|
||||
"""
|
||||
return self.inst_map.get(node_op.name, qubits, *node_op.params)
|
|
@ -0,0 +1,387 @@
|
|||
# This code is part of Qiskit.
|
||||
#
|
||||
# (C) Copyright IBM 2022.
|
||||
#
|
||||
# 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.
|
||||
|
||||
"""RZX calibration builders."""
|
||||
|
||||
import math
|
||||
import warnings
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import enum
|
||||
import numpy as np
|
||||
from qiskit.circuit import Instruction as CircuitInst
|
||||
from qiskit.circuit.library.standard_gates import RZXGate
|
||||
from qiskit.exceptions import QiskitError
|
||||
from qiskit.pulse import (
|
||||
Play,
|
||||
Delay,
|
||||
Schedule,
|
||||
ScheduleBlock,
|
||||
ControlChannel,
|
||||
DriveChannel,
|
||||
GaussianSquare,
|
||||
Waveform,
|
||||
)
|
||||
from qiskit.pulse.filters import filter_instructions
|
||||
from qiskit.pulse.instruction_schedule_map import InstructionScheduleMap, CalibrationPublisher
|
||||
|
||||
from .base_builder import CalibrationBuilder
|
||||
from .exceptions import CalibrationNotAvailable
|
||||
|
||||
|
||||
class CXCalType(enum.Enum):
|
||||
"""Estimated calibration type of backend CX gate."""
|
||||
|
||||
ECR = "Echoed Cross Resonance"
|
||||
DIRECT_CX = "Direct CX"
|
||||
|
||||
|
||||
class RZXCalibrationBuilder(CalibrationBuilder):
|
||||
"""
|
||||
Creates calibrations for RZXGate(theta) by stretching and compressing
|
||||
Gaussian square pulses in the CX gate. This is done by retrieving (for a given pair of
|
||||
qubits) the CX schedule in the instruction schedule map of the backend defaults.
|
||||
The CX schedule must be an echoed cross-resonance gate optionally with rotary tones.
|
||||
The cross-resonance drive tones and rotary pulses must be Gaussian square pulses.
|
||||
The width of the Gaussian square pulse is adjusted so as to match the desired rotation angle.
|
||||
If the rotation angle is small such that the width disappears then the amplitude of the
|
||||
zero width Gaussian square pulse (i.e. a Gaussian) is reduced to reach the target rotation
|
||||
angle. Additional details can be found in https://arxiv.org/abs/2012.11660.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
instruction_schedule_map: InstructionScheduleMap = None,
|
||||
qubit_channel_mapping: List[List[str]] = None,
|
||||
verbose: bool = True,
|
||||
):
|
||||
"""
|
||||
Initializes a RZXGate calibration builder.
|
||||
|
||||
Args:
|
||||
instruction_schedule_map: The :obj:`InstructionScheduleMap` object representing the
|
||||
default pulse calibrations for the target backend
|
||||
qubit_channel_mapping: The list mapping qubit indices to the list of
|
||||
channel names that apply on that qubit.
|
||||
verbose: Set True to raise a user warning when RZX schedule cannot be built.
|
||||
|
||||
Raises:
|
||||
QiskitError: Instruction schedule map is not provided.
|
||||
"""
|
||||
super().__init__()
|
||||
|
||||
if instruction_schedule_map is None:
|
||||
raise QiskitError("Calibrations can only be added to Pulse-enabled backends")
|
||||
|
||||
if qubit_channel_mapping:
|
||||
warnings.warn(
|
||||
"'qubit_channel_mapping' is no longer used. This value is ignored.",
|
||||
DeprecationWarning,
|
||||
)
|
||||
|
||||
self._inst_map = instruction_schedule_map
|
||||
self._verbose = verbose
|
||||
|
||||
def supported(self, node_op: CircuitInst, qubits: List) -> bool:
|
||||
"""Determine if a given node supports the calibration.
|
||||
|
||||
Args:
|
||||
node_op: Target instruction object.
|
||||
qubits: Integer qubit indices to check.
|
||||
|
||||
Returns:
|
||||
Return ``True`` is calibration can be provided.
|
||||
"""
|
||||
return isinstance(node_op, RZXGate) and self._inst_map.has("cx", qubits)
|
||||
|
||||
@staticmethod
|
||||
def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> Play:
|
||||
"""
|
||||
Args:
|
||||
instruction: The instruction from which to create a new shortened or lengthened pulse.
|
||||
theta: desired angle, pi/2 is assumed to be the angle that the pulse in the given
|
||||
play instruction implements.
|
||||
sample_mult: All pulses must be a multiple of sample_mult.
|
||||
|
||||
Returns:
|
||||
qiskit.pulse.Play: The play instruction with the stretched compressed
|
||||
GaussianSquare pulse.
|
||||
|
||||
Raises:
|
||||
QiskitError: if rotation angle is not assigned.
|
||||
"""
|
||||
try:
|
||||
theta = float(theta)
|
||||
except TypeError as ex:
|
||||
raise QiskitError("Target rotation angle is not assigned.") from ex
|
||||
|
||||
# This method is called for instructions which are guaranteed to play GaussianSquare pulse
|
||||
amp = instruction.pulse.amp
|
||||
width = instruction.pulse.width
|
||||
sigma = instruction.pulse.sigma
|
||||
n_sigmas = (instruction.pulse.duration - width) / sigma
|
||||
|
||||
# The error function is used because the Gaussian may have chopped tails.
|
||||
gaussian_area = abs(amp) * sigma * np.sqrt(2 * np.pi) * math.erf(n_sigmas)
|
||||
area = gaussian_area + abs(amp) * width
|
||||
|
||||
target_area = abs(theta) / (np.pi / 2.0) * area
|
||||
sign = np.sign(theta)
|
||||
|
||||
if target_area > gaussian_area:
|
||||
width = (target_area - gaussian_area) / abs(amp)
|
||||
duration = round((width + n_sigmas * sigma) / sample_mult) * sample_mult
|
||||
return Play(
|
||||
GaussianSquare(amp=sign * amp, width=width, sigma=sigma, duration=duration),
|
||||
channel=instruction.channel,
|
||||
)
|
||||
else:
|
||||
amp_scale = sign * target_area / gaussian_area
|
||||
duration = round(n_sigmas * sigma / sample_mult) * sample_mult
|
||||
return Play(
|
||||
GaussianSquare(amp=amp * amp_scale, width=0, sigma=sigma, duration=duration),
|
||||
channel=instruction.channel,
|
||||
)
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Builds the calibration schedule for the RZXGate(theta) with echos.
|
||||
|
||||
Args:
|
||||
node_op: Instruction of the RZXGate(theta). I.e. params[0] is theta.
|
||||
qubits: List of qubits for which to get the schedules. The first qubit is
|
||||
the control and the second is the target.
|
||||
|
||||
Returns:
|
||||
schedule: The calibration schedule for the RZXGate(theta).
|
||||
|
||||
Raises:
|
||||
QiskitError: If the control and target qubits cannot be identified.
|
||||
CalibrationNotAvailable: RZX schedule cannot be built for input node.
|
||||
"""
|
||||
theta = node_op.params[0]
|
||||
|
||||
rzx_theta = Schedule(name="rzx(%.3f)" % theta)
|
||||
rzx_theta.metadata["publisher"] = CalibrationPublisher.QISKIT
|
||||
|
||||
if np.isclose(theta, 0.0):
|
||||
return rzx_theta
|
||||
|
||||
cx_sched = self._inst_map.get("cx", qubits=qubits)
|
||||
cal_type, cr_tones, comp_tones = _check_calibration_type(cx_sched)
|
||||
|
||||
if cal_type != CXCalType.ECR:
|
||||
if self._verbose:
|
||||
warnings.warn(
|
||||
f"CX instruction for qubits {qubits} is likely {cal_type.value} sequence. "
|
||||
"Pulse stretch for this calibration is not currently implemented. "
|
||||
"RZX schedule is not generated for this qubit pair.",
|
||||
UserWarning,
|
||||
)
|
||||
raise CalibrationNotAvailable
|
||||
|
||||
if len(comp_tones) == 0:
|
||||
raise QiskitError(
|
||||
f"{repr(cx_sched)} has no target compensation tones. "
|
||||
"Native CR direction cannot be determined."
|
||||
)
|
||||
|
||||
# Determine native direction, assuming only single drive channel per qubit.
|
||||
# This guarantees channel and qubit index equality.
|
||||
is_native = comp_tones[0].channel.index == qubits[1]
|
||||
|
||||
stretched_cr_tones = list(map(lambda p: self.rescale_cr_inst(p, theta), cr_tones))
|
||||
stretched_comp_tones = list(map(lambda p: self.rescale_cr_inst(p, theta), comp_tones))
|
||||
|
||||
if is_native:
|
||||
xgate = self._inst_map.get("x", qubits[0])
|
||||
|
||||
for cr, comp in zip(stretched_cr_tones, stretched_comp_tones):
|
||||
current_dur = rzx_theta.duration
|
||||
rzx_theta.insert(current_dur, cr, inplace=True)
|
||||
rzx_theta.insert(current_dur, comp, inplace=True)
|
||||
rzx_theta.append(xgate, inplace=True)
|
||||
|
||||
else:
|
||||
# Add hadamard gate to flip
|
||||
xgate = self._inst_map.get("x", qubits[1])
|
||||
szc = self._inst_map.get("rz", qubits[1], np.pi / 2)
|
||||
sxc = self._inst_map.get("sx", qubits[1])
|
||||
szt = self._inst_map.get("rz", qubits[0], np.pi / 2)
|
||||
sxt = self._inst_map.get("sx", qubits[0])
|
||||
|
||||
# Hadamard to control
|
||||
rzx_theta.insert(0, szc, inplace=True)
|
||||
rzx_theta.insert(0, sxc, inplace=True)
|
||||
rzx_theta.insert(sxc.duration, szc, inplace=True)
|
||||
|
||||
# Hadamard to target
|
||||
rzx_theta.insert(0, szt, inplace=True)
|
||||
rzx_theta.insert(0, sxt, inplace=True)
|
||||
rzx_theta.insert(sxt.duration, szt, inplace=True)
|
||||
|
||||
for cr, comp in zip(stretched_cr_tones, stretched_comp_tones):
|
||||
current_dur = rzx_theta.duration
|
||||
rzx_theta.insert(current_dur, cr, inplace=True)
|
||||
rzx_theta.insert(current_dur, comp, inplace=True)
|
||||
rzx_theta.append(xgate, inplace=True)
|
||||
|
||||
current_dur = rzx_theta.duration
|
||||
|
||||
# Hadamard to control
|
||||
rzx_theta.insert(current_dur, szc, inplace=True)
|
||||
rzx_theta.insert(current_dur, sxc, inplace=True)
|
||||
rzx_theta.insert(current_dur + sxc.duration, szc, inplace=True)
|
||||
|
||||
# Hadamard to target
|
||||
rzx_theta.insert(current_dur, szt, inplace=True)
|
||||
rzx_theta.insert(current_dur, sxt, inplace=True)
|
||||
rzx_theta.insert(current_dur + sxt.duration, szt, inplace=True)
|
||||
|
||||
return rzx_theta
|
||||
|
||||
|
||||
class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder):
|
||||
"""
|
||||
Creates calibrations for RZXGate(theta) by stretching and compressing
|
||||
Gaussian square pulses in the CX gate.
|
||||
|
||||
The ``RZXCalibrationBuilderNoEcho`` is a variation of the
|
||||
:class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` pass
|
||||
that creates calibrations for the cross-resonance pulses without inserting
|
||||
the echo pulses in the pulse schedule. This enables exposing the echo in
|
||||
the cross-resonance sequence as gates so that the transpiler can simplify them.
|
||||
The ``RZXCalibrationBuilderNoEcho`` only supports the hardware-native direction
|
||||
of the CX gate.
|
||||
"""
|
||||
|
||||
def get_calibration(self, node_op: CircuitInst, qubits: List) -> Union[Schedule, ScheduleBlock]:
|
||||
"""Builds the calibration schedule for the RZXGate(theta) without echos.
|
||||
|
||||
Args:
|
||||
node_op: Instruction of the RZXGate(theta). I.e. params[0] is theta.
|
||||
qubits: List of qubits for which to get the schedules. The first qubit is
|
||||
the control and the second is the target.
|
||||
|
||||
Returns:
|
||||
schedule: The calibration schedule for the RZXGate(theta).
|
||||
|
||||
Raises:
|
||||
QiskitError: If the control and target qubits cannot be identified,
|
||||
or the backend does not natively support the specified direction of the cx.
|
||||
CalibrationNotAvailable: RZX schedule cannot be built for input node.
|
||||
"""
|
||||
theta = node_op.params[0]
|
||||
|
||||
rzx_theta = Schedule(name="rzx(%.3f)" % theta)
|
||||
rzx_theta.metadata["publisher"] = CalibrationPublisher.QISKIT
|
||||
|
||||
if np.isclose(theta, 0.0):
|
||||
return rzx_theta
|
||||
|
||||
cx_sched = self._inst_map.get("cx", qubits=qubits)
|
||||
cal_type, cr_tones, comp_tones = _check_calibration_type(cx_sched)
|
||||
|
||||
if cal_type != CXCalType.ECR:
|
||||
if self._verbose:
|
||||
warnings.warn(
|
||||
f"CX instruction for qubits {qubits} is likely {cal_type.value} sequence. "
|
||||
"Pulse stretch for this calibration is not currently implemented. "
|
||||
"RZX schedule is not generated for this qubit pair.",
|
||||
UserWarning,
|
||||
)
|
||||
raise CalibrationNotAvailable
|
||||
|
||||
if len(comp_tones) == 0:
|
||||
raise QiskitError(
|
||||
f"{repr(cx_sched)} has no target compensation tones. "
|
||||
"Native CR direction cannot be determined."
|
||||
)
|
||||
|
||||
# Determine native direction, assuming only single drive channel per qubit.
|
||||
# This guarantees channel and qubit index equality.
|
||||
is_native = comp_tones[0].channel.index == qubits[1]
|
||||
|
||||
stretched_cr_tone = self.rescale_cr_inst(cr_tones[0], 2 * theta)
|
||||
stretched_comp_tone = self.rescale_cr_inst(comp_tones[0], 2 * theta)
|
||||
|
||||
if is_native:
|
||||
# Placeholder to make pulse gate work
|
||||
delay = Delay(stretched_cr_tone.duration, DriveChannel(qubits[0]))
|
||||
|
||||
# This doesn't remove unwanted instruction such as ZI
|
||||
# These terms are eliminated along with other gates around the pulse gate.
|
||||
rzx_theta = rzx_theta.insert(0, stretched_cr_tone, inplace=True)
|
||||
rzx_theta = rzx_theta.insert(0, stretched_comp_tone, inplace=True)
|
||||
rzx_theta = rzx_theta.insert(0, delay, inplace=True)
|
||||
|
||||
return rzx_theta
|
||||
|
||||
raise QiskitError("RZXCalibrationBuilderNoEcho only supports hardware-native RZX gates.")
|
||||
|
||||
|
||||
def _filter_cr_tone(time_inst_tup):
|
||||
"""A helper function to filter pulses on control channels."""
|
||||
valid_types = ["GaussianSquare"]
|
||||
|
||||
_, inst = time_inst_tup
|
||||
if isinstance(inst, Play) and isinstance(inst.channel, ControlChannel):
|
||||
pulse = inst.pulse
|
||||
if isinstance(pulse, Waveform) or pulse.pulse_type in valid_types:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _filter_comp_tone(time_inst_tup):
|
||||
"""A helper function to filter pulses on drive channels."""
|
||||
valid_types = ["GaussianSquare"]
|
||||
|
||||
_, inst = time_inst_tup
|
||||
if isinstance(inst, Play) and isinstance(inst.channel, DriveChannel):
|
||||
pulse = inst.pulse
|
||||
if isinstance(pulse, Waveform) or pulse.pulse_type in valid_types:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def _check_calibration_type(cx_sched) -> Tuple[CXCalType, List[Play], List[Play]]:
|
||||
"""A helper function to check type of CR calibration.
|
||||
|
||||
Args:
|
||||
cx_sched: A target schedule to stretch.
|
||||
|
||||
Returns:
|
||||
Filtered instructions and most-likely type of calibration.
|
||||
|
||||
Raises:
|
||||
QiskitError: Unknown calibration type is detected.
|
||||
"""
|
||||
cr_tones = list(
|
||||
map(lambda t: t[1], filter_instructions(cx_sched, [_filter_cr_tone]).instructions)
|
||||
)
|
||||
comp_tones = list(
|
||||
map(lambda t: t[1], filter_instructions(cx_sched, [_filter_comp_tone]).instructions)
|
||||
)
|
||||
|
||||
if len(cr_tones) == 2 and len(comp_tones) in (0, 2):
|
||||
# ECR can be implemented without compensation tone at price of lower fidelity.
|
||||
# Remarkable noisy terms are usually eliminated by echo.
|
||||
return CXCalType.ECR, cr_tones, comp_tones
|
||||
|
||||
if len(cr_tones) == 1 and len(comp_tones) == 1:
|
||||
# Direct CX must have compensation tone on target qubit.
|
||||
# Otherwise, it cannot eliminate IX interaction.
|
||||
return CXCalType.DIRECT_CX, cr_tones, comp_tones
|
||||
|
||||
raise QiskitError(
|
||||
f"{repr(cx_sched)} is undefined pulse sequence. "
|
||||
"Check if this is a calibration for CX gate."
|
||||
)
|
|
@ -0,0 +1,22 @@
|
|||
---
|
||||
upgrade:
|
||||
- |
|
||||
:class:`.RZXCalibrationBuilder` and :class:`.RZXCalibrationBuilderNoEcho`
|
||||
have been upgraded to skip stretching CX gates implemented by
|
||||
non-echoed cross resonance (ECR) sequence to avoid termination of the pass
|
||||
with unexpected errors.
|
||||
These passes take new argument ``verbose`` that controls warning.
|
||||
If ``verbose=True`` is set, pass raises user warning when it enconters
|
||||
non-ECR sequence.
|
||||
deprecations:
|
||||
- |
|
||||
The unused argument ``qubit_channel_mapping`` in the
|
||||
:class:`.RZXCalibrationBuilder` and :class:`.RZXCalibrationBuilderNoEcho`
|
||||
transpiler passes have been deprecated and will be removed.
|
||||
This argument is no longer used.
|
||||
other:
|
||||
- |
|
||||
The transpiler pass module :mod:`~qiskit.transpiler.passes.calibration` has been reorganized.
|
||||
:class:`.PulseGates` has been moved to :mod:`~qiskit.transpiler.passes.calibration.pulse_gates`,
|
||||
and :class:`.RZXCalibrationBuilder` and :class:`.RZXCalibrationBuilderNoEcho`
|
||||
have been moved to :mod:`~qiskit.transpiler.passes.calibration.rzx_builders`.
|
|
@ -18,7 +18,17 @@ import numpy as np
|
|||
from ddt import data, ddt
|
||||
|
||||
from qiskit import circuit, schedule
|
||||
from qiskit.pulse import ControlChannel, Delay, DriveChannel, GaussianSquare, Play, ShiftPhase
|
||||
from qiskit.pulse import (
|
||||
ControlChannel,
|
||||
Delay,
|
||||
DriveChannel,
|
||||
GaussianSquare,
|
||||
Waveform,
|
||||
Play,
|
||||
ShiftPhase,
|
||||
InstructionScheduleMap,
|
||||
Schedule,
|
||||
)
|
||||
from qiskit.test import QiskitTestCase
|
||||
from qiskit.providers.fake_provider import FakeAthens
|
||||
from qiskit.transpiler import PassManager
|
||||
|
@ -60,6 +70,30 @@ class TestRZXCalibrationBuilder(TestCalibrationBuilder):
|
|||
expected_duration = round((width + n_sigmas * sigma) / sample_mult) * sample_mult
|
||||
self.assertEqual(scaled.duration, expected_duration)
|
||||
|
||||
def test_pass_alive_with_dcx_ish(self):
|
||||
"""Test if the pass is not terminated by error with direct CX input."""
|
||||
cx_sched = Schedule()
|
||||
# Fake direct cr
|
||||
cx_sched.insert(0, Play(GaussianSquare(800, 0.2, 64, 544), ControlChannel(1)), inplace=True)
|
||||
# Fake direct compensation tone
|
||||
# Compensation tone doesn't have dedicated pulse class.
|
||||
# So it's reported as a waveform now.
|
||||
compensation_tone = Waveform(0.1 * np.ones(800, dtype=complex))
|
||||
cx_sched.insert(0, Play(compensation_tone, DriveChannel(0)), inplace=True)
|
||||
|
||||
inst_map = InstructionScheduleMap()
|
||||
inst_map.add("cx", (1, 0), schedule=cx_sched)
|
||||
|
||||
theta = pi / 3
|
||||
rzx_qc = circuit.QuantumCircuit(2)
|
||||
rzx_qc.rzx(theta, 1, 0)
|
||||
|
||||
pass_ = RZXCalibrationBuilder(instruction_schedule_map=inst_map)
|
||||
with self.assertWarns(UserWarning):
|
||||
# User warning that says q0 q1 is invalid
|
||||
cal_qc = PassManager(pass_).run(rzx_qc)
|
||||
self.assertEqual(cal_qc, rzx_qc)
|
||||
|
||||
|
||||
class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder):
|
||||
"""Test RZXCalibrationBuilderNoEcho."""
|
||||
|
@ -77,8 +111,7 @@ class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder):
|
|||
|
||||
# apply the RZXCalibrationBuilderNoEcho.
|
||||
pass_ = RZXCalibrationBuilderNoEcho(
|
||||
instruction_schedule_map=self.backend.defaults().instruction_schedule_map,
|
||||
qubit_channel_mapping=self.backend.configuration().qubit_channel_mapping,
|
||||
instruction_schedule_map=self.backend.defaults().instruction_schedule_map
|
||||
)
|
||||
cal_qc = PassManager(pass_).run(rzx_qc)
|
||||
rzx_qc_duration = schedule(cal_qc, self.backend).duration
|
||||
|
@ -138,3 +171,27 @@ class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder):
|
|||
scaled_pulse = scaled.pulse
|
||||
|
||||
self.assertIsInstance(scaled_pulse.amp, complex)
|
||||
|
||||
def test_pass_alive_with_dcx_ish(self):
|
||||
"""Test if the pass is not terminated by error with direct CX input."""
|
||||
cx_sched = Schedule()
|
||||
# Fake direct cr
|
||||
cx_sched.insert(0, Play(GaussianSquare(800, 0.2, 64, 544), ControlChannel(1)), inplace=True)
|
||||
# Fake direct compensation tone
|
||||
# Compensation tone doesn't have dedicated pulse class.
|
||||
# So it's reported as a waveform now.
|
||||
compensation_tone = Waveform(0.1 * np.ones(800, dtype=complex))
|
||||
cx_sched.insert(0, Play(compensation_tone, DriveChannel(0)), inplace=True)
|
||||
|
||||
inst_map = InstructionScheduleMap()
|
||||
inst_map.add("cx", (1, 0), schedule=cx_sched)
|
||||
|
||||
theta = pi / 3
|
||||
rzx_qc = circuit.QuantumCircuit(2)
|
||||
rzx_qc.rzx(theta, 1, 0)
|
||||
|
||||
pass_ = RZXCalibrationBuilderNoEcho(instruction_schedule_map=inst_map)
|
||||
with self.assertWarns(UserWarning):
|
||||
# User warning that says q0 q1 is invalid
|
||||
cal_qc = PassManager(pass_).run(rzx_qc)
|
||||
self.assertEqual(cal_qc, rzx_qc)
|
||||
|
|
Loading…
Reference in New Issue