From 8df6123d1962c7b05978d9278f375eb345cc568b Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 26 Apr 2021 16:06:40 +0200 Subject: [PATCH 01/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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/70] 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)) From 1b5cf50ab1f9724d50c0b71f25e83df3399aef71 Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 16 Jun 2021 14:57:03 +0200 Subject: [PATCH 13/70] Added NativeCXDirection Transpiler Pass, added RZX decompositions of the RZZ and the SWAP Gate to the StandardEquivalenceLibrary --- .../standard_gates/equivalence_library.py | 90 +++++++++++++++++++ .../passes/scheduling/calibration_creators.py | 12 ++- 2 files changed, 99 insertions(+), 3 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index 215b69514171..49b21a21c45b 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -318,6 +318,26 @@ def_rzz.append(inst, qargs, cargs) _sel.add_equivalence(RZZGate(theta), def_rzz) +q = QuantumRegister(2, "q") +theta = Parameter("theta") +rzz_to_rzx = QuantumCircuit(q) +for inst, qargs, cargs in [ + (RZGate(theta), [q[1]], []), + (RZGate(pi/2), [q[1]], []), + (RXGate(pi/2), [q[1]], []), + (RZGate(pi/2), [q[1]], []), + (RXGate(-1*theta), [q[1]], []), + (RZXGate(theta / 2), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZXGate(-theta / 2), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZGate(pi/2), [q[1]], []), + (RXGate(pi/2), [q[1]], []), + (RZGate(pi/2), [q[1]], []), + ]: + rzz_to_rzx.append(inst, qargs, cargs) +_sel.add_equivalence(RZZGate(theta), rzz_to_rzx) + # RZXGate q = QuantumRegister(2, "q") @@ -371,6 +391,76 @@ def_swap.append(inst, qargs, cargs) _sel.add_equivalence(SwapGate(), def_swap) +# q = QuantumRegister(2, "q") +# swap_to_rzx = QuantumCircuit(q) +# for inst, qargs, cargs in [ +# (HGate(), [q[0]], []), +# (RZXGate(pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (RZXGate(-pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (HGate(), [q[0]], []), +# (SdgGate(), [q[1]], []), +# (SdgGate(), [q[0]], []), +# (HGate(), [q[0]], []), +# (RZXGate(pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (RZXGate(-pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (HGate(), [q[0]], []), +# (SGate(), [q[0]], []), +# (SGate(), [q[1]], []), +# (HGate(), [q[1]], []), +# (RZXGate(pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (RZXGate(-pi / 4), [q[0], q[1]], []), +# (XGate(), [q[0]], []), +# (HGate(), [q[1]], []), +# ]: +# swap_to_rzx.append(inst, qargs, cargs) +# _sel.add_equivalence(SwapGate(), swap_to_rzx) + +q = QuantumRegister(2, "q") +swap_to_rzx = QuantumCircuit(q) +for inst, qargs, cargs in [ + (RZGate(pi / 2), [q[0]], []), + (SXGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (RZXGate(pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZXGate(-pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (SXGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (SdgGate(), [q[1]], []), + (SdgGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (SXGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (RZXGate(pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZXGate(-pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (SXGate(), [q[0]], []), + (RZGate(pi / 2), [q[0]], []), + (SGate(), [q[0]], []), + (SGate(), [q[1]], []), + (RZGate(pi / 2), [q[1]], []), + (SXGate(), [q[1]], []), + (RZGate(pi / 2), [q[1]], []), + (RZXGate(pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZXGate(-pi / 4), [q[0], q[1]], []), + (XGate(), [q[0]], []), + (RZGate(pi / 2), [q[1]], []), + (SXGate(), [q[1]], []), + (RZGate(pi / 2), [q[1]], []), +]: + swap_to_rzx.append(inst, qargs, cargs) +_sel.add_equivalence(SwapGate(), swap_to_rzx) + # iSwapGate q = QuantumRegister(2, "q") diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 255e742123d4..87e9159cf17e 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -59,13 +59,15 @@ def run(self, dag): for node in dag.nodes(): if node.type == "op": if self.supported(node.op): - params = node.op.params + # params = node.op.params + # print(params) + params = [round(float(param), 8) for param in node.op.params] + # print(params) qubits = [bit_indices[qarg] for qarg in node.qargs] schedule = self.get_calibration(params, qubits) - dag.add_calibration(node.op, qubits, schedule, params=params) - + dag.add_calibration(node.op.name, qubits, schedule, params=params) return dag @@ -139,6 +141,9 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> P # 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 + # print(theta) + # print(type(theta)) + # theta = float(theta) target_area = abs(theta) / (np.pi / 2.0) * area sign = theta / abs(theta) @@ -328,6 +333,7 @@ def get_calibration(self, params: List, qubits: List) -> Schedule: support the specified direction of the cx. """ theta = params[0] + # print('Theta = ', theta) q1, q2 = qubits[0], qubits[1] if not self._inst_map.has("cx", qubits): From 448f94f1ff618a23b90f49b5a42e6fbbf9b1b5aa Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 16 Jun 2021 15:38:08 +0200 Subject: [PATCH 14/70] Added NativeCXDirection Transpiler Pass. --- .../passes/utils/native_cx_direction.py | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 qiskit/transpiler/passes/utils/native_cx_direction.py diff --git a/qiskit/transpiler/passes/utils/native_cx_direction.py b/qiskit/transpiler/passes/utils/native_cx_direction.py new file mode 100644 index 000000000000..9bd07b1001d6 --- /dev/null +++ b/qiskit/transpiler/passes/utils/native_cx_direction.py @@ -0,0 +1,136 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# 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. + +"""Rearrange the direction of the cx nodes to match the hardware-native backend cx direction.""" + +from math import pi +from typing import Tuple + +from qiskit.transpiler.layout import Layout +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError + +from qiskit.circuit import QuantumRegister +from qiskit.dagcircuit import DAGCircuit +from qiskit.circuit.library.standard_gates import RYGate, HGate, CXGate, ECRGate, RZXGate + +class NativeCXGateDirection(TransformationPass): + """Modify asymmetric gates to match the hardware coupling direction. + This pass makes use of the following identities:: + ┌───┐┌───┐┌───┐ + q_0: ──■── q_0: ┤ H ├┤ X ├┤ H ├ + ┌─┴─┐ = ├───┤└─┬─┘├───┤ + q_1: ┤ X ├ q_1: ┤ H ├──■──┤ H ├ + └───┘ └───┘ └───┘ + ┌──────┐ ┌───────────┐┌──────┐┌───┐ + q_0: ┤0 ├ q_0: ┤ RY(-pi/2) ├┤1 ├┤ H ├ + │ ECR │ = └┬──────────┤│ ECR │├───┤ + q_1: ┤1 ├ q_1: ─┤ RY(pi/2) ├┤0 ├┤ H ├ + └──────┘ └──────────┘└──────┘└───┘ + ┌─────────┐ ┌───┐┌─────────┐┌───┐ + q_0: ┤0 ├ q_0: ┤ H ├┤1 ├┤ H ├ + │ Rzx(θ) │ = ├───┤│ Rzx(θ) │├───┤ + q_1: ┤1 ├ q_1: ┤ H ├┤0 ├┤ H ├ + └─────────┘ └───┘└─────────┘└───┘ + """ + + def __init__(self, backend): + """GateDirection pass. + Args: + backend (BaseBackend): The hardware backend that is used. + """ + super().__init__() + self.backend = backend + + # Create the replacement dag and associated register. + self._cx_dag = DAGCircuit() + qr = QuantumRegister(2) + self._cx_dag.add_qreg(qr) + self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) + self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) + self._cx_dag.apply_operation_back(CXGate(), [qr[1], qr[0]], []) + self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) + self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) + + self._ecr_dag = DAGCircuit() + qr = QuantumRegister(2) + self._ecr_dag.add_qreg(qr) + self._ecr_dag.apply_operation_back(RYGate(-pi / 2), [qr[0]], []) + self._ecr_dag.apply_operation_back(RYGate(pi / 2), [qr[1]], []) + self._ecr_dag.apply_operation_back(ECRGate(), [qr[1], qr[0]], []) + self._ecr_dag.apply_operation_back(HGate(), [qr[0]], []) + self._ecr_dag.apply_operation_back(HGate(), [qr[1]], []) + + def _rzx_dag(self, theta): + + rzx_dag = DAGCircuit() + qr = QuantumRegister(2) + rzx_dag.add_qreg(qr) + rzx_dag.apply_operation_back(HGate(), [qr[0]], []) + rzx_dag.apply_operation_back(HGate(), [qr[1]], []) + rzx_dag.apply_operation_back(RZXGate(theta), [qr[1], qr[0]], []) + rzx_dag.apply_operation_back(HGate(), [qr[0]], []) + rzx_dag.apply_operation_back(HGate(), [qr[1]], []) + return rzx_dag + + def is_native_cx(self, qubit_pair: Tuple): + """Check that a CX for a qubit pair is native.""" + inst_map = self.backend.defaults().instruction_schedule_map + cx1 = inst_map.get('cx', qubit_pair) + cx2 = inst_map.get('cx', qubit_pair[::-1]) + return cx1.duration < cx2.duration + + def run(self, dag): + """Run the GateDirection pass on `dag`. + Flips the cx nodes to match the hardware-native coupling map. Modifies the + input dag. + Args: + dag (DAGCircuit): DAG to map. + Returns: + DAGCircuit: The rearranged dag for the hardware-native coupling map. + Raises: + TranspilerError: If the circuit cannot be mapped just by flipping the + cx nodes. + """ + + if len(dag.qregs) > 1: + raise TranspilerError('GateDirection expects a single qreg input DAG,' + 'but input DAG had qregs: {}.'.format( + dag.qregs)) + + trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + + for node in dag.two_qubit_ops(): + control = node.qargs[0] + target = node.qargs[1] + + physical_q0 = trivial_layout[control] + physical_q1 = trivial_layout[target] + + config = self.backend.configuration() + if [physical_q0, physical_q1] not in config.coupling_map: + raise TranspilerError('Qubits %s and %s are not connected on the backend' + % (physical_q0, physical_q1)) + + if not self.is_native_cx((physical_q0, physical_q1)): + if node.name == 'cx': + dag.substitute_node_with_dag(node, self._cx_dag) + elif node.name == 'ecr': + dag.substitute_node_with_dag(node, self._ecr_dag) + elif node.name == 'rzx': + theta = node.op.params[0] + dag.substitute_node_with_dag(node, self._rzx_dag(theta)) + else: + raise TranspilerError('Flipping of gate direction is only supported ' + 'for CX and ECR at this time.') + + return dag \ No newline at end of file From b589c86b6cae6417824d3801c02ca2d8b6ff130a Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 20 Jun 2021 13:07:42 +0200 Subject: [PATCH 15/70] Added EchoRZXWeylDecomposition Transpiler Pass. --- .../synthesis/two_qubit_decompose.py | 79 +++++++++++++++++++ .../echo_rzx_weyl_decomposition.py | 57 +++++++++++++ .../passes/scheduling/calibration_creators.py | 9 +-- 3 files changed, 137 insertions(+), 8 deletions(-) create mode 100644 qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 7ebe0b3c8de7..a8860108a20f 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -544,6 +544,85 @@ def specialize(self): self.K2r = np.asarray(RYGate(k2rtheta)) @ np.asarray(RXGate(k2rlambda)) +class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): + + def specialize(self): + pass + + def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): + """Appends Ud(a, b, c) to the circuit. + + Can be overriden in subclasses for special cases""" + # if not simplify or abs(self.a) > atol: + # circ.rz(np.pi / 2, 0) + # circ.sx(0) + # circ.rz(np.pi / 2, 0) + # circ.rzx(self.a / 2, 0, 1) + # circ.x(0) + # circ.rzx(-self.a / 2, 0, 1) + # if not simplify or abs(self.b) > atol: + # circ.sx(0) + # circ.rz(3*np.pi / 2, 1) + # circ.rzx(self.b / 2, 0, 1) + # circ.x(0) + # circ.rzx(-self.b / 2, 0, 1) + # if not simplify or abs(self.c) > atol: + # circ.rz(np.pi / 2, 0) + # circ.sx(0) + # circ.rz(np.pi, 1) + # circ.sx(1) + # circ.rz(np.pi / 2, 1) + # circ.rzx(self.c / 2, 0, 1) + # circ.x(0) + # circ.rzx(-self.c / 2, 0, 1) + # circ.x(0) + # circ.rz(np.pi / 2, 1) + # circ.sx(0) + # circ.rz(np.pi / 2, 1) + # if not simplify or abs(self.a) > atol: + # circ.h(0) + # circ.rzx(-self.a * 2, 0, 1) + # if not simplify or abs(self.b) > atol: + # circ.h(0) + # circ.sdg(0) + # circ.h(0) + # circ.sdg(1) + # circ.rzx(-self.b * 2, 0, 1) + # if not simplify or abs(self.c) > atol: + # circ.h(0) + # circ.s(0) + # circ.s(1) + # circ.h(1) + # circ.rzx(-self.c * 2, 0, 1) + # circ.h(1) + if not simplify or abs(self.a) > atol: + circ.h(0) + circ.rzx(-self.a, 0, 1) + circ.x(0) + circ.rzx(self.a, 0, 1) + circ.x(0) + if not simplify or abs(self.b) > atol: + circ.h(0) + circ.sdg(0) + circ.h(0) + circ.sdg(1) + circ.rzx(-self.b, 0, 1) + circ.x(0) + circ.rzx(self.b, 0, 1) + circ.x(0) + if not simplify or abs(self.c) > atol: + circ.h(0) + circ.s(0) + circ.s(1) + circ.h(1) + circ.rzx(-self.c, 0, 1) + circ.x(0) + circ.rzx(self.c, 0, 1) + circ.x(0) + circ.h(1) + + + class TwoQubitWeylMirrorControlledEquiv(TwoQubitWeylDecomposition): """U ~ Ud(𝜋/4, 𝜋/4, α) ~ SWAP . Ctrl-U diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py new file mode 100644 index 000000000000..0d4c38522d7f --- /dev/null +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -0,0 +1,57 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2021. +# +# 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. + +"""Weyl decomposition of an arbitrary two-qubit circiut in terms of echoed cross-resonance gates.""" + +from qiskit.transpiler.basepasses import TransformationPass +from qiskit.transpiler.exceptions import TranspilerError + +from qiskit.dagcircuit import DAGCircuit +from qiskit.converters import circuit_to_dag + +import qiskit.quantum_info as qi + +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylEchoRZX + + +class EchoRZXWeylDecomposition(TransformationPass): + """Modify asymmetric gates to match the hardware coupling direction. + """ + + def __init__(self): + """EchoRZXWeylDecomposition pass. + """ + super().__init__() + + def run(self, dag): + """Run the EchoRZXWeylDecomposition pass on `dag`. + Rewrites a two-qubit circuit in terms of echoed cross-resonance gates by computing + the Cartan decomposition of the corresponding unitary. Modifies the input dag. + Args: + dag (DAGCircuit): DAG to map. + Returns: + DAGCircuit: The rearranged dag. + Raises: + TranspilerError: If the circuit cannot be mapped. + """ + + if len(dag.qregs) > 1: + raise TranspilerError('EchoRZXWeylDecomposition expects a single qreg input DAG,' + 'but input DAG had qregs: {}.'.format(dag.qregs)) + + for idx, node in enumerate(dag.two_qubit_ops()): + if node.type == "op": + unitary = qi.Operator(node.op).data + dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit()) + dag.substitute_node_with_dag(node, dag_weyl) + + return dag diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 87e9159cf17e..0cf248e8ae1a 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -59,10 +59,7 @@ def run(self, dag): for node in dag.nodes(): if node.type == "op": if self.supported(node.op): - # params = node.op.params - # print(params) - params = [round(float(param), 8) for param in node.op.params] - # print(params) + params = node.op.params qubits = [bit_indices[qarg] for qarg in node.qargs] schedule = self.get_calibration(params, qubits) @@ -141,10 +138,6 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> P # 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 - # print(theta) - # print(type(theta)) - # theta = float(theta) - target_area = abs(theta) / (np.pi / 2.0) * area sign = theta / abs(theta) From d704b5a959315710ecbc748ef6ed10ffdb65ac80 Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 21 Jun 2021 11:54:43 +0200 Subject: [PATCH 16/70] Small change --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 0d4c38522d7f..cfc7ae51a95d 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -51,7 +51,7 @@ def run(self, dag): for idx, node in enumerate(dag.two_qubit_ops()): if node.type == "op": unitary = qi.Operator(node.op).data - dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit()) + dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit(simplify=True)) dag.substitute_node_with_dag(node, dag_weyl) return dag From 60f5b413031aa9ac5faa5b697fdcb1aea74594b7 Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 21 Jun 2021 13:07:52 +0200 Subject: [PATCH 17/70] Removed changes to Equivalence Library --- .../standard_gates/equivalence_library.py | 91 ------------------- 1 file changed, 91 deletions(-) diff --git a/qiskit/circuit/library/standard_gates/equivalence_library.py b/qiskit/circuit/library/standard_gates/equivalence_library.py index 49b21a21c45b..96c651d3cd45 100644 --- a/qiskit/circuit/library/standard_gates/equivalence_library.py +++ b/qiskit/circuit/library/standard_gates/equivalence_library.py @@ -13,7 +13,6 @@ """Standard gates.""" -# pylint: disable=invalid-name import warnings from qiskit.qasm import pi from qiskit.circuit import EquivalenceLibrary, Parameter, QuantumCircuit, QuantumRegister @@ -318,26 +317,6 @@ def_rzz.append(inst, qargs, cargs) _sel.add_equivalence(RZZGate(theta), def_rzz) -q = QuantumRegister(2, "q") -theta = Parameter("theta") -rzz_to_rzx = QuantumCircuit(q) -for inst, qargs, cargs in [ - (RZGate(theta), [q[1]], []), - (RZGate(pi/2), [q[1]], []), - (RXGate(pi/2), [q[1]], []), - (RZGate(pi/2), [q[1]], []), - (RXGate(-1*theta), [q[1]], []), - (RZXGate(theta / 2), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZXGate(-theta / 2), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZGate(pi/2), [q[1]], []), - (RXGate(pi/2), [q[1]], []), - (RZGate(pi/2), [q[1]], []), - ]: - rzz_to_rzx.append(inst, qargs, cargs) -_sel.add_equivalence(RZZGate(theta), rzz_to_rzx) - # RZXGate q = QuantumRegister(2, "q") @@ -391,76 +370,6 @@ def_swap.append(inst, qargs, cargs) _sel.add_equivalence(SwapGate(), def_swap) -# q = QuantumRegister(2, "q") -# swap_to_rzx = QuantumCircuit(q) -# for inst, qargs, cargs in [ -# (HGate(), [q[0]], []), -# (RZXGate(pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (RZXGate(-pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (HGate(), [q[0]], []), -# (SdgGate(), [q[1]], []), -# (SdgGate(), [q[0]], []), -# (HGate(), [q[0]], []), -# (RZXGate(pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (RZXGate(-pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (HGate(), [q[0]], []), -# (SGate(), [q[0]], []), -# (SGate(), [q[1]], []), -# (HGate(), [q[1]], []), -# (RZXGate(pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (RZXGate(-pi / 4), [q[0], q[1]], []), -# (XGate(), [q[0]], []), -# (HGate(), [q[1]], []), -# ]: -# swap_to_rzx.append(inst, qargs, cargs) -# _sel.add_equivalence(SwapGate(), swap_to_rzx) - -q = QuantumRegister(2, "q") -swap_to_rzx = QuantumCircuit(q) -for inst, qargs, cargs in [ - (RZGate(pi / 2), [q[0]], []), - (SXGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (RZXGate(pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZXGate(-pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (SXGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (SdgGate(), [q[1]], []), - (SdgGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (SXGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (RZXGate(pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZXGate(-pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (SXGate(), [q[0]], []), - (RZGate(pi / 2), [q[0]], []), - (SGate(), [q[0]], []), - (SGate(), [q[1]], []), - (RZGate(pi / 2), [q[1]], []), - (SXGate(), [q[1]], []), - (RZGate(pi / 2), [q[1]], []), - (RZXGate(pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZXGate(-pi / 4), [q[0], q[1]], []), - (XGate(), [q[0]], []), - (RZGate(pi / 2), [q[1]], []), - (SXGate(), [q[1]], []), - (RZGate(pi / 2), [q[1]], []), -]: - swap_to_rzx.append(inst, qargs, cargs) -_sel.add_equivalence(SwapGate(), swap_to_rzx) - # iSwapGate q = QuantumRegister(2, "q") From c17c534cec79f597eca3b48f35d55c3a9eb9a10f Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 22 Jun 2021 11:53:08 +0200 Subject: [PATCH 18/70] Minor changes --- qiskit/transpiler/passes/scheduling/calibration_creators.py | 2 ++ .../utils/{native_cx_direction.py => native_cr_direction.py} | 0 2 files changed, 2 insertions(+) rename qiskit/transpiler/passes/utils/{native_cx_direction.py => native_cr_direction.py} (100%) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 0cf248e8ae1a..3e534aa0ea5e 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -65,6 +65,7 @@ def run(self, dag): schedule = self.get_calibration(params, qubits) dag.add_calibration(node.op.name, qubits, schedule, params=params) + return dag @@ -138,6 +139,7 @@ def rescale_cr_inst(instruction: Play, theta: float, sample_mult: int = 16) -> P # 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 = theta / abs(theta) diff --git a/qiskit/transpiler/passes/utils/native_cx_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py similarity index 100% rename from qiskit/transpiler/passes/utils/native_cx_direction.py rename to qiskit/transpiler/passes/utils/native_cr_direction.py From b4d81d7a3957368ffea303ef2c7a2438f87bc1ab Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 22 Jun 2021 11:58:17 +0200 Subject: [PATCH 19/70] Removed bug due to simplified=True and modified _weyl_gate in class TwoQubitWeylEchoRZX --- .../synthesis/two_qubit_decompose.py | 77 ++++++------------- .../echo_rzx_weyl_decomposition.py | 12 +-- 2 files changed, 29 insertions(+), 60 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index a8860108a20f..d3b833b6a689 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -545,6 +545,8 @@ def specialize(self): class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): + """Decompose two-qubit unitary in terms of echoed cross-resonance gates. + """ def specialize(self): pass @@ -553,73 +555,38 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): """Appends Ud(a, b, c) to the circuit. Can be overriden in subclasses for special cases""" - # if not simplify or abs(self.a) > atol: - # circ.rz(np.pi / 2, 0) - # circ.sx(0) - # circ.rz(np.pi / 2, 0) - # circ.rzx(self.a / 2, 0, 1) - # circ.x(0) - # circ.rzx(-self.a / 2, 0, 1) - # if not simplify or abs(self.b) > atol: - # circ.sx(0) - # circ.rz(3*np.pi / 2, 1) - # circ.rzx(self.b / 2, 0, 1) - # circ.x(0) - # circ.rzx(-self.b / 2, 0, 1) - # if not simplify or abs(self.c) > atol: - # circ.rz(np.pi / 2, 0) - # circ.sx(0) - # circ.rz(np.pi, 1) - # circ.sx(1) - # circ.rz(np.pi / 2, 1) - # circ.rzx(self.c / 2, 0, 1) - # circ.x(0) - # circ.rzx(-self.c / 2, 0, 1) - # circ.x(0) - # circ.rz(np.pi / 2, 1) - # circ.sx(0) - # circ.rz(np.pi / 2, 1) - # if not simplify or abs(self.a) > atol: - # circ.h(0) - # circ.rzx(-self.a * 2, 0, 1) - # if not simplify or abs(self.b) > atol: - # circ.h(0) - # circ.sdg(0) - # circ.h(0) - # circ.sdg(1) - # circ.rzx(-self.b * 2, 0, 1) - # if not simplify or abs(self.c) > atol: - # circ.h(0) - # circ.s(0) - # circ.s(1) - # circ.h(1) - # circ.rzx(-self.c * 2, 0, 1) - # circ.h(1) - if not simplify or abs(self.a) > atol: - circ.h(0) + del simplify + circ.h(0) + if abs(self.a) > atol: circ.rzx(-self.a, 0, 1) circ.x(0) circ.rzx(self.a, 0, 1) circ.x(0) - if not simplify or abs(self.b) > atol: - circ.h(0) - circ.sdg(0) - circ.h(0) - circ.sdg(1) + else: + pass + circ.h(0) + circ.sdg(0) + circ.h(0) + circ.sdg(1) + if abs(self.b) > atol: circ.rzx(-self.b, 0, 1) circ.x(0) circ.rzx(self.b, 0, 1) circ.x(0) - if not simplify or abs(self.c) > atol: - circ.h(0) - circ.s(0) - circ.s(1) - circ.h(1) + else: + pass + circ.h(0) + circ.s(0) + circ.s(1) + circ.h(1) + if abs(self.c) > atol: circ.rzx(-self.c, 0, 1) circ.x(0) circ.rzx(self.c, 0, 1) circ.x(0) - circ.h(1) + else: + pass + circ.h(1) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index cfc7ae51a95d..ca78c613d5f3 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -10,7 +10,7 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""Weyl decomposition of an arbitrary two-qubit circiut in terms of echoed cross-resonance gates.""" +"""Weyl decomposition of two-qubit gates in terms of echoed cross-resonance gates.""" from qiskit.transpiler.basepasses import TransformationPass from qiskit.transpiler.exceptions import TranspilerError @@ -24,7 +24,8 @@ class EchoRZXWeylDecomposition(TransformationPass): - """Modify asymmetric gates to match the hardware coupling direction. + """Rewrite two-qubit gates in terms of echoed cross-resonance gates according + to the Weyl decomposition. """ def __init__(self): @@ -34,8 +35,9 @@ def __init__(self): def run(self, dag): """Run the EchoRZXWeylDecomposition pass on `dag`. - Rewrites a two-qubit circuit in terms of echoed cross-resonance gates by computing - the Cartan decomposition of the corresponding unitary. Modifies the input dag. + Rewrites two-qubit gates in an arbitrary circuit in terms of echoed cross-resonance + gates by computing the Cartan decomposition of the corresponding unitary. Modifies the + input dag. Args: dag (DAGCircuit): DAG to map. Returns: @@ -51,7 +53,7 @@ def run(self, dag): for idx, node in enumerate(dag.two_qubit_ops()): if node.type == "op": unitary = qi.Operator(node.op).data - dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit(simplify=True)) + dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit()) dag.substitute_node_with_dag(node, dag_weyl) return dag From ec5df30fec8e9fb5a1b21b83aa12cf4e4b45ed25 Mon Sep 17 00:00:00 2001 From: catornow Date: Tue, 22 Jun 2021 12:01:32 +0200 Subject: [PATCH 20/70] Changed name of NativeCXGateDirection to NativeCRGateDirection --- qiskit/transpiler/passes/utils/native_cr_direction.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/utils/native_cr_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py index 9bd07b1001d6..10403449dc46 100644 --- a/qiskit/transpiler/passes/utils/native_cr_direction.py +++ b/qiskit/transpiler/passes/utils/native_cr_direction.py @@ -23,7 +23,8 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.circuit.library.standard_gates import RYGate, HGate, CXGate, ECRGate, RZXGate -class NativeCXGateDirection(TransformationPass): + +class NativeCRGateDirection(TransformationPass): """Modify asymmetric gates to match the hardware coupling direction. This pass makes use of the following identities:: ┌───┐┌───┐┌───┐ From aa970c6ed81f9095220d050e4093e458f9977de5 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:27:47 +0200 Subject: [PATCH 21/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index d3b833b6a689..15fbf962a5a2 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -562,8 +562,6 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): circ.x(0) circ.rzx(self.a, 0, 1) circ.x(0) - else: - pass circ.h(0) circ.sdg(0) circ.h(0) From 2d27040ca8e0b72927fc84eabb2db44161657e6d Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:28:02 +0200 Subject: [PATCH 22/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 15fbf962a5a2..84e5c5346f65 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -571,8 +571,6 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): circ.x(0) circ.rzx(self.b, 0, 1) circ.x(0) - else: - pass circ.h(0) circ.s(0) circ.s(1) From 992a158e030f661039c6d63c1e89a671ec183687 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:28:17 +0200 Subject: [PATCH 23/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 84e5c5346f65..3dcb7d1fbb0c 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -580,8 +580,6 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): circ.x(0) circ.rzx(self.c, 0, 1) circ.x(0) - else: - pass circ.h(1) From d64a7f878faa3b68000f950cca904df2527eed03 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:44:48 +0200 Subject: [PATCH 24/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index ca78c613d5f3..da4c54a8d217 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -24,8 +24,11 @@ class EchoRZXWeylDecomposition(TransformationPass): - """Rewrite two-qubit gates in terms of echoed cross-resonance gates according - to the Weyl decomposition. + """Rewrite two-qubit gates using the Weyl decomposition. + + This transpiler pass rewrites two-qubit gates in terms of echoed cross-resonance gates according + to the Weyl decomposition. A two-qubit gate will be replaced with at most six non-echoed RZXGates. + Each pair of RZXGates forms an echoed RZXGate. """ def __init__(self): From 9dd97c2f3c9d99fa05382673d49cdc8a50a7971a Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:45:08 +0200 Subject: [PATCH 25/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index da4c54a8d217..cc7cdf7cf467 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -38,13 +38,17 @@ def __init__(self): def run(self, dag): """Run the EchoRZXWeylDecomposition pass on `dag`. + Rewrites two-qubit gates in an arbitrary circuit in terms of echoed cross-resonance gates by computing the Cartan decomposition of the corresponding unitary. Modifies the input dag. + Args: dag (DAGCircuit): DAG to map. + Returns: DAGCircuit: The rearranged dag. + Raises: TranspilerError: If the circuit cannot be mapped. """ From 2b0c6980e948b138c137b737b5096fa076fe6f8e Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:45:23 +0200 Subject: [PATCH 26/70] Update qiskit/transpiler/passes/utils/native_cr_direction.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/transpiler/passes/utils/native_cr_direction.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/transpiler/passes/utils/native_cr_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py index 10403449dc46..6428484894ea 100644 --- a/qiskit/transpiler/passes/utils/native_cr_direction.py +++ b/qiskit/transpiler/passes/utils/native_cr_direction.py @@ -92,8 +92,8 @@ def is_native_cx(self, qubit_pair: Tuple): def run(self, dag): """Run the GateDirection pass on `dag`. - Flips the cx nodes to match the hardware-native coupling map. Modifies the - input dag. + Flips the cx, ecr, and rzx nodes to match the hardware-native coupling map. + Modifies the input dag. Args: dag (DAGCircuit): DAG to map. Returns: @@ -134,4 +134,4 @@ def run(self, dag): raise TranspilerError('Flipping of gate direction is only supported ' 'for CX and ECR at this time.') - return dag \ No newline at end of file + return dag From 2ea238f7beb8d9f58b1002fc6883506736a75da3 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 15:45:30 +0200 Subject: [PATCH 27/70] Update qiskit/transpiler/passes/utils/native_cr_direction.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/transpiler/passes/utils/native_cr_direction.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/utils/native_cr_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py index 6428484894ea..f3f2d14062da 100644 --- a/qiskit/transpiler/passes/utils/native_cr_direction.py +++ b/qiskit/transpiler/passes/utils/native_cr_direction.py @@ -25,8 +25,13 @@ class NativeCRGateDirection(TransformationPass): - """Modify asymmetric gates to match the hardware coupling direction. - This pass makes use of the following identities:: + """Modify asymmetric gates to match the native cross-resonance direction. + + A cross-resonance gate is in the native direction if the control qubit is driven at the + frequency of the target qubit. The non-native direction is recovered by adding Hadamard + gates to the pulse schedule which increase the duration of the schedule. This pass replaces + any non-native gates with native gates and extra single-qubit gates which are therefore + exposed to the transpiler. This pass makes use of the following identities: ┌───┐┌───┐┌───┐ q_0: ──■── q_0: ┤ H ├┤ X ├┤ H ├ ┌─┴─┐ = ├───┤└─┬─┘├───┤ From 764d0ee970ff3b80fe336bb6fc5096cadce834aa Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:08:25 +0200 Subject: [PATCH 28/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 3dcb7d1fbb0c..f0a09eda936f 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -548,6 +548,10 @@ class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): """Decompose two-qubit unitary in terms of echoed cross-resonance gates. """ + def __init__(self, unitary, backend): + self._backend = backend + super().__init__(unitary) + def specialize(self): pass From 2c0aee8247e2d110bb943119cc1968e0ccf7a6df Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:08:38 +0200 Subject: [PATCH 29/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index f0a09eda936f..ae7885f4f2e1 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -562,10 +562,13 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): del simplify circ.h(0) if abs(self.a) > atol: - circ.rzx(-self.a, 0, 1) - circ.x(0) - circ.rzx(self.a, 0, 1) - circ.x(0) + if self.is_native(...): + circ.rzx(-self.a, 0, 1) + circ.x(0) + circ.rzx(self.a, 0, 1) + circ.x(0) + else: + # reverse the direction of the rzx circ.h(0) circ.sdg(0) circ.h(0) From d944a475bff1331db1a405638f08f4c1b9be30b5 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:08:44 +0200 Subject: [PATCH 30/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index cc7cdf7cf467..741f436bec9b 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -31,7 +31,8 @@ class EchoRZXWeylDecomposition(TransformationPass): Each pair of RZXGates forms an echoed RZXGate. """ - def __init__(self): + def __init__(self, backend): + self.backend = backend """EchoRZXWeylDecomposition pass. """ super().__init__() From 90f0f35da276b0344744c9c47a8af0697b0a23cf Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:08:55 +0200 Subject: [PATCH 31/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 741f436bec9b..a95e75cc6438 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -61,7 +61,7 @@ def run(self, dag): for idx, node in enumerate(dag.two_qubit_ops()): if node.type == "op": unitary = qi.Operator(node.op).data - dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary).circuit()) + dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary, backend).circuit()) dag.substitute_node_with_dag(node, dag_weyl) return dag From ee2f6bf91b9f799bcea02afa2f16344fee6e813d Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 16:09:05 +0200 Subject: [PATCH 32/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index ae7885f4f2e1..bd2eff70c2a8 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -555,6 +555,12 @@ def __init__(self, unitary, backend): def specialize(self): pass + def is_native_cx(self, qubit_pair: Tuple) -> bool: + """Check that a CX for a qubit pair is native.""" + inst_map = self.backend.defaults().instruction_schedule_map + cx1 = inst_map.get('cx', qubit_pair) + cx2 = inst_map.get('cx', qubit_pair[::-1]) + return cx1.duration < cx2.duration def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): """Appends Ud(a, b, c) to the circuit. From 882b178786d0a05585bff6a82633e7005512972c Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Tue, 22 Jun 2021 20:32:30 +0200 Subject: [PATCH 33/70] Update qiskit/transpiler/passes/utils/native_cr_direction.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/transpiler/passes/utils/native_cr_direction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/utils/native_cr_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py index f3f2d14062da..7fc1649699ab 100644 --- a/qiskit/transpiler/passes/utils/native_cr_direction.py +++ b/qiskit/transpiler/passes/utils/native_cr_direction.py @@ -88,7 +88,7 @@ def _rzx_dag(self, theta): rzx_dag.apply_operation_back(HGate(), [qr[1]], []) return rzx_dag - def is_native_cx(self, qubit_pair: Tuple): + def is_native_cx(self, qubit_pair: Tuple) -> bool: """Check that a CX for a qubit pair is native.""" inst_map = self.backend.defaults().instruction_schedule_map cx1 = inst_map.get('cx', qubit_pair) From 9615d2459594899af47fd516b23e7414d77e8ecd Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 24 Jun 2021 17:56:48 +0200 Subject: [PATCH 34/70] Minor changes --- .../synthesis/two_qubit_decompose.py | 68 ++++++--- .../echo_rzx_weyl_decomposition.py | 23 ++- .../passes/utils/native_cr_direction.py | 142 ------------------ 3 files changed, 71 insertions(+), 162 deletions(-) delete mode 100644 qiskit/transpiler/passes/utils/native_cr_direction.py diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index bd2eff70c2a8..08f179d3e675 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -28,7 +28,7 @@ import io import base64 import warnings -from typing import ClassVar, Optional +from typing import ClassVar, Optional, Tuple import logging @@ -42,6 +42,7 @@ from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.weyl import weyl_coordinates from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer, DEFAULT_ATOL +from qiskit.providers.backend import Backend logger = logging.getLogger(__name__) @@ -129,12 +130,12 @@ def __init_subclass__(cls, **kwargs): Make explicitly-instantiated subclass __new__ call base __new__ with fidelity=None""" super().__init_subclass__(**kwargs) - cls.__new__ = lambda cls, *a, fidelity=None, **k: TwoQubitWeylDecomposition.__new__( - cls, *a, fidelity=None, **k - ) + cls.__new__ = lambda cls, *a, fidelity=None, backend=None, qubit_pair=None, **k: \ + TwoQubitWeylDecomposition.__new__(cls, *a, fidelity=None, backend=None, + qubit_pair=None, **k) @staticmethod - def __new__(cls, unitary_matrix, *, fidelity=(1.0 - 1.0e-9)): + def __new__(cls, unitary_matrix, *, fidelity=(1.0 - 1.0e-9), backend=None, qubit_pair=None): """Perform the Weyl chamber decomposition, and optionally choose a specialized subclass. The flip into the Weyl Chamber is described in B. Kraus and J. I. Cirac, Phys. Rev. A 63, @@ -548,8 +549,9 @@ class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): """Decompose two-qubit unitary in terms of echoed cross-resonance gates. """ - def __init__(self, unitary, backend): - self._backend = backend + def __init__(self, unitary, backend: Backend, qubit_pair: Tuple): + self.backend = backend + self.qubit_pair = qubit_pair super().__init__(unitary) def specialize(self): @@ -561,6 +563,7 @@ def is_native_cx(self, qubit_pair: Tuple) -> bool: cx1 = inst_map.get('cx', qubit_pair) cx2 = inst_map.get('cx', qubit_pair[::-1]) return cx1.duration < cx2.duration + def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): """Appends Ud(a, b, c) to the circuit. @@ -568,35 +571,64 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): del simplify circ.h(0) if abs(self.a) > atol: - if self.is_native(...): + if self.is_native_cx(self.qubit_pair): circ.rzx(-self.a, 0, 1) circ.x(0) circ.rzx(self.a, 0, 1) circ.x(0) else: - # reverse the direction of the rzx + # reverse the direction of echoed rzx + circ.h(0) + circ.h(1) + circ.rzx(-self.a, 1, 0) + circ.x(1) + circ.rzx(self.a, 1, 0) + circ.x(1) + circ.h(0) + circ.h(1) circ.h(0) circ.sdg(0) circ.h(0) circ.sdg(1) if abs(self.b) > atol: - circ.rzx(-self.b, 0, 1) - circ.x(0) - circ.rzx(self.b, 0, 1) - circ.x(0) + if self.is_native_cx(self.qubit_pair): + circ.rzx(-self.b, 0, 1) + circ.x(0) + circ.rzx(self.b, 0, 1) + circ.x(0) + else: + # reverse the direction of echoed rzx + circ.h(0) + circ.h(1) + circ.rzx(-self.b, 1, 0) + circ.x(1) + circ.rzx(self.b, 1, 0) + circ.x(1) + circ.h(0) + circ.h(1) circ.h(0) circ.s(0) circ.s(1) circ.h(1) if abs(self.c) > atol: - circ.rzx(-self.c, 0, 1) - circ.x(0) - circ.rzx(self.c, 0, 1) - circ.x(0) + if self.is_native_cx(self.qubit_pair): + circ.rzx(-self.c, 0, 1) + circ.x(0) + circ.rzx(self.c, 0, 1) + circ.x(0) + else: + # reverse the direction of echoed rzx + circ.h(0) + circ.h(1) + circ.rzx(-self.c, 1, 0) + circ.x(1) + circ.rzx(self.c, 1, 0) + circ.x(1) + circ.h(0) + circ.h(1) circ.h(1) - class TwoQubitWeylMirrorControlledEquiv(TwoQubitWeylDecomposition): """U ~ Ud(𝜋/4, 𝜋/4, α) ~ SWAP . Ctrl-U diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index a95e75cc6438..6402d91f66cb 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -18,6 +18,8 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.converters import circuit_to_dag +from qiskit.transpiler.layout import Layout + import qiskit.quantum_info as qi from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylEchoRZX @@ -41,7 +43,7 @@ def run(self, dag): """Run the EchoRZXWeylDecomposition pass on `dag`. Rewrites two-qubit gates in an arbitrary circuit in terms of echoed cross-resonance - gates by computing the Cartan decomposition of the corresponding unitary. Modifies the + gates by computing the Weyl decomposition of the corresponding unitary. Modifies the input dag. Args: @@ -58,10 +60,27 @@ def run(self, dag): raise TranspilerError('EchoRZXWeylDecomposition expects a single qreg input DAG,' 'but input DAG had qregs: {}.'.format(dag.qregs)) + trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) + for idx, node in enumerate(dag.two_qubit_ops()): if node.type == "op": + control = node.qargs[0] + target = node.qargs[1] + + physical_q0 = trivial_layout[control] + physical_q1 = trivial_layout[target] + + config = self.backend.configuration() + if [physical_q0, physical_q1] not in config.coupling_map: + raise TranspilerError('Qubits %s and %s are not connected on the backend' + % (physical_q0, physical_q1)) + + qubit_pair = (physical_q0, physical_q1) + unitary = qi.Operator(node.op).data - dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary, backend).circuit()) + dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary, + backend=self.backend, + qubit_pair=qubit_pair).circuit()) dag.substitute_node_with_dag(node, dag_weyl) return dag diff --git a/qiskit/transpiler/passes/utils/native_cr_direction.py b/qiskit/transpiler/passes/utils/native_cr_direction.py deleted file mode 100644 index 7fc1649699ab..000000000000 --- a/qiskit/transpiler/passes/utils/native_cr_direction.py +++ /dev/null @@ -1,142 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2017, 2021. -# -# 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. - -"""Rearrange the direction of the cx nodes to match the hardware-native backend cx direction.""" - -from math import pi -from typing import Tuple - -from qiskit.transpiler.layout import Layout -from qiskit.transpiler.basepasses import TransformationPass -from qiskit.transpiler.exceptions import TranspilerError - -from qiskit.circuit import QuantumRegister -from qiskit.dagcircuit import DAGCircuit -from qiskit.circuit.library.standard_gates import RYGate, HGate, CXGate, ECRGate, RZXGate - - -class NativeCRGateDirection(TransformationPass): - """Modify asymmetric gates to match the native cross-resonance direction. - - A cross-resonance gate is in the native direction if the control qubit is driven at the - frequency of the target qubit. The non-native direction is recovered by adding Hadamard - gates to the pulse schedule which increase the duration of the schedule. This pass replaces - any non-native gates with native gates and extra single-qubit gates which are therefore - exposed to the transpiler. This pass makes use of the following identities: - ┌───┐┌───┐┌───┐ - q_0: ──■── q_0: ┤ H ├┤ X ├┤ H ├ - ┌─┴─┐ = ├───┤└─┬─┘├───┤ - q_1: ┤ X ├ q_1: ┤ H ├──■──┤ H ├ - └───┘ └───┘ └───┘ - ┌──────┐ ┌───────────┐┌──────┐┌───┐ - q_0: ┤0 ├ q_0: ┤ RY(-pi/2) ├┤1 ├┤ H ├ - │ ECR │ = └┬──────────┤│ ECR │├───┤ - q_1: ┤1 ├ q_1: ─┤ RY(pi/2) ├┤0 ├┤ H ├ - └──────┘ └──────────┘└──────┘└───┘ - ┌─────────┐ ┌───┐┌─────────┐┌───┐ - q_0: ┤0 ├ q_0: ┤ H ├┤1 ├┤ H ├ - │ Rzx(θ) │ = ├───┤│ Rzx(θ) │├───┤ - q_1: ┤1 ├ q_1: ┤ H ├┤0 ├┤ H ├ - └─────────┘ └───┘└─────────┘└───┘ - """ - - def __init__(self, backend): - """GateDirection pass. - Args: - backend (BaseBackend): The hardware backend that is used. - """ - super().__init__() - self.backend = backend - - # Create the replacement dag and associated register. - self._cx_dag = DAGCircuit() - qr = QuantumRegister(2) - self._cx_dag.add_qreg(qr) - self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) - self._cx_dag.apply_operation_back(CXGate(), [qr[1], qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[0]], []) - self._cx_dag.apply_operation_back(HGate(), [qr[1]], []) - - self._ecr_dag = DAGCircuit() - qr = QuantumRegister(2) - self._ecr_dag.add_qreg(qr) - self._ecr_dag.apply_operation_back(RYGate(-pi / 2), [qr[0]], []) - self._ecr_dag.apply_operation_back(RYGate(pi / 2), [qr[1]], []) - self._ecr_dag.apply_operation_back(ECRGate(), [qr[1], qr[0]], []) - self._ecr_dag.apply_operation_back(HGate(), [qr[0]], []) - self._ecr_dag.apply_operation_back(HGate(), [qr[1]], []) - - def _rzx_dag(self, theta): - - rzx_dag = DAGCircuit() - qr = QuantumRegister(2) - rzx_dag.add_qreg(qr) - rzx_dag.apply_operation_back(HGate(), [qr[0]], []) - rzx_dag.apply_operation_back(HGate(), [qr[1]], []) - rzx_dag.apply_operation_back(RZXGate(theta), [qr[1], qr[0]], []) - rzx_dag.apply_operation_back(HGate(), [qr[0]], []) - rzx_dag.apply_operation_back(HGate(), [qr[1]], []) - return rzx_dag - - def is_native_cx(self, qubit_pair: Tuple) -> bool: - """Check that a CX for a qubit pair is native.""" - inst_map = self.backend.defaults().instruction_schedule_map - cx1 = inst_map.get('cx', qubit_pair) - cx2 = inst_map.get('cx', qubit_pair[::-1]) - return cx1.duration < cx2.duration - - def run(self, dag): - """Run the GateDirection pass on `dag`. - Flips the cx, ecr, and rzx nodes to match the hardware-native coupling map. - Modifies the input dag. - Args: - dag (DAGCircuit): DAG to map. - Returns: - DAGCircuit: The rearranged dag for the hardware-native coupling map. - Raises: - TranspilerError: If the circuit cannot be mapped just by flipping the - cx nodes. - """ - - if len(dag.qregs) > 1: - raise TranspilerError('GateDirection expects a single qreg input DAG,' - 'but input DAG had qregs: {}.'.format( - dag.qregs)) - - trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) - - for node in dag.two_qubit_ops(): - control = node.qargs[0] - target = node.qargs[1] - - physical_q0 = trivial_layout[control] - physical_q1 = trivial_layout[target] - - config = self.backend.configuration() - if [physical_q0, physical_q1] not in config.coupling_map: - raise TranspilerError('Qubits %s and %s are not connected on the backend' - % (physical_q0, physical_q1)) - - if not self.is_native_cx((physical_q0, physical_q1)): - if node.name == 'cx': - dag.substitute_node_with_dag(node, self._cx_dag) - elif node.name == 'ecr': - dag.substitute_node_with_dag(node, self._ecr_dag) - elif node.name == 'rzx': - theta = node.op.params[0] - dag.substitute_node_with_dag(node, self._rzx_dag(theta)) - else: - raise TranspilerError('Flipping of gate direction is only supported ' - 'for CX and ECR at this time.') - - return dag From 2c3c4cab1bcf18af57ceef7759bd05dedb70ea97 Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 24 Jun 2021 18:00:20 +0200 Subject: [PATCH 35/70] Added "backend" to passmanager_config and transpiler to use it in pass manager --- qiskit/compiler/transpiler.py | 22 +++++++++++++++++----- qiskit/transpiler/passmanager_config.py | 3 +++ 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index fe556e2c6100..04c46774b946 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -35,6 +35,7 @@ from qiskit.transpiler.passmanager_config import PassManagerConfig from qiskit.transpiler.preset_passmanagers import ( level_0_pass_manager, + pulse_efficient_pass_manager, level_1_pass_manager, level_2_pass_manager, level_3_pass_manager, @@ -346,6 +347,8 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua if level == 0: pass_manager = level_0_pass_manager(pass_manager_config) + elif level == 'pulse_efficient': + pass_manager = pulse_efficient_pass_manager(pass_manager_config) elif level == 1: pass_manager = level_1_pass_manager(pass_manager_config) elif level == 2: @@ -475,6 +478,7 @@ def _parse_transpile_args( translation_method = _parse_translation_method(translation_method, num_circuits) approximation_degree = _parse_approximation_degree(approximation_degree, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) + backend = _parse_backend(backend, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) output_name = _parse_output_name(output_name, circuits) callback = _parse_callback(callback, num_circuits) @@ -499,6 +503,7 @@ def _parse_transpile_args( durations, approximation_degree, seed_transpiler, + backend, optimization_level, output_name, callback, @@ -518,12 +523,13 @@ def _parse_transpile_args( instruction_durations=args[8], approximation_degree=args[9], seed_transpiler=args[10], + backend=args[11], ), - "optimization_level": args[11], - "output_name": args[12], - "callback": args[13], - "backend_num_qubits": args[14], - "faulty_qubits_map": args[15], + "optimization_level": args[12], + "output_name": args[13], + "callback": args[14], + "backend_num_qubits": args[15], + "faulty_qubits_map": args[16], } list_transpile_args.append(transpile_args) @@ -759,6 +765,12 @@ def _parse_seed_transpiler(seed_transpiler, num_circuits): return seed_transpiler +def _parse_backend(backend, num_circuits): + if not isinstance(backend, list): + backend = [backend] * num_circuits + return backend + + def _parse_optimization_level(optimization_level, num_circuits): if not isinstance(optimization_level, list): optimization_level = [optimization_level] * num_circuits diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index e91ed5ed623d..a6c67c8d2403 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -29,6 +29,7 @@ def __init__( backend_properties=None, approximation_degree=None, seed_transpiler=None, + backend=None, ): """Initialize a PassManagerConfig object @@ -54,6 +55,7 @@ def __init__( (1.0=no approximation, 0.0=maximal approximation) seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. + backend (Backend): the backend that is used. """ self.initial_layout = initial_layout self.basis_gates = basis_gates @@ -66,3 +68,4 @@ def __init__( self.backend_properties = backend_properties self.approximation_degree = approximation_degree self.seed_transpiler = seed_transpiler + self.backend = backend From ed5c0aa2de0d227f0c6434c43b4fbd5c56704fcf Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 24 Jun 2021 18:13:32 +0200 Subject: [PATCH 36/70] Added new pulse-efficient preset pass manager --- .../preset_passmanagers/pulse_efficient.py | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 qiskit/transpiler/preset_passmanagers/pulse_efficient.py diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py new file mode 100644 index 000000000000..76e317d01e36 --- /dev/null +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -0,0 +1,79 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +"""Pass manager for pulse-efficient, cross-resonance gate based circuits. + +Pulse-efficient pass manager: Computes Weyl parameters of two-qubit operations in a circuit +and translates the circuit into a pulse-efficient, cross-resonance gate based circuit by creating +calibrations for the cross-resonance gates. Reference: https://arxiv.org/pdf/2105.01063.pdf +""" + +from qiskit.transpiler.passmanager_config import PassManagerConfig +from qiskit.transpiler.passmanager import PassManager + +from qiskit.transpiler.passes import Collect2qBlocks +from qiskit.transpiler.passes import ConsolidateBlocks + +from qiskit.transpiler.passes.scheduling.calibration_creators import RZXCalibrationBuilderNoEcho +from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition +from qiskit.circuit.library.standard_gates.equivalence_library import StandardEquivalenceLibrary as std_eqlib +from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions +from qiskit.transpiler.passes import Optimize1qGatesDecomposition + + +def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: + """Pulse-efficient pass manager. + + This pass manager consolidates all consecutive two-qubit operations in a quantum circuit and + computes the Weyl decomposition of the corresponding unitaries. It rewrites the unitary operations + in terms of cross-resonance gates and creates calibrations for these gates. Lastly, the circuit + is rewritten in the hardware-native basis ['rzx', 'rz', 'x', 'sx']. + + Note: + This pass manager does currently not support the transpilation of two-qubit operations + between qubits that are not connected on the hardware. + + Args: + pass_manager_config: configuration of the pass manager. + + Returns: + a pulse-efficient pass manager. + """ + backend = pass_manager_config.backend + + # 1. Consolidate all consecutive two-qubit operations + _collect_2q_blocks = Collect2qBlocks() + _consolidate_blocks = ConsolidateBlocks(basis_gates=['rz', 'sx', 'x', 'rxx']) + + # 2. Decompose two-qubit unitaries in terms of echoed RZX gates according to the Weyl decomposition + _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(backend) + + # 3. Add calibrations + _rzx_calibrations = RZXCalibrationBuilderNoEcho(backend) + + # 4. Unroll to ['rzx', 'rz', 'x', 'sx'] basis + rzx_basis = ['rzx', 'rz', 'x', 'sx'] + _unroll = [UnrollCustomDefinitions(std_eqlib, rzx_basis), + BasisTranslator(std_eqlib, rzx_basis)] + + # 5. Optimize one-qubit decomposition + _optimize_1q_decomposition = Optimize1qGatesDecomposition(rzx_basis) + + # Build pass manager + pm0 = PassManager() + pm0.append(_collect_2q_blocks) + pm0.append(_consolidate_blocks) + pm0.append(_echo_rzx_weyl_decomposition) + pm0.append(_rzx_calibrations) + pm0.append(_unroll) + pm0.append(_optimize_1q_decomposition) + return pm0 From b501631b541d1daf2f9f5486ef6325fba3f4ac56 Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 24 Jun 2021 18:15:21 +0200 Subject: [PATCH 37/70] Added new pulse-efficient preset pass manager --- qiskit/transpiler/preset_passmanagers/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/qiskit/transpiler/preset_passmanagers/__init__.py b/qiskit/transpiler/preset_passmanagers/__init__.py index f005cb0c54a2..2f7730317bbd 100644 --- a/qiskit/transpiler/preset_passmanagers/__init__.py +++ b/qiskit/transpiler/preset_passmanagers/__init__.py @@ -30,3 +30,4 @@ from .level1 import level_1_pass_manager from .level2 import level_2_pass_manager from .level3 import level_3_pass_manager +from .pulse_efficient import pulse_efficient_pass_manager From a4e28d525ef415a5c9818db57822326c342ce2e4 Mon Sep 17 00:00:00 2001 From: catornow Date: Thu, 24 Jun 2021 18:16:01 +0200 Subject: [PATCH 38/70] Added tests for the EchoRZXWeylDecomposition pass and the TwoQubitWeylEchoRZX class --- .../test_echo_rzx_weyl_decomposition.py | 168 ++++++++++++++++++ 1 file changed, 168 insertions(+) create mode 100644 test/python/transpiler/test_echo_rzx_weyl_decomposition.py diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py new file mode 100644 index 000000000000..d159f22e99dd --- /dev/null +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -0,0 +1,168 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2018. +# +# 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 EchoRZXWeylDecomposition pass and the TwoQubitWeylEchoRZX class""" +import unittest +from math import pi +import numpy as np + +from qiskit import QuantumRegister, QuantumCircuit +from qiskit.transpiler import TranspilerError + +from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition +from qiskit.converters import circuit_to_dag, dag_to_circuit +from qiskit.test import QiskitTestCase +from qiskit.test.mock import FakeParis + +import qiskit.quantum_info as qi + +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition, TwoQubitWeylEchoRZX + + +class TestEchoRZXWeylDecomposition(QiskitTestCase): + """Tests the EchoRZXWeylDecomposition pass and the TwoQubitWeylEchoRZX class.""" + + def setUp(self): + super().setUp() + self.backend = FakeParis() + + def test_coupling_error(self): + """Raise TranspilerError if qubits are not coupled on the hardware. + """ + qr = QuantumRegister(4, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[3]) + dag = circuit_to_dag(circuit) + + pass_ = EchoRZXWeylDecomposition(self.backend) + + with self.assertRaises(TranspilerError): + pass_.run(dag) + + def test_native_weyl_decomposition(self): + """The CX is in the hardware-native direction + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + + unitary_circuit = qi.Operator(circuit).data + + dag = circuit_to_dag(circuit) + pass_ = EchoRZXWeylDecomposition(self.backend) + after = dag_to_circuit(pass_.run(dag)) + + unitary_after = qi.Operator(after).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_after)) + + def test_non_native_weyl_decomposition(self): + """The RZZ is not in the hardware-native direction + """ + theta = pi / 9 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.rzz(theta, qr[1], qr[0]) + + unitary_circuit = qi.Operator(circuit).data + + dag = circuit_to_dag(circuit) + pass_ = EchoRZXWeylDecomposition(self.backend) + after = dag_to_circuit(pass_.run(dag)) + + unitary_after = qi.Operator(after).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_after)) + + def test_weyl_unitaries_random_circuit(self): + """Weyl decomposition for random two-qubit circuit. + """ + theta = pi / 9 + epsilon = 5 + delta = -1 + eta = 0.2 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + + # random two-qubit circuit + circuit.rzx(theta, 0, 1) + circuit.rzz(epsilon, 0, 1) + circuit.rz(eta, 0) + circuit.swap(1, 0) + circuit.h(0) + circuit.rzz(delta, 1, 0) + circuit.swap(0, 1) + circuit.cx(1, 0) + circuit.swap(0, 1) + circuit.h(1) + circuit.rxx(theta, 0, 1) + circuit.ryy(theta, 1, 0) + circuit.ecr(0, 1) + + unitary_circuit = qi.Operator(circuit).data + + dag = circuit_to_dag(circuit) + pass_ = EchoRZXWeylDecomposition(self.backend) + after = dag_to_circuit(pass_.run(dag)) + + unitary_after = qi.Operator(after).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_after)) + + def test_weyl_parameters(self): + """Computation of the correct RZX Weyl parameters + """ + theta = pi / 3 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + qubit_pair = (qr[0], qr[1]) + + circuit.rzz(theta, qubit_pair[0], qubit_pair[1]) + circuit.swap(qubit_pair[0], qubit_pair[1]) + unitary_circuit = qi.Operator(circuit).data + + # Weyl parameters (alpha, beta, gamma) + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) + rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).a + rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).b + rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).c + + self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) + + def test_non_native_weyl_parameters(self): + """Weyl parameters for a non-hardware-native CX direction + """ + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + qubit_pair = (qr[1], qr[0]) + circuit.cx(qubit_pair[1], qubit_pair[0]) + unitary_circuit = qi.Operator(circuit).data + + # Weyl parameters (alpha, beta, gamma) + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) + rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).a + rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).b + rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).c + + self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) + + +if __name__ == "__main__": + unittest.main() \ No newline at end of file From f38fad208a34944c394bcee748b6301f24ff6571 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 13:49:04 +0200 Subject: [PATCH 39/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 6402d91f66cb..ec89a77d54f4 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -35,8 +35,7 @@ class EchoRZXWeylDecomposition(TransformationPass): def __init__(self, backend): self.backend = backend - """EchoRZXWeylDecomposition pass. - """ + """EchoRZXWeylDecomposition pass.""" super().__init__() def run(self, dag): From b67fb0a140486c3f70ad5c7344077a10fa56db4b Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 13:49:48 +0200 Subject: [PATCH 40/70] Update test/python/transpiler/test_echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- test/python/transpiler/test_echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index d159f22e99dd..2276f7a775e5 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -165,4 +165,4 @@ def test_non_native_weyl_parameters(self): if __name__ == "__main__": - unittest.main() \ No newline at end of file + unittest.main() From 4aa7da9ae66ea61a723ed4db2e1f3f37b0e8b557 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 14:46:03 +0200 Subject: [PATCH 41/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 08f179d3e675..0f2b4cb0e1af 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -565,9 +565,7 @@ def is_native_cx(self, qubit_pair: Tuple) -> bool: return cx1.duration < cx2.duration def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): - """Appends Ud(a, b, c) to the circuit. - - Can be overriden in subclasses for special cases""" + """Appends Ud(a, b, c) to the circuit.""" del simplify circ.h(0) if abs(self.a) > atol: From 8dd4556a9fe324c8b4da0426bdf2615f2ce05435 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 22:19:00 +0200 Subject: [PATCH 42/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index ec89a77d54f4..3236e8d7d547 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -61,7 +61,7 @@ def run(self, dag): trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) - for idx, node in enumerate(dag.two_qubit_ops()): + for node in dag.two_qubit_ops(): if node.type == "op": control = node.qargs[0] target = node.qargs[1] From 8e0115812d1356f6d18c415b54a3ed28f6e71efa Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 22:19:55 +0200 Subject: [PATCH 43/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 3236e8d7d547..b2e57e1400a7 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -57,7 +57,7 @@ def run(self, dag): if len(dag.qregs) > 1: raise TranspilerError('EchoRZXWeylDecomposition expects a single qreg input DAG,' - 'but input DAG had qregs: {}.'.format(dag.qregs)) + f'but input DAG had qregs: {dag.qregs}.') trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) From 33439a9f8624d4031015a7f9c0621a3dc988651c Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Fri, 25 Jun 2021 22:21:11 +0200 Subject: [PATCH 44/70] Update qiskit/transpiler/preset_passmanagers/pulse_efficient.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/transpiler/preset_passmanagers/pulse_efficient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index 76e317d01e36..fc7044d4d55e 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -36,7 +36,7 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass This pass manager consolidates all consecutive two-qubit operations in a quantum circuit and computes the Weyl decomposition of the corresponding unitaries. It rewrites the unitary operations in terms of cross-resonance gates and creates calibrations for these gates. Lastly, the circuit - is rewritten in the hardware-native basis ['rzx', 'rz', 'x', 'sx']. + is rewritten in the hardware-native basis with 'rzx'. Note: This pass manager does currently not support the transpilation of two-qubit operations From 59224690be6feae4d5af4a78ba0af15ef9287234 Mon Sep 17 00:00:00 2001 From: catornow Date: Sat, 26 Jun 2021 16:42:56 +0200 Subject: [PATCH 45/70] Use instruction schedule map as variable instead of backend, added _apply_rzx(circ, angle) and _apply_reverse_rzx(circ, angle) --- .../synthesis/two_qubit_decompose.py | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 0f2b4cb0e1af..d258be2cb623 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -42,7 +42,6 @@ from qiskit.quantum_info.operators import Operator from qiskit.quantum_info.synthesis.weyl import weyl_coordinates from qiskit.quantum_info.synthesis.one_qubit_decompose import OneQubitEulerDecomposer, DEFAULT_ATOL -from qiskit.providers.backend import Backend logger = logging.getLogger(__name__) @@ -130,12 +129,12 @@ def __init_subclass__(cls, **kwargs): Make explicitly-instantiated subclass __new__ call base __new__ with fidelity=None""" super().__init_subclass__(**kwargs) - cls.__new__ = lambda cls, *a, fidelity=None, backend=None, qubit_pair=None, **k: \ - TwoQubitWeylDecomposition.__new__(cls, *a, fidelity=None, backend=None, + cls.__new__ = lambda cls, *a, fidelity=None, inst_map=None, qubit_pair=None, **k: \ + TwoQubitWeylDecomposition.__new__(cls, *a, fidelity=None, inst_map=None, qubit_pair=None, **k) @staticmethod - def __new__(cls, unitary_matrix, *, fidelity=(1.0 - 1.0e-9), backend=None, qubit_pair=None): + def __new__(cls, unitary_matrix, *, fidelity=(1.0 - 1.0e-9), inst_map=None, qubit_pair=None): """Perform the Weyl chamber decomposition, and optionally choose a specialized subclass. The flip into the Weyl Chamber is described in B. Kraus and J. I. Cirac, Phys. Rev. A 63, @@ -549,19 +548,40 @@ class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): """Decompose two-qubit unitary in terms of echoed cross-resonance gates. """ - def __init__(self, unitary, backend: Backend, qubit_pair: Tuple): - self.backend = backend + def __init__(self, unitary, inst_map, qubit_pair: Tuple): + self.inst_map = inst_map self.qubit_pair = qubit_pair super().__init__(unitary) def specialize(self): pass + @staticmethod + def _apply_rzx(circ: QuantumCircuit, angle: float): + """Echoed RZX gate""" + circ.rzx(-angle, 0, 1) + circ.x(0) + circ.rzx(angle, 0, 1) + circ.x(0) + return circ + + @staticmethod + def _apply_reverse_rzx(circ: QuantumCircuit, angle: float): + """Reverse direction of the echoed RZX gate""" + circ.h(0) + circ.h(1) + circ.rzx(-angle, 1, 0) + circ.x(1) + circ.rzx(angle, 1, 0) + circ.x(1) + circ.h(0) + circ.h(1) + return circ + def is_native_cx(self, qubit_pair: Tuple) -> bool: """Check that a CX for a qubit pair is native.""" - inst_map = self.backend.defaults().instruction_schedule_map - cx1 = inst_map.get('cx', qubit_pair) - cx2 = inst_map.get('cx', qubit_pair[::-1]) + cx1 = self.inst_map.get('cx', qubit_pair) + cx2 = self.inst_map.get('cx', qubit_pair[::-1]) return cx1.duration < cx2.duration def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): @@ -570,60 +590,27 @@ def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): circ.h(0) if abs(self.a) > atol: if self.is_native_cx(self.qubit_pair): - circ.rzx(-self.a, 0, 1) - circ.x(0) - circ.rzx(self.a, 0, 1) - circ.x(0) + self._apply_rzx(circ, self.a) else: - # reverse the direction of echoed rzx - circ.h(0) - circ.h(1) - circ.rzx(-self.a, 1, 0) - circ.x(1) - circ.rzx(self.a, 1, 0) - circ.x(1) - circ.h(0) - circ.h(1) + self._apply_reverse_rzx(circ, self.a) circ.h(0) circ.sdg(0) circ.h(0) circ.sdg(1) if abs(self.b) > atol: if self.is_native_cx(self.qubit_pair): - circ.rzx(-self.b, 0, 1) - circ.x(0) - circ.rzx(self.b, 0, 1) - circ.x(0) + self._apply_rzx(circ, self.b) else: - # reverse the direction of echoed rzx - circ.h(0) - circ.h(1) - circ.rzx(-self.b, 1, 0) - circ.x(1) - circ.rzx(self.b, 1, 0) - circ.x(1) - circ.h(0) - circ.h(1) + self._apply_reverse_rzx(circ, self.b) circ.h(0) circ.s(0) circ.s(1) circ.h(1) if abs(self.c) > atol: if self.is_native_cx(self.qubit_pair): - circ.rzx(-self.c, 0, 1) - circ.x(0) - circ.rzx(self.c, 0, 1) - circ.x(0) + self._apply_rzx(circ, self.c) else: - # reverse the direction of echoed rzx - circ.h(0) - circ.h(1) - circ.rzx(-self.c, 1, 0) - circ.x(1) - circ.rzx(self.c, 1, 0) - circ.x(1) - circ.h(0) - circ.h(1) + self._apply_reverse_rzx(circ, self.c) circ.h(1) From e335416edea9e80eb45c271810e527763900f4e4 Mon Sep 17 00:00:00 2001 From: catornow Date: Sat, 26 Jun 2021 16:49:49 +0200 Subject: [PATCH 46/70] Added instruction schedule map as variable to pass manager config and transpiler --- .../optimization/echo_rzx_weyl_decomposition.py | 14 +++++++------- .../preset_passmanagers/pulse_efficient.py | 14 +++++++------- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index ec89a77d54f4..3d6f69874aa6 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -33,8 +33,8 @@ class EchoRZXWeylDecomposition(TransformationPass): Each pair of RZXGates forms an echoed RZXGate. """ - def __init__(self, backend): - self.backend = backend + def __init__(self, inst_map): + self.inst_map = inst_map """EchoRZXWeylDecomposition pass.""" super().__init__() @@ -69,16 +69,16 @@ def run(self, dag): physical_q0 = trivial_layout[control] physical_q1 = trivial_layout[target] - config = self.backend.configuration() - if [physical_q0, physical_q1] not in config.coupling_map: - raise TranspilerError('Qubits %s and %s are not connected on the backend' - % (physical_q0, physical_q1)) + # config = self.backend.configuration() + # if [physical_q0, physical_q1] not in config.coupling_map: + # raise TranspilerError('Qubits %s and %s are not connected on the backend' + # % (physical_q0, physical_q1)) qubit_pair = (physical_q0, physical_q1) unitary = qi.Operator(node.op).data dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary, - backend=self.backend, + inst_map=self.inst_map, qubit_pair=qubit_pair).circuit()) dag.substitute_node_with_dag(node, dag_weyl) diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index 76e317d01e36..d874d8e61903 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -48,19 +48,19 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass Returns: a pulse-efficient pass manager. """ - backend = pass_manager_config.backend + inst_map = pass_manager_config.inst_map # 1. Consolidate all consecutive two-qubit operations _collect_2q_blocks = Collect2qBlocks() _consolidate_blocks = ConsolidateBlocks(basis_gates=['rz', 'sx', 'x', 'rxx']) # 2. Decompose two-qubit unitaries in terms of echoed RZX gates according to the Weyl decomposition - _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(backend) - - # 3. Add calibrations - _rzx_calibrations = RZXCalibrationBuilderNoEcho(backend) - - # 4. Unroll to ['rzx', 'rz', 'x', 'sx'] basis + _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(inst_map) + # + # # 3. Add calibrations + _rzx_calibrations = RZXCalibrationBuilderNoEcho(inst_map) + # + # # 4. Unroll to ['rzx', 'rz', 'x', 'sx'] basis rzx_basis = ['rzx', 'rz', 'x', 'sx'] _unroll = [UnrollCustomDefinitions(std_eqlib, rzx_basis), BasisTranslator(std_eqlib, rzx_basis)] From 67690da132ed89861ef84b742216458c33729fcd Mon Sep 17 00:00:00 2001 From: catornow Date: Sat, 26 Jun 2021 17:09:21 +0200 Subject: [PATCH 47/70] Use instruction schedule map in pass manager, modification in pulse-efficient pass manager --- qiskit/compiler/transpiler.py | 29 ++++++++++++------- qiskit/transpiler/passmanager_config.py | 6 ++-- .../preset_passmanagers/pulse_efficient.py | 18 +++++++----- 3 files changed, 32 insertions(+), 21 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 04c46774b946..59d6c1386366 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -59,6 +59,7 @@ def transpile( dt: Optional[float] = None, approximation_degree: Optional[float] = None, seed_transpiler: Optional[int] = None, + inst_map: Dict[str, Dict[Tuple[int], Schedule]] = None, optimization_level: Optional[int] = None, pass_manager: Optional[PassManager] = None, callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None, @@ -187,6 +188,7 @@ def callback_func(**kwargs): output_name: A list with strings to identify the output circuits. The length of the list should be exactly the length of the ``circuits`` parameter. + inst_map: ... Returns: The transpiled circuit(s). @@ -222,6 +224,7 @@ def callback_func(**kwargs): translation_method=translation_method, approximation_degree=approximation_degree, backend=backend, + inst_map=inst_map, ) warnings.warn( @@ -261,6 +264,7 @@ def callback_func(**kwargs): dt, approximation_degree, seed_transpiler, + inst_map, optimization_level, callback, output_name, @@ -347,16 +351,16 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua if level == 0: pass_manager = level_0_pass_manager(pass_manager_config) - elif level == 'pulse_efficient': - pass_manager = pulse_efficient_pass_manager(pass_manager_config) elif level == 1: pass_manager = level_1_pass_manager(pass_manager_config) elif level == 2: pass_manager = level_2_pass_manager(pass_manager_config) elif level == 3: pass_manager = level_3_pass_manager(pass_manager_config) + elif level == 'pulse_efficient': + pass_manager = pulse_efficient_pass_manager(pass_manager_config) else: - raise TranspilerError("optimization_level can range from 0 to 3.") + raise TranspilerError("optimization_level can range from 0 to 3, or can be 'pulse_efficient'.") result = pass_manager.run( circuit, callback=transpile_config["callback"], output_name=transpile_config["output_name"] @@ -443,6 +447,7 @@ def _parse_transpile_args( dt, approximation_degree, seed_transpiler, + inst_map, optimization_level, callback, output_name, @@ -478,7 +483,7 @@ def _parse_transpile_args( translation_method = _parse_translation_method(translation_method, num_circuits) approximation_degree = _parse_approximation_degree(approximation_degree, num_circuits) seed_transpiler = _parse_seed_transpiler(seed_transpiler, num_circuits) - backend = _parse_backend(backend, num_circuits) + inst_map = _parse_inst_map(inst_map, backend, num_circuits) optimization_level = _parse_optimization_level(optimization_level, num_circuits) output_name = _parse_output_name(output_name, circuits) callback = _parse_callback(callback, num_circuits) @@ -503,7 +508,7 @@ def _parse_transpile_args( durations, approximation_degree, seed_transpiler, - backend, + inst_map, optimization_level, output_name, callback, @@ -523,7 +528,7 @@ def _parse_transpile_args( instruction_durations=args[8], approximation_degree=args[9], seed_transpiler=args[10], - backend=args[11], + inst_map=args[11], ), "optimization_level": args[12], "output_name": args[13], @@ -765,10 +770,14 @@ def _parse_seed_transpiler(seed_transpiler, num_circuits): return seed_transpiler -def _parse_backend(backend, num_circuits): - if not isinstance(backend, list): - backend = [backend] * num_circuits - return backend +def _parse_inst_map(inst_map, backend, num_circuits): + if inst_map is None: + if backend.defaults(): + backend_defaults = backend.defaults() + inst_map = backend_defaults.instruction_schedule_map + if not isinstance(inst_map, list): + inst_map = [inst_map] * num_circuits + return inst_map def _parse_optimization_level(optimization_level, num_circuits): diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index a6c67c8d2403..456ce414f339 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -29,7 +29,7 @@ def __init__( backend_properties=None, approximation_degree=None, seed_transpiler=None, - backend=None, + inst_map=None ): """Initialize a PassManagerConfig object @@ -55,7 +55,7 @@ def __init__( (1.0=no approximation, 0.0=maximal approximation) seed_transpiler (int): Sets random seed for the stochastic parts of the transpiler. - backend (Backend): the backend that is used. + inst_map (Dict[str, Dict[Tuple[int], Schedule]]): Schedule instructions """ self.initial_layout = initial_layout self.basis_gates = basis_gates @@ -68,4 +68,4 @@ def __init__( self.backend_properties = backend_properties self.approximation_degree = approximation_degree self.seed_transpiler = seed_transpiler - self.backend = backend + self.inst_map = inst_map diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index c5c962a38cfd..ad2c6c6ee630 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -49,6 +49,7 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass a pulse-efficient pass manager. """ inst_map = pass_manager_config.inst_map + basis_gates = pass_manager_config.basis_gates # 1. Consolidate all consecutive two-qubit operations _collect_2q_blocks = Collect2qBlocks() @@ -56,17 +57,18 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass # 2. Decompose two-qubit unitaries in terms of echoed RZX gates according to the Weyl decomposition _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(inst_map) - # - # # 3. Add calibrations + + # 3. Add calibrations _rzx_calibrations = RZXCalibrationBuilderNoEcho(inst_map) - # - # # 4. Unroll to ['rzx', 'rz', 'x', 'sx'] basis - rzx_basis = ['rzx', 'rz', 'x', 'sx'] - _unroll = [UnrollCustomDefinitions(std_eqlib, rzx_basis), - BasisTranslator(std_eqlib, rzx_basis)] + + # 4. Unroll to backend basis with rzx + basis_gates = list(set(basis_gates) | {'rzx'}) + + _unroll = [UnrollCustomDefinitions(std_eqlib, basis_gates), + BasisTranslator(std_eqlib, basis_gates)] # 5. Optimize one-qubit decomposition - _optimize_1q_decomposition = Optimize1qGatesDecomposition(rzx_basis) + _optimize_1q_decomposition = Optimize1qGatesDecomposition(basis_gates) # Build pass manager pm0 = PassManager() From 093ab180c957f27c84ae53fa8f87e3fe43f4000e Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 27 Jun 2021 11:53:32 +0200 Subject: [PATCH 48/70] Allow for str expression as optimization_level --- qiskit/compiler/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 59d6c1386366..c66b0515a3bf 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -60,7 +60,7 @@ def transpile( approximation_degree: Optional[float] = None, seed_transpiler: Optional[int] = None, inst_map: Dict[str, Dict[Tuple[int], Schedule]] = None, - optimization_level: Optional[int] = None, + optimization_level: Union[Optional[int], Optional[str]] = None, pass_manager: Optional[PassManager] = None, callback: Optional[Callable[[BasePass, DAGCircuit, float, PropertySet, int], Any]] = None, output_name: Optional[Union[str, List[str]]] = None, From b274b844840eb954e334f91efdfa5c12b23fb374 Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 27 Jun 2021 11:59:29 +0200 Subject: [PATCH 49/70] Added instruction schedule map as variable for RZXCalibrationBuilder --- .../passes/scheduling/calibration_creators.py | 36 ++++++++++--------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 401e751045a0..013c27478809 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -13,7 +13,7 @@ """Calibration creators.""" import math -from typing import List, Union +from typing import List, Union, Optional, Dict, Tuple from abc import abstractmethod import numpy as np @@ -28,7 +28,7 @@ ) from qiskit.pulse.instructions.instruction import Instruction from qiskit.exceptions import QiskitError -from qiskit.providers import basebackend +from qiskit.providers import BaseBackend from qiskit.dagcircuit import DAGNode from qiskit.circuit.library.standard_gates import RZXGate from qiskit.transpiler.basepasses import TransformationPass @@ -82,26 +82,28 @@ class RZXCalibrationBuilder(CalibrationCreator): angle. Additional details can be found in https://arxiv.org/abs/2012.11660. """ - def __init__(self, backend: basebackend): + def __init__(self, inst_map: Dict[str, Dict[Tuple[int], Schedule]], backend: Optional[BaseBackend] = None): """ - Initializes a RZXGate calibration builder. + Initializes a RZXGate calibration builder. - Args: - backend: Backend for which to construct the gates. + Args: + inst_map: Instruction schedule map. + backend: Backend for which to construct the gates. + + Raises: + QiskitError: if open pulse is not supported by the backend. + """ - Raises: - QiskitError: if open pulse is not supported by the backend. - """ super().__init__() - if not backend.configuration().open_pulse: - raise QiskitError( - "Calibrations can only be added to Pulse-enabled backends, " - "but {} is not enabled with Pulse.".format(backend.name()) - ) - self._inst_map = backend.defaults().instruction_schedule_map - self._config = backend.configuration() - self._channel_map = backend.configuration().qubit_channel_mapping + self._inst_map = inst_map + + if backend is not None: + if not backend.configuration().open_pulse: + raise QiskitError( + "Calibrations can only be added to Pulse-enabled backends, " + "but {} is not enabled with Pulse.".format(backend.name()) + ) def supported(self, node_op: DAGNode) -> bool: """ From e00eeac9629a5e7ec93b913ba3beb3c0c511f76b Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 27 Jun 2021 12:01:59 +0200 Subject: [PATCH 50/70] Added tests for pulse-efficient transpiler pass and pass manager --- .../test_echo_rzx_weyl_decomposition.py | 67 +++-- .../test_pulse_efficient_passmanager.py | 228 ++++++++++++++++++ 2 files changed, 272 insertions(+), 23 deletions(-) create mode 100644 test/python/transpiler/test_pulse_efficient_passmanager.py diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 2276f7a775e5..b4706803f009 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -16,7 +16,6 @@ import numpy as np from qiskit import QuantumRegister, QuantumCircuit -from qiskit.transpiler import TranspilerError from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition from qiskit.converters import circuit_to_dag, dag_to_circuit @@ -34,19 +33,7 @@ class TestEchoRZXWeylDecomposition(QiskitTestCase): def setUp(self): super().setUp() self.backend = FakeParis() - - def test_coupling_error(self): - """Raise TranspilerError if qubits are not coupled on the hardware. - """ - qr = QuantumRegister(4, "qr") - circuit = QuantumCircuit(qr) - circuit.cx(qr[1], qr[3]) - dag = circuit_to_dag(circuit) - - pass_ = EchoRZXWeylDecomposition(self.backend) - - with self.assertRaises(TranspilerError): - pass_.run(dag) + self.inst_map = self.backend.defaults().instruction_schedule_map def test_native_weyl_decomposition(self): """The CX is in the hardware-native direction @@ -58,13 +45,30 @@ def test_native_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) unitary_after = qi.Operator(after).data self.assertTrue(np.allclose(unitary_circuit, unitary_after)) + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # check whether after circuit has correct number of rzx gates + expected_rzx_number = 0 + if not alpha == 0: + expected_rzx_number += 2 + if not beta == 0: + expected_rzx_number += 2 + if not gamma == 0: + expected_rzx_number += 2 + + circuit_rzx_number = QuantumCircuit.count_ops(after)['rzx'] + + self.assertEqual(expected_rzx_number, circuit_rzx_number) + def test_non_native_weyl_decomposition(self): """The RZZ is not in the hardware-native direction """ @@ -76,13 +80,30 @@ def test_non_native_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) unitary_after = qi.Operator(after).data self.assertTrue(np.allclose(unitary_circuit, unitary_after)) + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # check whether after circuit has correct number of rzx gates + expected_rzx_number = 0 + if not alpha == 0: + expected_rzx_number += 2 + if not beta == 0: + expected_rzx_number += 2 + if not gamma == 0: + expected_rzx_number += 2 + + circuit_rzx_number = QuantumCircuit.count_ops(after)['rzx'] + + self.assertEqual(expected_rzx_number, circuit_rzx_number) + def test_weyl_unitaries_random_circuit(self): """Weyl decomposition for random two-qubit circuit. """ @@ -111,7 +132,7 @@ def test_weyl_unitaries_random_circuit(self): unitary_circuit = qi.Operator(circuit).data dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) unitary_after = qi.Operator(after).data @@ -136,9 +157,9 @@ def test_weyl_parameters(self): gamma = TwoQubitWeylDecomposition(unitary_circuit).c # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) - rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).a - rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).b - rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).c + rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).a + rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).b + rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).c self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) @@ -157,9 +178,9 @@ def test_non_native_weyl_parameters(self): gamma = TwoQubitWeylDecomposition(unitary_circuit).c # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) - rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).a - rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).b - rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, backend=self.backend, qubit_pair=qubit_pair).c + rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).a + rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).b + rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).c self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py new file mode 100644 index 000000000000..5a4b44ca0e51 --- /dev/null +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -0,0 +1,228 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2017, 2019. +# +# 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. + +"""Tests pulse-efficient pass manager API""" + +import unittest + +from qiskit.converters import circuit_to_dag + +from qiskit import QuantumCircuit, QuantumRegister +from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition +from qiskit.compiler import transpile +from qiskit.test import QiskitTestCase +from qiskit.test.mock import ( + FakeParis, + FakeAthens, +) + +from math import pi +import numpy as np + +import qiskit.quantum_info as qi + + +def emptycircuit(): + """Empty circuit""" + return QuantumCircuit() + + +class TestPulseEfficientTranspilerPass(QiskitTestCase): + """Test the pulse-efficient pass manager""" + + def setUp(self): + super().setUp() + self.passes = [] + self.backends = (FakeParis(), FakeAthens()) + + def test_empty_circuit(self): + """Test empty circuit""" + + circuit = emptycircuit() + + unitary_circuit = qi.Operator(circuit).data + + result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + + unitary_result = qi.Operator(result).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + + def test_2q_circuit_transpilation(self): + """Test random two-qubit circuit""" + + theta = 0.2 + epsilon = pi / 3 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) + circuit.rzx(theta, qr[1], qr[0]) + circuit.sx(qr[1]) + circuit.rzz(epsilon, qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.h(qr[1]) + + unitary_circuit = qi.Operator(circuit).data + + result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + + unitary_result = qi.Operator(result).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + + def test_2q_circuit_rzx_number(self): + """Test correct number of rzx gates""" + + theta = -2 + epsilon = pi / 9 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[0], qr[1]) + circuit.rxx(theta, qr[1], qr[0]) + circuit.x(qr[1]) + circuit.cx(qr[1], qr[0]) + circuit.rzz(epsilon, qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + circuit.h(qr[1]) + + unitary_circuit = qi.Operator(circuit).data + + result = transpile(circuit, self.backends[1], optimization_level='pulse_efficient') + + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # check whether after circuit has correct number of rzx gates + expected_rzx_number = 0 + if not alpha == 0: + expected_rzx_number += 2 + if not beta == 0: + expected_rzx_number += 2 + if not gamma == 0: + expected_rzx_number += 2 + + circuit_rzx_number = QuantumCircuit.count_ops(result)['rzx'] + + self.assertEqual(expected_rzx_number, circuit_rzx_number) + + def test_alpha_beta_gamma(self): + """Check if the Weyl parameters match the expected ones for rzz""" + + theta = 1 + qr = QuantumRegister(2, "qr") + circuit = QuantumCircuit(qr) + circuit.rzz(theta, qr[1], qr[0]) + + result = transpile(circuit, self.backends[1], optimization_level='pulse_efficient') + + unitary_result = qi.Operator(result).data + + alpha = TwoQubitWeylDecomposition(unitary_result).a + beta = TwoQubitWeylDecomposition(unitary_result).b + gamma = TwoQubitWeylDecomposition(unitary_result).c + + self.assertEqual((alpha, beta, gamma), (0.5, 0, 0)) + + def test_5q_circuit_weyl_decomposition(self): + """Test random five-qubit circuit""" + + delta = 6 * pi / 5 + epsilon = pi / 3 + zeta = -2.1 + theta = 0.2 + qr = QuantumRegister(5, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) + circuit.rzx(theta, qr[1], qr[0]) + circuit.rzz(epsilon, qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.rzz(delta, qr[0], qr[1]) + circuit.swap(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.h(qr[1]) + circuit.rxx(zeta, qr[1], qr[0]) + circuit.rzz(theta, qr[0], qr[1]) + circuit.swap(qr[3], qr[2]) + circuit.cx(qr[1], qr[2]) + circuit.swap(qr[1], qr[4]) + circuit.h(qr[3]) + + unitary_circuit = qi.Operator(circuit).data + + result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + + unitary_result = qi.Operator(result).data + + self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + + def test_rzx_calibrations(self): + """Test whether there exist calibrations for rzx""" + + delta = 0.001 + theta = 3.8 + qr = QuantumRegister(5, "qr") + circuit = QuantumCircuit(qr) + circuit.rzz(delta, qr[0], qr[1]) + circuit.swap(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.s(qr[1]) + circuit.rzz(theta, qr[0], qr[1]) + circuit.swap(qr[3], qr[2]) + circuit.cx(qr[1], qr[2]) + circuit.sx(qr[4]) + circuit.swap(qr[1], qr[4]) + circuit.h(qr[3]) + + result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + + self.assertIn('rzx', result.calibrations) + + def test_params_values(self): + """Test whether absolute value of rzx angles is smaller than or equals pi""" + + delta = 8 * pi / 5 + epsilon = pi / 2 + zeta = -5.1 + theta = 0.02 + qr = QuantumRegister(5, "qr") + circuit = QuantumCircuit(qr) + circuit.cx(qr[1], qr[0]) + circuit.rzx(theta, qr[1], qr[0]) + circuit.rzz(epsilon, qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.rzz(delta, qr[0], qr[1]) + circuit.swap(qr[1], qr[0]) + circuit.cx(qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) + circuit.h(qr[1]) + circuit.rxx(zeta, qr[1], qr[0]) + circuit.rzz(theta, qr[0], qr[1]) + circuit.swap(qr[3], qr[2]) + circuit.cx(qr[1], qr[2]) + circuit.swap(qr[1], qr[4]) + circuit.h(qr[3]) + + result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + + after_dag = circuit_to_dag(result) + + for node in after_dag.nodes(): + if node.type == "op" and node.op.name == 'rzx': + params = node.op.params + self.assertTrue(abs(params[0]) <= np.pi) + + +if __name__ == "__main__": + unittest.main() From 3cbb40fd3f17f03b3417e29711ffd6a56dcf7249 Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 27 Jun 2021 12:13:59 +0200 Subject: [PATCH 51/70] black --- qiskit/compiler/transpiler.py | 6 ++- .../synthesis/two_qubit_decompose.py | 13 +++-- .../echo_rzx_weyl_decomposition.py | 24 +++++---- .../passes/scheduling/calibration_creators.py | 18 ++++--- qiskit/transpiler/passmanager_config.py | 2 +- .../preset_passmanagers/pulse_efficient.py | 18 ++++--- .../test_echo_rzx_weyl_decomposition.py | 52 ++++++++++++------- .../test_pulse_efficient_passmanager.py | 20 +++---- 8 files changed, 89 insertions(+), 64 deletions(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index c66b0515a3bf..81b898fb259b 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -357,10 +357,12 @@ def _transpile_circuit(circuit_config_tuple: Tuple[QuantumCircuit, Dict]) -> Qua pass_manager = level_2_pass_manager(pass_manager_config) elif level == 3: pass_manager = level_3_pass_manager(pass_manager_config) - elif level == 'pulse_efficient': + elif level == "pulse_efficient": pass_manager = pulse_efficient_pass_manager(pass_manager_config) else: - raise TranspilerError("optimization_level can range from 0 to 3, or can be 'pulse_efficient'.") + raise TranspilerError( + "optimization_level can range from 0 to 3, or can be 'pulse_efficient'." + ) result = pass_manager.run( circuit, callback=transpile_config["callback"], output_name=transpile_config["output_name"] diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index d258be2cb623..4891d1aa489f 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -129,9 +129,9 @@ def __init_subclass__(cls, **kwargs): Make explicitly-instantiated subclass __new__ call base __new__ with fidelity=None""" super().__init_subclass__(**kwargs) - cls.__new__ = lambda cls, *a, fidelity=None, inst_map=None, qubit_pair=None, **k: \ - TwoQubitWeylDecomposition.__new__(cls, *a, fidelity=None, inst_map=None, - qubit_pair=None, **k) + cls.__new__ = lambda cls, *a, fidelity=None, inst_map=None, qubit_pair=None, **k: TwoQubitWeylDecomposition.__new__( + cls, *a, fidelity=None, inst_map=None, qubit_pair=None, **k + ) @staticmethod def __new__(cls, unitary_matrix, *, fidelity=(1.0 - 1.0e-9), inst_map=None, qubit_pair=None): @@ -545,8 +545,7 @@ def specialize(self): class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): - """Decompose two-qubit unitary in terms of echoed cross-resonance gates. - """ + """Decompose two-qubit unitary in terms of echoed cross-resonance gates.""" def __init__(self, unitary, inst_map, qubit_pair: Tuple): self.inst_map = inst_map @@ -580,8 +579,8 @@ def _apply_reverse_rzx(circ: QuantumCircuit, angle: float): def is_native_cx(self, qubit_pair: Tuple) -> bool: """Check that a CX for a qubit pair is native.""" - cx1 = self.inst_map.get('cx', qubit_pair) - cx2 = self.inst_map.get('cx', qubit_pair[::-1]) + cx1 = self.inst_map.get("cx", qubit_pair) + cx2 = self.inst_map.get("cx", qubit_pair[::-1]) return cx1.duration < cx2.duration def _weyl_gate(self, simplify, circ: QuantumCircuit, atol): diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index a49ca791573c..e4da6c863f23 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -27,7 +27,7 @@ class EchoRZXWeylDecomposition(TransformationPass): """Rewrite two-qubit gates using the Weyl decomposition. - + This transpiler pass rewrites two-qubit gates in terms of echoed cross-resonance gates according to the Weyl decomposition. A two-qubit gate will be replaced with at most six non-echoed RZXGates. Each pair of RZXGates forms an echoed RZXGate. @@ -40,24 +40,26 @@ def __init__(self, inst_map): def run(self, dag): """Run the EchoRZXWeylDecomposition pass on `dag`. - + Rewrites two-qubit gates in an arbitrary circuit in terms of echoed cross-resonance gates by computing the Weyl decomposition of the corresponding unitary. Modifies the input dag. - + Args: dag (DAGCircuit): DAG to map. - + Returns: DAGCircuit: The rearranged dag. - + Raises: TranspilerError: If the circuit cannot be mapped. """ if len(dag.qregs) > 1: - raise TranspilerError('EchoRZXWeylDecomposition expects a single qreg input DAG,' - f'but input DAG had qregs: {dag.qregs}.') + raise TranspilerError( + "EchoRZXWeylDecomposition expects a single qreg input DAG," + f"but input DAG had qregs: {dag.qregs}." + ) trivial_layout = Layout.generate_trivial_layout(*dag.qregs.values()) @@ -77,9 +79,11 @@ def run(self, dag): qubit_pair = (physical_q0, physical_q1) unitary = qi.Operator(node.op).data - dag_weyl = circuit_to_dag(TwoQubitWeylEchoRZX(unitary, - inst_map=self.inst_map, - qubit_pair=qubit_pair).circuit()) + dag_weyl = circuit_to_dag( + TwoQubitWeylEchoRZX( + unitary, inst_map=self.inst_map, qubit_pair=qubit_pair + ).circuit() + ) dag.substitute_node_with_dag(node, dag_weyl) return dag diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 013c27478809..575a52907112 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -82,17 +82,19 @@ class RZXCalibrationBuilder(CalibrationCreator): angle. Additional details can be found in https://arxiv.org/abs/2012.11660. """ - def __init__(self, inst_map: Dict[str, Dict[Tuple[int], Schedule]], backend: Optional[BaseBackend] = None): + def __init__( + self, inst_map: Dict[str, Dict[Tuple[int], Schedule]], backend: Optional[BaseBackend] = None + ): """ - Initializes a RZXGate calibration builder. + Initializes a RZXGate calibration builder. - Args: - inst_map: Instruction schedule map. - backend: Backend for which to construct the gates. + Args: + inst_map: Instruction schedule map. + backend: Backend for which to construct the gates. - Raises: - QiskitError: if open pulse is not supported by the backend. - """ + Raises: + QiskitError: if open pulse is not supported by the backend. + """ super().__init__() diff --git a/qiskit/transpiler/passmanager_config.py b/qiskit/transpiler/passmanager_config.py index 456ce414f339..311cce90c145 100644 --- a/qiskit/transpiler/passmanager_config.py +++ b/qiskit/transpiler/passmanager_config.py @@ -29,7 +29,7 @@ def __init__( backend_properties=None, approximation_degree=None, seed_transpiler=None, - inst_map=None + inst_map=None, ): """Initialize a PassManagerConfig object diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index ad2c6c6ee630..b0b8fb8d285d 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -24,8 +24,12 @@ from qiskit.transpiler.passes import ConsolidateBlocks from qiskit.transpiler.passes.scheduling.calibration_creators import RZXCalibrationBuilderNoEcho -from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition -from qiskit.circuit.library.standard_gates.equivalence_library import StandardEquivalenceLibrary as std_eqlib +from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import ( + EchoRZXWeylDecomposition, +) +from qiskit.circuit.library.standard_gates.equivalence_library import ( + StandardEquivalenceLibrary as std_eqlib, +) from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions from qiskit.transpiler.passes import Optimize1qGatesDecomposition @@ -53,7 +57,7 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass # 1. Consolidate all consecutive two-qubit operations _collect_2q_blocks = Collect2qBlocks() - _consolidate_blocks = ConsolidateBlocks(basis_gates=['rz', 'sx', 'x', 'rxx']) + _consolidate_blocks = ConsolidateBlocks(basis_gates=["rz", "sx", "x", "rxx"]) # 2. Decompose two-qubit unitaries in terms of echoed RZX gates according to the Weyl decomposition _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(inst_map) @@ -62,10 +66,12 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass _rzx_calibrations = RZXCalibrationBuilderNoEcho(inst_map) # 4. Unroll to backend basis with rzx - basis_gates = list(set(basis_gates) | {'rzx'}) + basis_gates = list(set(basis_gates) | {"rzx"}) - _unroll = [UnrollCustomDefinitions(std_eqlib, basis_gates), - BasisTranslator(std_eqlib, basis_gates)] + _unroll = [ + UnrollCustomDefinitions(std_eqlib, basis_gates), + BasisTranslator(std_eqlib, basis_gates), + ] # 5. Optimize one-qubit decomposition _optimize_1q_decomposition = Optimize1qGatesDecomposition(basis_gates) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index b4706803f009..3d4de17b8277 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -17,14 +17,19 @@ from qiskit import QuantumRegister, QuantumCircuit -from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import EchoRZXWeylDecomposition +from qiskit.transpiler.passes.optimization.echo_rzx_weyl_decomposition import ( + EchoRZXWeylDecomposition, +) from qiskit.converters import circuit_to_dag, dag_to_circuit from qiskit.test import QiskitTestCase from qiskit.test.mock import FakeParis import qiskit.quantum_info as qi -from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitWeylDecomposition, TwoQubitWeylEchoRZX +from qiskit.quantum_info.synthesis.two_qubit_decompose import ( + TwoQubitWeylDecomposition, + TwoQubitWeylEchoRZX, +) class TestEchoRZXWeylDecomposition(QiskitTestCase): @@ -36,8 +41,7 @@ def setUp(self): self.inst_map = self.backend.defaults().instruction_schedule_map def test_native_weyl_decomposition(self): - """The CX is in the hardware-native direction - """ + """The CX is in the hardware-native direction""" qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) circuit.cx(qr[0], qr[1]) @@ -65,13 +69,12 @@ def test_native_weyl_decomposition(self): if not gamma == 0: expected_rzx_number += 2 - circuit_rzx_number = QuantumCircuit.count_ops(after)['rzx'] + circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] self.assertEqual(expected_rzx_number, circuit_rzx_number) def test_non_native_weyl_decomposition(self): - """The RZZ is not in the hardware-native direction - """ + """The RZZ is not in the hardware-native direction""" theta = pi / 9 qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) @@ -100,13 +103,12 @@ def test_non_native_weyl_decomposition(self): if not gamma == 0: expected_rzx_number += 2 - circuit_rzx_number = QuantumCircuit.count_ops(after)['rzx'] + circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] self.assertEqual(expected_rzx_number, circuit_rzx_number) def test_weyl_unitaries_random_circuit(self): - """Weyl decomposition for random two-qubit circuit. - """ + """Weyl decomposition for random two-qubit circuit.""" theta = pi / 9 epsilon = 5 delta = -1 @@ -140,8 +142,7 @@ def test_weyl_unitaries_random_circuit(self): self.assertTrue(np.allclose(unitary_circuit, unitary_after)) def test_weyl_parameters(self): - """Computation of the correct RZX Weyl parameters - """ + """Computation of the correct RZX Weyl parameters""" theta = pi / 3 qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) @@ -157,15 +158,20 @@ def test_weyl_parameters(self): gamma = TwoQubitWeylDecomposition(unitary_circuit).c # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) - rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).a - rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).b - rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).c + rzx_alpha = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).a + rzx_beta = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).b + rzx_gamma = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).c self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) def test_non_native_weyl_parameters(self): - """Weyl parameters for a non-hardware-native CX direction - """ + """Weyl parameters for a non-hardware-native CX direction""" qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) qubit_pair = (qr[1], qr[0]) @@ -178,9 +184,15 @@ def test_non_native_weyl_parameters(self): gamma = TwoQubitWeylDecomposition(unitary_circuit).c # RZX Weyl parameters (rzx_alpha, rzx_beta, rzx_gamma) - rzx_alpha = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).a - rzx_beta = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).b - rzx_gamma = TwoQubitWeylEchoRZX(unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair).c + rzx_alpha = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).a + rzx_beta = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).b + rzx_gamma = TwoQubitWeylEchoRZX( + unitary_circuit, inst_map=self.inst_map, qubit_pair=qubit_pair + ).c self.assertEqual((alpha, beta, gamma), (rzx_alpha, rzx_beta, rzx_gamma)) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index 5a4b44ca0e51..09caf2857571 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -51,7 +51,7 @@ def test_empty_circuit(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -73,7 +73,7 @@ def test_2q_circuit_transpilation(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -96,7 +96,7 @@ def test_2q_circuit_rzx_number(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[1], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[1], optimization_level="pulse_efficient") alpha = TwoQubitWeylDecomposition(unitary_circuit).a beta = TwoQubitWeylDecomposition(unitary_circuit).b @@ -111,7 +111,7 @@ def test_2q_circuit_rzx_number(self): if not gamma == 0: expected_rzx_number += 2 - circuit_rzx_number = QuantumCircuit.count_ops(result)['rzx'] + circuit_rzx_number = QuantumCircuit.count_ops(result)["rzx"] self.assertEqual(expected_rzx_number, circuit_rzx_number) @@ -123,7 +123,7 @@ def test_alpha_beta_gamma(self): circuit = QuantumCircuit(qr) circuit.rzz(theta, qr[1], qr[0]) - result = transpile(circuit, self.backends[1], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[1], optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -160,7 +160,7 @@ def test_5q_circuit_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -185,9 +185,9 @@ def test_rzx_calibrations(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") - self.assertIn('rzx', result.calibrations) + self.assertIn("rzx", result.calibrations) def test_params_values(self): """Test whether absolute value of rzx angles is smaller than or equals pi""" @@ -214,12 +214,12 @@ def test_params_values(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, self.backends[0], optimization_level='pulse_efficient') + result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") after_dag = circuit_to_dag(result) for node in after_dag.nodes(): - if node.type == "op" and node.op.name == 'rzx': + if node.type == "op" and node.op.name == "rzx": params = node.op.params self.assertTrue(abs(params[0]) <= np.pi) From 81cd58dd61b68f56b17fbd470f856cb5738ccd6e Mon Sep 17 00:00:00 2001 From: catornow Date: Sun, 27 Jun 2021 14:58:01 +0200 Subject: [PATCH 52/70] Minor change --- qiskit/compiler/transpiler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/compiler/transpiler.py b/qiskit/compiler/transpiler.py index 81b898fb259b..40c5921f9ef8 100644 --- a/qiskit/compiler/transpiler.py +++ b/qiskit/compiler/transpiler.py @@ -188,7 +188,7 @@ def callback_func(**kwargs): output_name: A list with strings to identify the output circuits. The length of the list should be exactly the length of the ``circuits`` parameter. - inst_map: ... + inst_map: Instruction schedule map. Returns: The transpiled circuit(s). From a8f9ea5d5442e0fd9f32303cccef4a0928c1e7c2 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 09:59:33 +0200 Subject: [PATCH 53/70] Update test/python/transpiler/test_echo_rzx_weyl_decomposition.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 3d4de17b8277..20385e65aa6d 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017, 2021. # # 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 From 5d9d582936d3491805e5aa5187b40c24e409c826 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 09:59:54 +0200 Subject: [PATCH 54/70] Update test/python/transpiler/test_echo_rzx_weyl_decomposition.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_echo_rzx_weyl_decomposition.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 20385e65aa6d..3fec038e0cf8 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -48,9 +48,7 @@ def test_native_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data - dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.inst_map) - after = dag_to_circuit(pass_.run(dag)) + after = EchoRZXWeylDecomposition(self.inst_map)(circuit) unitary_after = qi.Operator(after).data From 4c8597722292e8ab3ded605ab235d5522499021c Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:00:52 +0200 Subject: [PATCH 55/70] Update test/python/transpiler/test_echo_rzx_weyl_decomposition.py Co-authored-by: Luciano Bello --- .../test_echo_rzx_weyl_decomposition.py | 35 ++++++++++--------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 3fec038e0cf8..052a896be546 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -88,22 +88,25 @@ def test_non_native_weyl_decomposition(self): self.assertTrue(np.allclose(unitary_circuit, unitary_after)) - alpha = TwoQubitWeylDecomposition(unitary_circuit).a - beta = TwoQubitWeylDecomposition(unitary_circuit).b - gamma = TwoQubitWeylDecomposition(unitary_circuit).c - - # check whether after circuit has correct number of rzx gates - expected_rzx_number = 0 - if not alpha == 0: - expected_rzx_number += 2 - if not beta == 0: - expected_rzx_number += 2 - if not gamma == 0: - expected_rzx_number += 2 - - circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] - - self.assertEqual(expected_rzx_number, circuit_rzx_number) + self.assertRZXgates(unitary_circuit) + + def assertRZXgates(unitary_circuit): + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # check whether after circuit has correct number of rzx gates + expected_rzx_number = 0 + if not alpha == 0: + expected_rzx_number += 2 + if not beta == 0: + expected_rzx_number += 2 + if not gamma == 0: + expected_rzx_number += 2 + + circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] + + self.assertEqual(expected_rzx_number, circuit_rzx_number) def test_weyl_unitaries_random_circuit(self): """Weyl decomposition for random two-qubit circuit.""" From 70d5c52965b0433e0bfa5af07c4cb00046add6eb Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:01:08 +0200 Subject: [PATCH 56/70] Update test/python/transpiler/test_echo_rzx_weyl_decomposition.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 052a896be546..06960df76cb8 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -67,7 +67,7 @@ def test_native_weyl_decomposition(self): if not gamma == 0: expected_rzx_number += 2 - circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] + circuit_rzx_number = after.count_ops()["rzx"] self.assertEqual(expected_rzx_number, circuit_rzx_number) From b0f8700dd49b0bf541db69f1b7ce5a43cd903e20 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:03:02 +0200 Subject: [PATCH 57/70] Update test/python/transpiler/test_pulse_efficient_passmanager.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_pulse_efficient_passmanager.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index 09caf2857571..b6fa827f24b5 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -31,10 +31,6 @@ import qiskit.quantum_info as qi -def emptycircuit(): - """Empty circuit""" - return QuantumCircuit() - class TestPulseEfficientTranspilerPass(QiskitTestCase): """Test the pulse-efficient pass manager""" @@ -47,7 +43,7 @@ def setUp(self): def test_empty_circuit(self): """Test empty circuit""" - circuit = emptycircuit() + circuit = QuantumCircuit() unitary_circuit = qi.Operator(circuit).data From 7f4b2dfab6def6fe0866b661de113e1ff7b01568 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:04:58 +0200 Subject: [PATCH 58/70] Update qiskit/transpiler/preset_passmanagers/pulse_efficient.py Co-authored-by: Luciano Bello --- .../preset_passmanagers/pulse_efficient.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index b0b8fb8d285d..a296e738a54c 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -77,11 +77,11 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass _optimize_1q_decomposition = Optimize1qGatesDecomposition(basis_gates) # Build pass manager - pm0 = PassManager() - pm0.append(_collect_2q_blocks) - pm0.append(_consolidate_blocks) - pm0.append(_echo_rzx_weyl_decomposition) - pm0.append(_rzx_calibrations) - pm0.append(_unroll) - pm0.append(_optimize_1q_decomposition) - return pm0 + pm = PassManager() + pm.append(_collect_2q_blocks) + pm.append(_consolidate_blocks) + pm.append(_echo_rzx_weyl_decomposition) + pm.append(_rzx_calibrations) + pm.append(_unroll) + pm.append(_optimize_1q_decomposition) + return pm From b79aed79d571f2e2db420944b6425309e4522d06 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:05:55 +0200 Subject: [PATCH 59/70] Update test/python/transpiler/test_pulse_efficient_passmanager.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_pulse_efficient_passmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index b6fa827f24b5..28dbe6c8491e 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2019. +# (C) Copyright IBM 2017, 2021. # # 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 From 17ea8a85c0d5d44bf65e4a82194270653363213c Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:06:09 +0200 Subject: [PATCH 60/70] Update test/python/transpiler/test_pulse_efficient_passmanager.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_pulse_efficient_passmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index 28dbe6c8491e..8bb4c5376255 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -32,7 +32,7 @@ -class TestPulseEfficientTranspilerPass(QiskitTestCase): +class TestPulseEfficientPassManager(QiskitTestCase): """Test the pulse-efficient pass manager""" def setUp(self): From 261ba126567142369815a30f45c2702fcedc4609 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:07:11 +0200 Subject: [PATCH 61/70] Update test/python/transpiler/test_pulse_efficient_passmanager.py Co-authored-by: Luciano Bello --- test/python/transpiler/test_pulse_efficient_passmanager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index 8bb4c5376255..815f53bf5191 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -47,7 +47,7 @@ def test_empty_circuit(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data From a34a2973aa0b8b1e8aa9183009037106ce676db8 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:07:58 +0200 Subject: [PATCH 62/70] Update qiskit/transpiler/preset_passmanagers/pulse_efficient.py Co-authored-by: Luciano Bello --- qiskit/transpiler/preset_passmanagers/pulse_efficient.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index a296e738a54c..0618d7674f17 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -1,6 +1,6 @@ # This code is part of Qiskit. # -# (C) Copyright IBM 2017, 2018. +# (C) Copyright IBM 2017, 2021. # # 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 From f0e03c43cfaa68846d81fc1df74735ee0de29a75 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 10:08:33 +0200 Subject: [PATCH 63/70] Update qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py Co-authored-by: Luciano Bello --- .../passes/optimization/echo_rzx_weyl_decomposition.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index e4da6c863f23..43f285316ff3 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -34,8 +34,8 @@ class EchoRZXWeylDecomposition(TransformationPass): """ def __init__(self, inst_map): - self.inst_map = inst_map """EchoRZXWeylDecomposition pass.""" + self.inst_map = inst_map super().__init__() def run(self, dag): From 598f7ec453abb927a144fcee9ea8ad05f9df7deb Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 28 Jun 2021 11:35:37 +0200 Subject: [PATCH 64/70] Implement suggestions of Luciano --- .../echo_rzx_weyl_decomposition.py | 5 --- .../test_echo_rzx_weyl_decomposition.py | 34 +++++++++---------- .../test_pulse_efficient_passmanager.py | 15 ++++---- 3 files changed, 23 insertions(+), 31 deletions(-) diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 43f285316ff3..149f2f0f3771 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -71,11 +71,6 @@ def run(self, dag): physical_q0 = trivial_layout[control] physical_q1 = trivial_layout[target] - # config = self.backend.configuration() - # if [physical_q0, physical_q1] not in config.coupling_map: - # raise TranspilerError('Qubits %s and %s are not connected on the backend' - # % (physical_q0, physical_q1)) - qubit_pair = (physical_q0, physical_q1) unitary = qi.Operator(node.op).data diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index 06960df76cb8..b22bc00fa243 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -88,25 +88,25 @@ def test_non_native_weyl_decomposition(self): self.assertTrue(np.allclose(unitary_circuit, unitary_after)) - self.assertRZXgates(unitary_circuit) + self.assertRZXgates(unitary_circuit, after) - def assertRZXgates(unitary_circuit): - alpha = TwoQubitWeylDecomposition(unitary_circuit).a - beta = TwoQubitWeylDecomposition(unitary_circuit).b - gamma = TwoQubitWeylDecomposition(unitary_circuit).c - - # check whether after circuit has correct number of rzx gates - expected_rzx_number = 0 - if not alpha == 0: - expected_rzx_number += 2 - if not beta == 0: - expected_rzx_number += 2 - if not gamma == 0: - expected_rzx_number += 2 - - circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] + def assertRZXgates(self, unitary_circuit, after): + alpha = TwoQubitWeylDecomposition(unitary_circuit).a + beta = TwoQubitWeylDecomposition(unitary_circuit).b + gamma = TwoQubitWeylDecomposition(unitary_circuit).c + + # check whether after circuit has correct number of rzx gates + expected_rzx_number = 0 + if not alpha == 0: + expected_rzx_number += 2 + if not beta == 0: + expected_rzx_number += 2 + if not gamma == 0: + expected_rzx_number += 2 + + circuit_rzx_number = QuantumCircuit.count_ops(after)["rzx"] - self.assertEqual(expected_rzx_number, circuit_rzx_number) + self.assertEqual(expected_rzx_number, circuit_rzx_number) def test_weyl_unitaries_random_circuit(self): """Weyl decomposition for random two-qubit circuit.""" diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index 815f53bf5191..f44079bc8af8 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -31,14 +31,11 @@ import qiskit.quantum_info as qi - class TestPulseEfficientPassManager(QiskitTestCase): """Test the pulse-efficient pass manager""" def setUp(self): super().setUp() - self.passes = [] - self.backends = (FakeParis(), FakeAthens()) def test_empty_circuit(self): """Test empty circuit""" @@ -69,7 +66,7 @@ def test_2q_circuit_transpilation(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -92,7 +89,7 @@ def test_2q_circuit_rzx_number(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[1], optimization_level="pulse_efficient") + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient") alpha = TwoQubitWeylDecomposition(unitary_circuit).a beta = TwoQubitWeylDecomposition(unitary_circuit).b @@ -119,7 +116,7 @@ def test_alpha_beta_gamma(self): circuit = QuantumCircuit(qr) circuit.rzz(theta, qr[1], qr[0]) - result = transpile(circuit, self.backends[1], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -156,7 +153,7 @@ def test_5q_circuit_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") unitary_result = qi.Operator(result).data @@ -181,7 +178,7 @@ def test_rzx_calibrations(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") self.assertIn("rzx", result.calibrations) @@ -210,7 +207,7 @@ def test_params_values(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, self.backends[0], optimization_level="pulse_efficient") + result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") after_dag = circuit_to_dag(result) From 2f675ed4f858d43f7febf35b0e22b6fc034718b6 Mon Sep 17 00:00:00 2001 From: catornow Date: Mon, 28 Jun 2021 14:06:40 +0200 Subject: [PATCH 65/70] Included optimization_level 3 in pulse efficient pass manager --- .../passes/scheduling/calibration_creators.py | 13 +- .../preset_passmanagers/pulse_efficient.py | 11 +- .../test_pulse_efficient_passmanager.py | 142 +++++++++++------- 3 files changed, 102 insertions(+), 64 deletions(-) diff --git a/qiskit/transpiler/passes/scheduling/calibration_creators.py b/qiskit/transpiler/passes/scheduling/calibration_creators.py index 575a52907112..94278ffc9381 100644 --- a/qiskit/transpiler/passes/scheduling/calibration_creators.py +++ b/qiskit/transpiler/passes/scheduling/calibration_creators.py @@ -83,7 +83,8 @@ class RZXCalibrationBuilder(CalibrationCreator): """ def __init__( - self, inst_map: Dict[str, Dict[Tuple[int], Schedule]], backend: Optional[BaseBackend] = None + self, backend: Optional[BaseBackend] = None, + inst_map: Optional[Dict[str, Dict[Tuple[int], Schedule]]] = None ): """ Initializes a RZXGate calibration builder. @@ -98,14 +99,20 @@ def __init__( super().__init__() - self._inst_map = inst_map - if backend is not None: + self._inst_map = backend.defaults().instruction_schedule_map if not backend.configuration().open_pulse: raise QiskitError( "Calibrations can only be added to Pulse-enabled backends, " "but {} is not enabled with Pulse.".format(backend.name()) ) + elif inst_map is not None: + self._inst_map = inst_map + else: + raise QiskitError( + "Either a backend or an instruction schedule map must be specified.") + + # self._inst_map = inst_map def supported(self, node_op: DAGNode) -> bool: """ diff --git a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py index 0618d7674f17..ebc25e02773b 100644 --- a/qiskit/transpiler/preset_passmanagers/pulse_efficient.py +++ b/qiskit/transpiler/preset_passmanagers/pulse_efficient.py @@ -33,6 +33,10 @@ from qiskit.transpiler.passes.basis import BasisTranslator, UnrollCustomDefinitions from qiskit.transpiler.passes import Optimize1qGatesDecomposition +from qiskit.transpiler.preset_passmanagers.level3 import level_3_pass_manager + +from qiskit.transpiler.preset_passmanagers.level0 import level_0_pass_manager + def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> PassManager: """Pulse-efficient pass manager. @@ -60,10 +64,10 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass _consolidate_blocks = ConsolidateBlocks(basis_gates=["rz", "sx", "x", "rxx"]) # 2. Decompose two-qubit unitaries in terms of echoed RZX gates according to the Weyl decomposition - _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(inst_map) + _echo_rzx_weyl_decomposition = EchoRZXWeylDecomposition(inst_map=inst_map) # 3. Add calibrations - _rzx_calibrations = RZXCalibrationBuilderNoEcho(inst_map) + _rzx_calibrations = RZXCalibrationBuilderNoEcho(inst_map=inst_map) # 4. Unroll to backend basis with rzx basis_gates = list(set(basis_gates) | {"rzx"}) @@ -77,7 +81,8 @@ def pulse_efficient_pass_manager(pass_manager_config: PassManagerConfig) -> Pass _optimize_1q_decomposition = Optimize1qGatesDecomposition(basis_gates) # Build pass manager - pm = PassManager() + # pm = PassManager() + pm = level_3_pass_manager(pass_manager_config) pm.append(_collect_2q_blocks) pm.append(_consolidate_blocks) pm.append(_echo_rzx_weyl_decomposition) diff --git a/test/python/transpiler/test_pulse_efficient_passmanager.py b/test/python/transpiler/test_pulse_efficient_passmanager.py index f44079bc8af8..5834cf164d44 100644 --- a/test/python/transpiler/test_pulse_efficient_passmanager.py +++ b/test/python/transpiler/test_pulse_efficient_passmanager.py @@ -44,18 +44,22 @@ def test_empty_circuit(self): unitary_circuit = qi.Operator(circuit).data - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=100) + + transpile_level_3 = transpile(circuit, FakeAthens(), optimization_level=3, seed_transpiler=100) unitary_result = qi.Operator(result).data - self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + unitary_3 = qi.Operator(transpile_level_3).data + + self.assertTrue(np.allclose(unitary_3, unitary_result)) def test_2q_circuit_transpilation(self): """Test random two-qubit circuit""" theta = 0.2 epsilon = pi / 3 - qr = QuantumRegister(2, "qr") + qr = QuantumRegister(5, "qr") circuit = QuantumCircuit(qr) circuit.cx(qr[1], qr[0]) circuit.rzx(theta, qr[1], qr[0]) @@ -64,68 +68,88 @@ def test_2q_circuit_transpilation(self): circuit.swap(qr[1], qr[0]) circuit.h(qr[1]) - unitary_circuit = qi.Operator(circuit).data + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=100) - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") + transpile_level_3 = transpile(circuit, FakeAthens(), optimization_level=3, seed_transpiler=100) unitary_result = qi.Operator(result).data - self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + unitary_3 = qi.Operator(transpile_level_3).data + + self.assertTrue(np.allclose(unitary_3, unitary_result)) - def test_2q_circuit_rzx_number(self): - """Test correct number of rzx gates""" + def test_correct_basis(self): + """Test random two-qubit circuit""" - theta = -2 - epsilon = pi / 9 + theta = 0.2 + epsilon = pi / 3 qr = QuantumRegister(2, "qr") circuit = QuantumCircuit(qr) - circuit.cx(qr[0], qr[1]) - circuit.rxx(theta, qr[1], qr[0]) - circuit.x(qr[1]) circuit.cx(qr[1], qr[0]) + circuit.rzx(theta, qr[1], qr[0]) + circuit.sx(qr[1]) circuit.rzz(epsilon, qr[1], qr[0]) - circuit.cx(qr[1], qr[0]) + circuit.swap(qr[1], qr[0]) circuit.h(qr[1]) - unitary_circuit = qi.Operator(circuit).data - - result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient") - - alpha = TwoQubitWeylDecomposition(unitary_circuit).a - beta = TwoQubitWeylDecomposition(unitary_circuit).b - gamma = TwoQubitWeylDecomposition(unitary_circuit).c - - # check whether after circuit has correct number of rzx gates - expected_rzx_number = 0 - if not alpha == 0: - expected_rzx_number += 2 - if not beta == 0: - expected_rzx_number += 2 - if not gamma == 0: - expected_rzx_number += 2 - - circuit_rzx_number = QuantumCircuit.count_ops(result)["rzx"] - - self.assertEqual(expected_rzx_number, circuit_rzx_number) - - def test_alpha_beta_gamma(self): - """Check if the Weyl parameters match the expected ones for rzz""" - - theta = 1 - qr = QuantumRegister(2, "qr") - circuit = QuantumCircuit(qr) - circuit.rzz(theta, qr[1], qr[0]) - - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") - - unitary_result = qi.Operator(result).data - - alpha = TwoQubitWeylDecomposition(unitary_result).a - beta = TwoQubitWeylDecomposition(unitary_result).b - gamma = TwoQubitWeylDecomposition(unitary_result).c - - self.assertEqual((alpha, beta, gamma), (0.5, 0, 0)) - + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=100) + + self.assertEqual(set(result.count_ops().keys()), {'rz', 'rzx', 'sx', 'x'}) + + # def test_2q_circuit_rzx_number(self): + # """Test correct number of rzx gates""" + # + # theta = -2 + # epsilon = pi / 9 + # qr = QuantumRegister(2, "qr") + # circuit = QuantumCircuit(qr) + # circuit.cx(qr[0], qr[1]) + # circuit.rxx(theta, qr[1], qr[0]) + # circuit.x(qr[1]) + # circuit.cx(qr[1], qr[0]) + # circuit.rzz(epsilon, qr[1], qr[0]) + # circuit.cx(qr[1], qr[0]) + # circuit.h(qr[1]) + # + # unitary_circuit = qi.Operator(circuit).data + # + # result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=10) + # + # alpha = TwoQubitWeylDecomposition(unitary_circuit).a + # beta = TwoQubitWeylDecomposition(unitary_circuit).b + # gamma = TwoQubitWeylDecomposition(unitary_circuit).c + # + # # check whether after circuit has correct number of rzx gates + # expected_rzx_number = 0 + # if not alpha == 0: + # expected_rzx_number += 2 + # if not beta == 0: + # expected_rzx_number += 2 + # if not gamma == 0: + # expected_rzx_number += 2 + # + # circuit_rzx_number = QuantumCircuit.count_ops(result)["rzx"] + # + # self.assertEqual(expected_rzx_number, circuit_rzx_number) + + # def test_alpha_beta_gamma(self): + # """Check if the Weyl parameters match the expected ones for rzz""" + # + # theta = 1 + # qr = QuantumRegister(2, "qr") + # circuit = QuantumCircuit(qr) + # circuit.rzz(theta, qr[1], qr[0]) + # + # result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=10) + # + # unitary_result = qi.Operator(result).data + # + # alpha = TwoQubitWeylDecomposition(unitary_result).a + # beta = TwoQubitWeylDecomposition(unitary_result).b + # gamma = TwoQubitWeylDecomposition(unitary_result).c + # + # self.assertEqual((alpha, beta, gamma), (0.5, 0, 0)) + # def test_5q_circuit_weyl_decomposition(self): """Test random five-qubit circuit""" @@ -151,13 +175,15 @@ def test_5q_circuit_weyl_decomposition(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - unitary_circuit = qi.Operator(circuit).data + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=10) - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") + transpile_level_3 = transpile(circuit, FakeAthens(), optimization_level=3, seed_transpiler=100) unitary_result = qi.Operator(result).data - self.assertTrue(np.allclose(unitary_circuit, unitary_result)) + unitary_3 = qi.Operator(transpile_level_3).data + + self.assertTrue(np.allclose(unitary_3, unitary_result)) def test_rzx_calibrations(self): """Test whether there exist calibrations for rzx""" @@ -178,7 +204,7 @@ def test_rzx_calibrations(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=10) self.assertIn("rzx", result.calibrations) @@ -207,7 +233,7 @@ def test_params_values(self): circuit.swap(qr[1], qr[4]) circuit.h(qr[3]) - result = transpile(circuit, FakeParis(), optimization_level="pulse_efficient") + result = transpile(circuit, FakeAthens(), optimization_level="pulse_efficient", seed_transpiler=10) after_dag = circuit_to_dag(result) From 6b54311299be04fd08d0e6d8ad88685cf674572b Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:08:53 +0200 Subject: [PATCH 66/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 4891d1aa489f..8a29cfc38b32 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -575,7 +575,6 @@ def _apply_reverse_rzx(circ: QuantumCircuit, angle: float): circ.x(1) circ.h(0) circ.h(1) - return circ def is_native_cx(self, qubit_pair: Tuple) -> bool: """Check that a CX for a qubit pair is native.""" From 7f909addbe47cf58be8243a57ff1149b157edc80 Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:09:19 +0200 Subject: [PATCH 67/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 8a29cfc38b32..bfc70fa0df83 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -562,7 +562,6 @@ def _apply_rzx(circ: QuantumCircuit, angle: float): circ.x(0) circ.rzx(angle, 0, 1) circ.x(0) - return circ @staticmethod def _apply_reverse_rzx(circ: QuantumCircuit, angle: float): From fa5e96107b960f61e0cf5237f256dd925be5a56b Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:09:29 +0200 Subject: [PATCH 68/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index bfc70fa0df83..8d2e46b55b02 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -544,7 +544,7 @@ def specialize(self): self.K2r = np.asarray(RYGate(k2rtheta)) @ np.asarray(RXGate(k2rlambda)) -class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): +class TwoQubitWeylEchoRZX(TwoQubitWeylGeneral): """Decompose two-qubit unitary in terms of echoed cross-resonance gates.""" def __init__(self, unitary, inst_map, qubit_pair: Tuple): From 45ad4175059e6393b8cc0dcc1d2da67eef027d9e Mon Sep 17 00:00:00 2001 From: catornow <79633854+catornow@users.noreply.github.com> Date: Mon, 28 Jun 2021 14:09:39 +0200 Subject: [PATCH 69/70] Update qiskit/quantum_info/synthesis/two_qubit_decompose.py Co-authored-by: Daniel Egger <38065505+eggerdj@users.noreply.github.com> --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 8d2e46b55b02..7bb5ec5453da 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -552,8 +552,6 @@ def __init__(self, unitary, inst_map, qubit_pair: Tuple): self.qubit_pair = qubit_pair super().__init__(unitary) - def specialize(self): - pass @staticmethod def _apply_rzx(circ: QuantumCircuit, angle: float): From 48a3a53e94abe298ab5315899b6e4c482c11adbe Mon Sep 17 00:00:00 2001 From: catornow Date: Wed, 21 Jul 2021 10:29:48 +0200 Subject: [PATCH 70/70] Added function specialize in class TwoQubitWeylEchoRZX again --- qiskit/quantum_info/synthesis/two_qubit_decompose.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qiskit/quantum_info/synthesis/two_qubit_decompose.py b/qiskit/quantum_info/synthesis/two_qubit_decompose.py index 7bb5ec5453da..9d22ece73947 100644 --- a/qiskit/quantum_info/synthesis/two_qubit_decompose.py +++ b/qiskit/quantum_info/synthesis/two_qubit_decompose.py @@ -544,7 +544,7 @@ def specialize(self): self.K2r = np.asarray(RYGate(k2rtheta)) @ np.asarray(RXGate(k2rlambda)) -class TwoQubitWeylEchoRZX(TwoQubitWeylGeneral): +class TwoQubitWeylEchoRZX(TwoQubitWeylDecomposition): """Decompose two-qubit unitary in terms of echoed cross-resonance gates.""" def __init__(self, unitary, inst_map, qubit_pair: Tuple): @@ -552,6 +552,8 @@ def __init__(self, unitary, inst_map, qubit_pair: Tuple): self.qubit_pair = qubit_pair super().__init__(unitary) + def specialize(self): + pass # Nothing to do @staticmethod def _apply_rzx(circ: QuantumCircuit, angle: float):