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

Added the class RZXCalibrationBuilderNoEcho #6300

Merged
merged 15 commits into from
May 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
125 changes: 123 additions & 2 deletions qiskit/transpiler/passes/scheduling/calibration_creators.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,20 @@
"""Calibration creators."""

import math
from typing import List
from typing import List, Union
from abc import abstractmethod
import numpy as np

from qiskit.pulse import Play, ShiftPhase, Schedule, ControlChannel, DriveChannel, GaussianSquare
from qiskit.pulse import (
Play,
Delay,
ShiftPhase,
Schedule,
ControlChannel,
DriveChannel,
GaussianSquare,
)
from qiskit.pulse.instructions.instruction import Instruction
from qiskit.exceptions import QiskitError
from qiskit.providers import basebackend
from qiskit.dagcircuit import DAGNode
Expand Down Expand Up @@ -254,3 +263,115 @@ def get_calibration(self, params: List, qubits: List) -> Schedule:
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 RZXCalibrationBuilder
as it 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", Instruction])) -> 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", Instruction])) -> 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, params: List, qubits: List) -> Schedule:
"""
Builds the calibration schedule for the RZXGate(theta) without echos.
Args:
params: Parameters 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 = 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)

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.")
catornow marked this conversation as resolved.
Show resolved Hide resolved

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
features:
- |
The RZXCalibrationBuilderNoEcho creates calibrations for RZXGate(theta) 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.
103 changes: 103 additions & 0 deletions test/python/pulse/test_calibrationbuilder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2020.
#
# 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 the RZXCalibrationBuilderNoEcho."""

from math import pi, erf, ceil

import numpy as np

from qiskit import circuit, schedule
from qiskit.transpiler import PassManager
from qiskit.test import QiskitTestCase
from qiskit.pulse import (
Play,
Delay,
ShiftPhase,
ControlChannel,
DriveChannel,
GaussianSquare,
)
from qiskit.transpiler.passes.scheduling.calibration_creators import (
RZXCalibrationBuilderNoEcho,
)
from qiskit.test.mock import FakeAthens


class TestCalibrationBuilder(QiskitTestCase):
"""Test the Calibration Builder."""

def setUp(self):
super().setUp()
self.backend = FakeAthens()
self.inst_map = self.backend.defaults().instruction_schedule_map


class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder):
"""Test RZXCalibrationBuilderNoEcho."""

def test_rzx_calibration_builder(self):
"""Test whether RZXCalibrationBuilderNoEcho scales pulses correctly."""

# Define a circuit with one RZX gate and an angle theta.
theta = pi / 3
rzx_qc = circuit.QuantumCircuit(2)
rzx_qc.rzx(theta / 2, 1, 0)

# Verify that there are no calibrations for this circuit yet.
self.assertEqual(rzx_qc.calibrations, {})

# apply the RZXCalibrationBuilderNoEcho.
pass_ = RZXCalibrationBuilderNoEcho(self.backend)
cal_qc = PassManager(pass_).run(rzx_qc)
rzx_qc_duration = schedule(cal_qc, self.backend).duration

# Check that the calibrations contain the correct instructions
# and pulses on the correct channels.
# pylint: disable=no-member
rzx_qc_instructions = cal_qc.calibrations["rzx"][((1, 0), (theta / 2,))].instructions
self.assertEqual(rzx_qc_instructions[0][1].channel, DriveChannel(0))
self.assertTrue(isinstance(rzx_qc_instructions[0][1], Play))
self.assertTrue(isinstance(rzx_qc_instructions[0][1].pulse, GaussianSquare))
self.assertEqual(rzx_qc_instructions[1][1].channel, DriveChannel(1))
self.assertTrue(isinstance(rzx_qc_instructions[1][1], Delay))
self.assertEqual(rzx_qc_instructions[2][1].channel, ControlChannel(1))
self.assertTrue(isinstance(rzx_qc_instructions[2][1], Play))
self.assertTrue(isinstance(rzx_qc_instructions[2][1].pulse, GaussianSquare))

# Calculate the duration of one scaled Gaussian square pulse from the CX gate.
cx_sched = self.inst_map.get("cx", qubits=(1, 0))

crs = []
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))

pulse_ = crs[0][1].pulse
amp = pulse_.amp
width = pulse_.width
sigma = pulse_.sigma
n_sigmas = (pulse_.duration - width) / sigma
sample_mult = 16

gaussian_area = abs(amp) * sigma * np.sqrt(2 * np.pi) * erf(n_sigmas)
area = gaussian_area + abs(amp) * width
target_area = abs(theta) / (np.pi / 2.0) * area
width = (target_area - gaussian_area) / abs(amp)
duration = ceil((width + n_sigmas * sigma) / sample_mult) * sample_mult

# Check whether the durations of the RZX pulse and
# the scaled CR pulse from the CX gate match.
self.assertEqual(rzx_qc_duration, duration)