Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix arguments for RZXCalibrationBuilder and EchoRZXWeylDecomposition #7331

Merged
merged 3 commits into from
Dec 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 39 additions & 10 deletions qiskit/transpiler/passes/calibration/builders.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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):
mtreinish marked this conversation as resolved.
Show resolved Hide resolved
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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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."""
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
5 changes: 4 additions & 1 deletion test/python/pulse/test_calibrationbuilder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
15 changes: 8 additions & 7 deletions test/python/transpiler/test_echo_rzx_weyl_decomposition.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand Down Expand Up @@ -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

Expand All @@ -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)
Expand All @@ -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)
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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
Expand Down