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