From 8df6123d1962c7b05978d9278f375eb345cc568b Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 26 Apr 2021 16:06:40 +0200 Subject: [PATCH 01/12] Added the class RZXCalibrationBuilderNoEcho --- .../passes/scheduling/calibration_creators.py | 103 +++++++++++++++++- ...tion-builder-no-echo-fdc1a0cac05c1905.yaml | 7 ++ 2 files changed, 108 insertions(+), 2 deletions(-) create mode 100644 releasenotes/notes/calibration-builder-no-echo-fdc1a0cac05c1905.yaml diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 77abfb1b53be..88184f6fcade 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -13,11 +13,12 @@ """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, ShiftPhase, Schedule, ControlChannel, DriveChannel, GaussianSquare, Delay +from qiskit.pulse.instructions.instruction import Instruction from qiskit.exceptions import QiskitError from qiskit.providers import basebackend from qiskit.dagcircuit import DAGNode @@ -245,3 +246,101 @@ 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. + """ + + def __init__(self, backend: basebackend): + """ + Initializes a RZXGate calibration builder without echos. + Args: + backend: Backend for which to construct the gates. + """ + super().__init__(backend) + + def _filter_instruction(self, inst: (int, Union['Schedule', Instruction])) -> bool: + """ + Filters the Schedule instructions for a play instruction with a Gaussian square pulse. + Args: + inst: Instructions to be filtered. + Returns: + match: True if the instruction is a play instruction and contains + a Gaussian square pulse. + """ + if isinstance(inst[1], Play) and isinstance(inst[1].pulse, GaussianSquare): + 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, the backend + does not support cx 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 time, inst in cx_sched.instructions: + # Identify the compensation tones. + if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase): + 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.') + + # Get the filtered Schedule instructions for the CR gates and compensation tones. + crs = cx_sched.filter(*[self._filter_instruction], + channels=[ControlChannel(target), + ControlChannel(control)]).instructions + rotaries = cx_sched.filter(*[self._filter_instruction], + channels=[DriveChannel(target)]).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))) + + # Reverse direction of the ZX. + if control == qubits[0]: + return rzx_theta + else: + raise QiskitError('Reverse direction not supported.') diff --git a/releasenotes/notes/calibration-builder-no-echo-fdc1a0cac05c1905.yaml b/releasenotes/notes/calibration-builder-no-echo-fdc1a0cac05c1905.yaml new file mode 100644 index 000000000000..27ac9fa75013 --- /dev/null +++ b/releasenotes/notes/calibration-builder-no-echo-fdc1a0cac05c1905.yaml @@ -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. From 51a3caf639f4a4222000359e3e1a07007f30dc4c Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 27 Apr 2021 10:29:47 +0200 Subject: [PATCH 02/12] Fixed style and lint error in line 20 --- qiskit/transpiler/passes/scheduling/calibration_creators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 88184f6fcade..21ba92bf75ab 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -17,7 +17,8 @@ from abc import abstractmethod import numpy as np -from qiskit.pulse import Play, ShiftPhase, Schedule, ControlChannel, DriveChannel, GaussianSquare, Delay +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 aad138912de44fcfa6068b4661f6bf98f4fbcd9a Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 27 Apr 2021 15:03:58 +0200 Subject: [PATCH 03/12] Fixed style and lint errors --- .../passes/scheduling/calibration_creators.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 21ba92bf75ab..5173039875f7 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -261,14 +261,6 @@ class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder): of the CX gate. """ - def __init__(self, backend: basebackend): - """ - Initializes a RZXGate calibration builder without echos. - Args: - backend: Backend for which to construct the gates. - """ - super().__init__(backend) - def _filter_instruction(self, inst: (int, Union['Schedule', Instruction])) -> bool: """ Filters the Schedule instructions for a play instruction with a Gaussian square pulse. @@ -312,7 +304,7 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: control, target = None, None - for time, inst in cx_sched.instructions: + for _, inst in cx_sched.instructions: # Identify the compensation tones. if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase): if isinstance(inst.pulse, GaussianSquare): From 7ae864a66f874430b6cd19a86fc52c377f085742 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 28 Apr 2021 17:48:15 +0200 Subject: [PATCH 04/12] Corrected code by implementing new filter_funcs --- .../passes/scheduling/calibration_creators.py | 39 +++++++++++++------ 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 5173039875f7..f710961fd061 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -261,17 +261,37 @@ class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder): of the CX gate. """ - def _filter_instruction(self, inst: (int, Union['Schedule', Instruction])) -> bool: + @staticmethod + def _filter_control(inst: (int, Union['Schedule', Instruction])) -> bool: + """ + Filters the Schedule instructions for a Gaussian square pulse on the ControlChannel. + 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: """ - Filters the Schedule instructions for a play instruction with a Gaussian square pulse. + Filters the Schedule instructions for a Gaussian square pulse on the DriveChannel. Args: inst: Instructions to be filtered. Returns: - match: True if the instruction is a play instruction and contains - a Gaussian square pulse. + match: True if the instruction is a Play instruction with + a Gaussian square pulse on the DriveChannel. """ - if isinstance(inst[1], Play) and isinstance(inst[1].pulse, GaussianSquare): - return True + if isinstance(inst[1], Play): + if isinstance(inst[1].pulse, GaussianSquare) and \ + isinstance(inst[1].channel, DriveChannel): + return True return False @@ -317,11 +337,8 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: raise QiskitError('Target qubit is None.') # Get the filtered Schedule instructions for the CR gates and compensation tones. - crs = cx_sched.filter(*[self._filter_instruction], - channels=[ControlChannel(target), - ControlChannel(control)]).instructions - rotaries = cx_sched.filter(*[self._filter_instruction], - channels=[DriveChannel(target)]).instructions + 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) From 271093ea70c86aed2c54fc3c79d48951e59eb7c6 Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 29 Apr 2021 17:01:58 +0200 Subject: [PATCH 05/12] Implemented Daniel's suggestions --- .../passes/scheduling/calibration_creators.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index f710961fd061..2bc982702ece 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -264,7 +264,7 @@ class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder): @staticmethod def _filter_control(inst: (int, Union['Schedule', Instruction])) -> bool: """ - Filters the Schedule instructions for a Gaussian square pulse on the ControlChannel. + Looks for Gaussian square pulses applied to control channels. Args: inst: Instructions to be filtered. Returns: @@ -281,7 +281,7 @@ def _filter_control(inst: (int, Union['Schedule', Instruction])) -> bool: @staticmethod def _filter_drive(inst: (int, Union['Schedule', Instruction])) -> bool: """ - Filters the Schedule instructions for a Gaussian square pulse on the DriveChannel. + Looks for Gaussian square pulses applied to drive channels. Args: inst: Instructions to be filtered. Returns: @@ -305,9 +305,9 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: Returns: schedule: The calibration schedule for the RZXGate(theta). Raises: - QiskitError: if the control and target qubits cannot be identified, the backend - does not support cx between the qubits or the backend does not natively support the - specified direction of the cx. + 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] @@ -326,7 +326,7 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: for _, inst in cx_sched.instructions: # Identify the compensation tones. - if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase): + 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 From b0e1527b86eed33c8f97dbe380ccd47e70ac0b65 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 5 May 2021 15:49:42 +0200 Subject: [PATCH 06/12] Added tests for the RZXCalibrationBuilderNoEcho --- test/python/pulse/test_calibrationbuilder.py | 96 ++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 test/python/pulse/test_calibrationbuilder.py diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py new file mode 100644 index 000000000000..019fa3080fe6 --- /dev/null +++ b/test/python/pulse/test_calibrationbuilder.py @@ -0,0 +1,96 @@ +# 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.configuration = self.backend.configuration() + self.defaults = self.backend.defaults() + self.inst_map = self.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. + rzx_qc_instructions = cal_qc.calibrations['rzx'][((1, 0), (theta/2,))].instructions + self.assertEqual(rzx_qc_instructions[0][1].channel, DriveChannel(0)) + self.assertEqual(isinstance(rzx_qc_instructions[0][1], Play), True) + self.assertEqual(isinstance(rzx_qc_instructions[0][1].pulse, GaussianSquare), True) + self.assertEqual(rzx_qc_instructions[1][1].channel, DriveChannel(1)) + self.assertEqual(isinstance(rzx_qc_instructions[1][1], Delay), True) + self.assertEqual(rzx_qc_instructions[2][1].channel, ControlChannel(1)) + self.assertEqual(isinstance(rzx_qc_instructions[2][1], Play), True) + self.assertEqual(isinstance(rzx_qc_instructions[2][1].pulse, GaussianSquare), True) + + # 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.) * 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) + + From d37ee2aa208de0bdf735e007d04b9a7a5d738426 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 5 May 2021 17:06:41 +0200 Subject: [PATCH 07/12] Fixed style and lint error --- test/python/pulse/test_calibrationbuilder.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index 019fa3080fe6..4ddeb57a8848 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -90,7 +90,6 @@ def test_rzx_calibration_builder(self): 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. + # Check whether the durations of the RZX pulse and + # the scaled CR pulse from the CX gate match. self.assertEqual(rzx_qc_duration, duration) - - From 28ea533a89c0cf42e8a8499fe81ff3bf2d25b045 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 5 May 2021 18:33:18 +0200 Subject: [PATCH 08/12] Formatted the code --- test/python/pulse/test_calibrationbuilder.py | 29 +++++++++++++++----- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index 4ddeb57a8848..8bceacb47f27 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -19,8 +19,17 @@ 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.pulse import ( + Play, + Delay, + ShiftPhase, + ControlChannel, + DriveChannel, + GaussianSquare, +) +from qiskit.transpiler.passes.scheduling.calibration_creators import ( + RZXCalibrationBuilderNoEcho, +) from qiskit.test.mock import FakeAthens @@ -56,18 +65,24 @@ def test_rzx_calibration_builder(self): # Check that the calibrations contain the correct instructions # and pulses on the correct channels. - rzx_qc_instructions = cal_qc.calibrations['rzx'][((1, 0), (theta/2,))].instructions + rzx_qc_instructions = cal_qc.calibrations["rzx"][ + ((1, 0), (theta / 2,)) + ].instructions self.assertEqual(rzx_qc_instructions[0][1].channel, DriveChannel(0)) self.assertEqual(isinstance(rzx_qc_instructions[0][1], Play), True) - self.assertEqual(isinstance(rzx_qc_instructions[0][1].pulse, GaussianSquare), True) + self.assertEqual( + isinstance(rzx_qc_instructions[0][1].pulse, GaussianSquare), True + ) self.assertEqual(rzx_qc_instructions[1][1].channel, DriveChannel(1)) self.assertEqual(isinstance(rzx_qc_instructions[1][1], Delay), True) self.assertEqual(rzx_qc_instructions[2][1].channel, ControlChannel(1)) self.assertEqual(isinstance(rzx_qc_instructions[2][1], Play), True) - self.assertEqual(isinstance(rzx_qc_instructions[2][1].pulse, GaussianSquare), True) + self.assertEqual( + isinstance(rzx_qc_instructions[2][1].pulse, GaussianSquare), True + ) # Calculate the duration of one scaled Gaussian square pulse from the CX gate. - cx_sched = self.inst_map.get('cx', qubits=(1, 0)) + cx_sched = self.inst_map.get("cx", qubits=(1, 0)) crs = [] for time, inst in cx_sched.instructions: @@ -86,7 +101,7 @@ def test_rzx_calibration_builder(self): 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.) * area + 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 From e31cc5648cd0a7129843686f2335434659f54a9e Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 6 May 2021 10:33:21 +0200 Subject: [PATCH 09/12] Reformatted calibration_creators.py --- .../passes/scheduling/calibration_creators.py | 120 +++++++++++------- 1 file changed, 75 insertions(+), 45 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 2bc982702ece..c00c21bf47b4 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -17,8 +17,15 @@ from abc import abstractmethod import numpy as np -from qiskit.pulse import Play, Delay, 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 @@ -50,7 +57,7 @@ def run(self, dag): bit_indices = {bit: index for index, bit in enumerate(dag.qubits)} for node in dag.nodes(): - if node.type == 'op': + if node.type == "op": if self.supported(node.op): params = node.op.params qubits = [bit_indices[qarg] for qarg in node.qargs] @@ -87,8 +94,10 @@ def __init__(self, backend: basebackend): """ super().__init__() if not backend.configuration().open_pulse: - raise QiskitError('Calibrations can only be added to Pulse-enabled backends, ' - 'but {0} is not enabled with Pulse.'.format(backend.name())) + raise QiskitError( + "Calibrations can only be added to Pulse-enabled backends, " + "but {0} is not enabled with Pulse.".format(backend.name()) + ) self._inst_map = backend.defaults().instruction_schedule_map self._config = backend.configuration() @@ -131,22 +140,33 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> P 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.) * area + target_area = abs(theta) / (np.pi / 2.0) * area sign = theta / abs(theta) if target_area > gaussian_area: width = (target_area - gaussian_area) / abs(amp) - duration = math.ceil((width + n_sigmas * sigma) / sample_mult) * sample_mult - return Play(GaussianSquare(amp=sign*amp, width=width, sigma=sigma, - duration=duration), channel=instruction.channel) + duration = ( + math.ceil((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 = math.ceil(n_sigmas * sigma / sample_mult) * sample_mult return Play( - GaussianSquare(amp=amp * amp_scale, width=0, sigma=sigma, duration=duration), - channel=instruction.channel) + GaussianSquare( + amp=amp * amp_scale, width=0, sigma=sigma, duration=duration + ), + channel=instruction.channel, + ) else: - raise QiskitError('RZXCalibrationBuilder only stretches/compresses GaussianSquare.') + raise QiskitError( + "RZXCalibrationBuilder only stretches/compresses GaussianSquare." + ) def get_calibration(self, params: List, qubits: List) -> Schedule: """ @@ -165,12 +185,14 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: 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)) + 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) + cx_sched = self._inst_map.get("cx", qubits=(q1, q2)) + rzx_theta = Schedule(name="rzx(%.3f)" % theta) if theta == 0.0: return rzx_theta @@ -186,18 +208,20 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: crs.append((time, inst)) # Identify the compensation tones. - if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase): + 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.') + raise QiskitError("Control qubit is None.") if target is None: - raise QiskitError('Target qubit is None.') + raise QiskitError("Target qubit is None.") - echo_x = self._inst_map.get('x', qubits=control) + echo_x = self._inst_map.get("x", qubits=control) # Build the schedule @@ -211,8 +235,10 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: 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))) + 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) @@ -227,18 +253,18 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: if comp2 is not None: rzx_theta = rzx_theta.insert(time, comp2) - time = 2*comp1.duration + echo_x.duration + 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') + 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) @@ -262,7 +288,7 @@ class RZXCalibrationBuilderNoEcho(RZXCalibrationBuilder): """ @staticmethod - def _filter_control(inst: (int, Union['Schedule', Instruction])) -> bool: + def _filter_control(inst: (int, Union["Schedule", Instruction])) -> bool: """ Looks for Gaussian square pulses applied to control channels. Args: @@ -272,14 +298,15 @@ def _filter_control(inst: (int, Union['Schedule', Instruction])) -> bool: a Gaussian square pulse on the ControlChannel. """ if isinstance(inst[1], Play): - if isinstance(inst[1].pulse, GaussianSquare) and \ - isinstance(inst[1].channel, ControlChannel): + 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: + def _filter_drive(inst: (int, Union["Schedule", Instruction])) -> bool: """ Looks for Gaussian square pulses applied to drive channels. Args: @@ -289,8 +316,9 @@ def _filter_drive(inst: (int, Union['Schedule', Instruction])) -> bool: a Gaussian square pulse on the DriveChannel. """ if isinstance(inst[1], Play): - if isinstance(inst[1].pulse, GaussianSquare) and \ - isinstance(inst[1].channel, DriveChannel): + if isinstance(inst[1].pulse, GaussianSquare) and isinstance( + inst[1].channel, DriveChannel + ): return True return False @@ -312,12 +340,14 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: 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)) + 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) + cx_sched = self._inst_map.get("cx", qubits=(q1, q2)) + rzx_theta = Schedule(name="rzx(%.3f)" % theta) if theta == 0.0: return rzx_theta @@ -332,17 +362,17 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: control = q1 if target == q2 else q2 if control is None: - raise QiskitError('Control qubit is None.') + raise QiskitError("Control qubit is None.") if target is None: - raise QiskitError('Target qubit is None.') + raise QiskitError("Target qubit is None.") # 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) + 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) @@ -353,4 +383,4 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: if control == qubits[0]: return rzx_theta else: - raise QiskitError('Reverse direction not supported.') + raise QiskitError("Reverse direction not supported.") From 226ca8461b212fecfd6796fefb38f37b1181a412 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Thu, 6 May 2021 21:29:57 +0200 Subject: [PATCH 10/12] Apply suggestions from code review Implemented Daniel's suggestions Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/scheduling/calibration_creators.py | 9 ++++----- test/python/pulse/test_calibrationbuilder.py | 18 ++++++------------ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index c00c21bf47b4..b30352f8c6fc 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -365,6 +365,9 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: 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 @@ -379,8 +382,4 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: rzx_theta = rzx_theta.insert(0, rot) rzx_theta = rzx_theta.insert(0, Delay(cr.duration, DriveChannel(control))) - # Reverse direction of the ZX. - if control == qubits[0]: - return rzx_theta - else: - raise QiskitError("Reverse direction not supported.") + return rzx_theta diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index 8bceacb47f27..cb2841ad0c79 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -39,9 +39,7 @@ class TestCalibrationBuilder(QiskitTestCase): def setUp(self): super().setUp() self.backend = FakeAthens() - self.configuration = self.backend.configuration() - self.defaults = self.backend.defaults() - self.inst_map = self.defaults.instruction_schedule_map + self.inst_map = self. backend.defaults().instruction_schedule_map class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder): @@ -69,17 +67,13 @@ def test_rzx_calibration_builder(self): ((1, 0), (theta / 2,)) ].instructions self.assertEqual(rzx_qc_instructions[0][1].channel, DriveChannel(0)) - self.assertEqual(isinstance(rzx_qc_instructions[0][1], Play), True) - self.assertEqual( - isinstance(rzx_qc_instructions[0][1].pulse, GaussianSquare), True - ) + 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.assertEqual(isinstance(rzx_qc_instructions[1][1], Delay), True) + self.assertTrue(isinstance(rzx_qc_instructions[1][1], Delay)) self.assertEqual(rzx_qc_instructions[2][1].channel, ControlChannel(1)) - self.assertEqual(isinstance(rzx_qc_instructions[2][1], Play), True) - self.assertEqual( - isinstance(rzx_qc_instructions[2][1].pulse, GaussianSquare), True - ) + 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)) From 63ed1e9b875007f4c903fa300978901584bd81fc Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 11 May 2021 20:58:20 +0200 Subject: [PATCH 11/12] Reformatted the code --- .../passes/scheduling/calibration_creators.py | 26 +++++++------------ test/python/pulse/test_calibrationbuilder.py | 6 ++--- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index b30352f8c6fc..255e742123d4 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -145,28 +145,20 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> P if target_area > gaussian_area: width = (target_area - gaussian_area) / abs(amp) - duration = ( - math.ceil((width + n_sigmas * sigma) / sample_mult) * sample_mult - ) + duration = math.ceil((width + n_sigmas * sigma) / sample_mult) * sample_mult return Play( - GaussianSquare( - amp=sign * amp, width=width, sigma=sigma, duration=duration - ), + GaussianSquare(amp=sign * amp, width=width, sigma=sigma, duration=duration), channel=instruction.channel, ) else: amp_scale = sign * target_area / gaussian_area duration = math.ceil(n_sigmas * sigma / sample_mult) * sample_mult return Play( - GaussianSquare( - amp=amp * amp_scale, width=0, sigma=sigma, duration=duration - ), + GaussianSquare(amp=amp * amp_scale, width=0, sigma=sigma, duration=duration), channel=instruction.channel, ) else: - raise QiskitError( - "RZXCalibrationBuilder only stretches/compresses GaussianSquare." - ) + raise QiskitError("RZXCalibrationBuilder only stretches/compresses GaussianSquare.") def get_calibration(self, params: List, qubits: List) -> Schedule: """ @@ -208,9 +200,7 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: crs.append((time, inst)) # Identify the compensation tones. - if isinstance(inst.channel, DriveChannel) and not isinstance( - inst, ShiftPhase - ): + if isinstance(inst.channel, DriveChannel) and not isinstance(inst, ShiftPhase): if isinstance(inst.pulse, GaussianSquare): comp_tones.append((time, inst)) target = inst.channel.index @@ -365,9 +355,11 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: 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.") + 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 diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index cb2841ad0c79..040bb7d9c831 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -39,7 +39,7 @@ class TestCalibrationBuilder(QiskitTestCase): def setUp(self): super().setUp() self.backend = FakeAthens() - self.inst_map = self. backend.defaults().instruction_schedule_map + self.inst_map = self.backend.defaults().instruction_schedule_map class TestRZXCalibrationBuilderNoEcho(TestCalibrationBuilder): @@ -63,9 +63,7 @@ def test_rzx_calibration_builder(self): # Check that the calibrations contain the correct instructions # and pulses on the correct channels. - rzx_qc_instructions = cal_qc.calibrations["rzx"][ - ((1, 0), (theta / 2,)) - ].instructions + 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)) From a2ed8879a7e7c41c09fce57e4105a17764ee6689 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 19 May 2021 12:25:36 +0200 Subject: [PATCH 12/12] Fixed Pylint no-member error --- test/python/pulse/test_calibrationbuilder.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index 040bb7d9c831..0579d8c1d10a 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -63,6 +63,7 @@ def test_rzx_calibration_builder(self): # 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))