From 6b6c68318aa74a4a44b375b62daf5d1bcc687994 Mon Sep 17 00:00:00 2001 From: Matthew Treinish Date: Wed, 1 Dec 2021 12:57:04 -0500 Subject: [PATCH] Fix arguments for RZXCalibrationBuilder and EchoRZXWeylDecomposition (#7331) * Fix arguments for RZXCalibrationBuilder and EchoRZXWeylDecomposition The RZXCalibrationBuilder and EchoRZXWeylDecomposition transpiler passes were previously taking BaseBackend instances as arguments. Besides that being a deprecated class which will be removed soon, it also is incorrect because backend objects are not guaranteed to be pickleable so when a pass manager runs in parallel processes this will cause an error. This was never caught because these passes aren't part of any default pass managers and their tests don't include running them as part of transpile(). To fix this passes typically take the properties of a backend they require. This is being reworked for BackendV2 in #5885 so a target object can be used to encapsulate the model of a backend so we have a single data structure to pass around, but until that is the minimum backend version we need to also support taking the individual components for BackendV1 and BaseBackend. This commit changes the pass constructors to take only use the parameters from the backend object used in the pass internals and stop requiring a backend object be used to construct an instance of either pass. * Deprecate backend for RZXCalibrationBuilder instead of removing --- .../transpiler/passes/calibration/builders.py | 49 +++++++++++++++---- .../echo_rzx_weyl_decomposition.py | 6 +-- ...ackend-rzx-cal-build-8eda1526725d7e7d.yaml | 46 +++++++++++++++++ test/python/pulse/test_calibrationbuilder.py | 5 +- .../test_echo_rzx_weyl_decomposition.py | 15 +++--- 5 files changed, 99 insertions(+), 22 deletions(-) create mode 100644 releasenotes/notes/deprecate-backend-rzx-cal-build-8eda1526725d7e7d.yaml diff --git a/qiskit/transpiler/passes/calibration/builders.py b/qiskit/transpiler/passes/calibration/builders.py index 9c328bd9dd8f..966916e384ce 100644 --- a/qiskit/transpiler/passes/calibration/builders.py +++ b/qiskit/transpiler/passes/calibration/builders.py @@ -14,15 +14,17 @@ from abc import abstractmethod from typing import List, Union +import warnings import math import numpy as np +from qiskit.providers.basebackend import BaseBackend +from qiskit.providers.backend import BackendV1 from qiskit.circuit import Instruction as CircuitInst from qiskit.circuit.library.standard_gates import RZXGate from qiskit.dagcircuit import DAGCircuit from qiskit.exceptions import QiskitError -from qiskit.providers import basebackend from qiskit.pulse import ( Play, Delay, @@ -103,26 +105,53 @@ class RZXCalibrationBuilder(CalibrationBuilder): angle. Additional details can be found in https://arxiv.org/abs/2012.11660. """ - def __init__(self, backend: basebackend): + def __init__( + self, + backend: Union[BaseBackend, BackendV1] = None, + instruction_schedule_map: InstructionScheduleMap = None, + qubit_channel_mapping: List[List[str]] = None, + ): """ Initializes a RZXGate calibration builder. Args: - backend: Backend for which to construct the gates. + backend: DEPRECATED a backend object to build the calibrations for. + Use of this argument is deprecated in favor of directly + specifying ``instruction_schedule_map`` and + ``qubit_channel_map``. + instruction_schedule_map: The :obj:`InstructionScheduleMap` object representing the + default pulse calibrations for the target backend + qubit_channel_mapping: The list mapping qubit indices to the list of + channel names that apply on that qubit. Raises: QiskitError: if open pulse is not supported by the backend. """ super().__init__() - if 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()) + if backend is not None: + warnings.warn( + "Passing a backend object directly to this pass (either as the first positional " + "argument or as the named 'backend' kwarg is deprecated and will no long be " + "supported in a future release. Instead use the instruction_schedule_map and " + "qubit_channel_mapping kwargs.", + DeprecationWarning, + stacklevel=2, ) - self._inst_map = backend.defaults().instruction_schedule_map - self._config = backend.configuration() - self._channel_map = backend.configuration().qubit_channel_mapping + 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._channel_map = backend.configuration().qubit_channel_mapping + + else: + if instruction_schedule_map is None or qubit_channel_mapping is None: + raise QiskitError("Calibrations can only be added to Pulse-enabled backends") + + self._inst_map = instruction_schedule_map + self._channel_map = qubit_channel_mapping def supported(self, node_op: CircuitInst, qubits: List) -> bool: """Determine if a given node supports the calibration. diff --git a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py index 76e0ecc13ac9..2b896fe5d351 100644 --- a/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py +++ b/qiskit/transpiler/passes/optimization/echo_rzx_weyl_decomposition.py @@ -24,8 +24,6 @@ from qiskit.dagcircuit import DAGCircuit from qiskit.converters import circuit_to_dag -from qiskit.providers import basebackend - import qiskit.quantum_info as qi from qiskit.quantum_info.synthesis.two_qubit_decompose import TwoQubitControlledUDecomposer @@ -38,10 +36,10 @@ class EchoRZXWeylDecomposition(TransformationPass): Each pair of RZXGates forms an echoed RZXGate. """ - def __init__(self, backend: basebackend): + def __init__(self, instruction_schedule_map: "InstructionScheduleMap"): """EchoRZXWeylDecomposition pass.""" - self._inst_map = backend.defaults().instruction_schedule_map super().__init__() + self._inst_map = instruction_schedule_map def _is_native(self, qubit_pair: Tuple) -> bool: """Return the direction of the qubit pair that is native, i.e. with the shortest schedule.""" diff --git a/releasenotes/notes/deprecate-backend-rzx-cal-build-8eda1526725d7e7d.yaml b/releasenotes/notes/deprecate-backend-rzx-cal-build-8eda1526725d7e7d.yaml new file mode 100644 index 000000000000..a669f40f610d --- /dev/null +++ b/releasenotes/notes/deprecate-backend-rzx-cal-build-8eda1526725d7e7d.yaml @@ -0,0 +1,46 @@ +--- +features: + - | + The constructor of :class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` + has two new kwargs ``instruction_schedule_map`` and ``qubit_channel_mapping`` + which take a :class:`~qiskit.pulse.InstructionScheduleMap` and list of + channel name lists for each qubit respectively. These new arguments are used + to directly specify the information needed from a backend target. They should + be used instead of passing a :class:`~qiskit.providers.BaseBackend` or + :class:`~qiskit.providers.BackendV1` object directly to the pass with the + ``backend`` argument. + +deprecations: + - | + For the constructor of the + :class:`~qiskit.transpiler.passes.RZXCalibrationBuilder` passing a backend + either as the first positional argument or with the named ``backend`` kwarg + is deprecated and will no longer work in a future release. Instead a + a :class:`~qiskit.pulse.InstructionScheduleMap` should be passed directly to + the ``instruction_schedule_map`` kwarg and a list of channel name lists for + each qubit should be passed directly to ``qubit_channel_mapping``. For example, + if you were calling the pass like:: + + from qiskit.transpiler.passes import RZXCalibrationBuilder + from qiskit.test.mock import FakeMumbai + + backend = FakeMumbai() + cal_pass = RZXCalibrationBuilder(backend) + + instead you should call it like:: + + from qiskit.transpiler.passes import RZXCalibrationBuilder + from qiskit.test.mock import FakeMumbai + + backend = FakeMumbai() + inst_map = backend.defaults().instruction_schedule_map + channel_map = self.backend.configuration().qubit_channel_mapping + cal_pass = RZXCalibrationBuilder( + instruction_schedule_map=inst_map, + qubit_channel_mapping=channel_map, + ) + + This change is necessary because as a general rule backend objects are not + pickle serializeable and it would break when it was used with multiple + processes inside of :func:`~qiskit.compiler.transpile` when compliing + multiple circuits at once. diff --git a/test/python/pulse/test_calibrationbuilder.py b/test/python/pulse/test_calibrationbuilder.py index 3637bb06136b..79cc20fb6654 100644 --- a/test/python/pulse/test_calibrationbuilder.py +++ b/test/python/pulse/test_calibrationbuilder.py @@ -57,7 +57,10 @@ def test_rzx_calibration_builder(self): self.assertEqual(rzx_qc.calibrations, {}) # apply the RZXCalibrationBuilderNoEcho. - pass_ = RZXCalibrationBuilderNoEcho(self.backend) + pass_ = RZXCalibrationBuilderNoEcho( + instruction_schedule_map=self.backend.defaults().instruction_schedule_map, + qubit_channel_mapping=self.backend.configuration().qubit_channel_mapping, + ) cal_qc = PassManager(pass_).run(rzx_qc) rzx_qc_duration = schedule(cal_qc, self.backend).duration diff --git a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py index b9a38871bbad..410a1441ffcf 100644 --- a/test/python/transpiler/test_echo_rzx_weyl_decomposition.py +++ b/test/python/transpiler/test_echo_rzx_weyl_decomposition.py @@ -38,6 +38,7 @@ class TestEchoRZXWeylDecomposition(QiskitTestCase): def setUp(self): super().setUp() self.backend = FakeParis() + self.inst_map = self.backend.defaults().instruction_schedule_map def assertRZXgates(self, unitary_circuit, after): """Check the number of rzx gates""" @@ -74,7 +75,7 @@ def test_rzx_number_native_weyl_decomposition(self): unitary_circuit = qi.Operator(circuit).data - after = EchoRZXWeylDecomposition(self.backend)(circuit) + after = EchoRZXWeylDecomposition(self.inst_map)(circuit) unitary_after = qi.Operator(after).data @@ -96,11 +97,11 @@ def test_h_number_non_native_weyl_decomposition_1(self): circuit_non_native.rzz(theta, qr[1], qr[0]) dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) dag_non_native = circuit_to_dag(circuit_non_native) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after_non_native = dag_to_circuit(pass_.run(dag_non_native)) circuit_rzx_number = self.count_gate_number("rzx", after) @@ -126,11 +127,11 @@ def test_h_number_non_native_weyl_decomposition_2(self): circuit_non_native.swap(qr[1], qr[0]) dag = circuit_to_dag(circuit) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after = dag_to_circuit(pass_.run(dag)) dag_non_native = circuit_to_dag(circuit_non_native) - pass_ = EchoRZXWeylDecomposition(self.backend) + pass_ = EchoRZXWeylDecomposition(self.inst_map) after_non_native = dag_to_circuit(pass_.run(dag_non_native)) circuit_rzx_number = self.count_gate_number("rzx", after) @@ -165,7 +166,7 @@ def test_weyl_decomposition_gate_angles(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)) dag_after = circuit_to_dag(after) @@ -220,7 +221,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