-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BackendV2Converter class for treating BackendV1 as BackendV2 (#8759)
* Add BackendV2Convert class for treating BackendV1 as BackendV2 This commit adds a new class BackendV2Converter which is a BackendV2 implementation that converts an input BackendV1 object into a BackendV2 implementation. This is useful for users that are supporting working with arbitrary providers so that they can standardize on using the newest access patterns even if a provider is still using BackendV1. Similarly, for qiskit's internal usage, this gives us a path to move all of the transpiler internals to use Target and avoid carrying around duplicate information in the PassManagerConfig for passes that haven't been updated. This will enable us to convert input BackendV1 instances to a target once on initial calling and have the transpiler only ever see a target. Fixes #8611 * Update docstring * Return empty options for _default_options * Remove leftover pylint disable * Expand standard gate conversions and handle missing basis_gates * Fix copy paste error qubit_props_list_from_props() docstring * Add name mapping argument to allow setting custom name-> gate mappings for conversion * Add missing gamma parameter * Fix handling of global ops in configuration * Raise exception on custom gates without mapping * Move name mapping to standard gates module * Fix lint and docs * Use gate object name attribute to build name mapping Co-authored-by: Jake Lishman <jake.lishman@ibm.com> * Fix lint * Fix pylint cyclic-import error * Update qiskit/providers/backend_compat.py Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com> * Remove supported_instructions and add option for adding delay The supported instructions field is underdocumented and it's not clear that the instructions it contains are something that we want to support directly. Since the only reason we added support for it in the target generation was to handle the delay for ibm backends (mostly in tests as the IBM provider will be using BackendV2 natively soon) this commit removes its usage and adds a new flag for explicitly adding delay to the backend's target. * Add mention of converter class to API changes section * Add missing flag usage to delay test * Run black Co-authored-by: Jake Lishman <jake.lishman@ibm.com> Co-authored-by: Naoki Kanazawa <nkanazawa1989@gmail.com>
- Loading branch information
1 parent
7dbcb82
commit 4a857f6
Showing
5 changed files
with
406 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,282 @@ | ||
# 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. | ||
|
||
"""Backend abstract interface for providers.""" | ||
|
||
from __future__ import annotations | ||
|
||
from typing import List, Iterable, Any, Dict, Optional | ||
|
||
from qiskit.exceptions import QiskitError | ||
|
||
from qiskit.providers.backend import BackendV1, BackendV2 | ||
from qiskit.providers.backend import QubitProperties | ||
from qiskit.utils.units import apply_prefix | ||
from qiskit.circuit.library.standard_gates import get_standard_gate_name_mapping | ||
from qiskit.circuit.measure import Measure | ||
from qiskit.providers.models.backendconfiguration import BackendConfiguration | ||
from qiskit.providers.models.backendproperties import BackendProperties | ||
from qiskit.providers.models.pulsedefaults import PulseDefaults | ||
from qiskit.providers.options import Options | ||
from qiskit.providers.exceptions import BackendPropertyError | ||
|
||
|
||
def convert_to_target( | ||
configuration: BackendConfiguration, | ||
properties: BackendProperties = None, | ||
defaults: PulseDefaults = None, | ||
custom_name_mapping: Optional[Dict[str, Any]] = None, | ||
add_delay: bool = False, | ||
): | ||
"""Uses configuration, properties and pulse defaults | ||
to construct and return Target class. | ||
""" | ||
# pylint: disable=cyclic-import | ||
from qiskit.transpiler.target import ( | ||
Target, | ||
InstructionProperties, | ||
) | ||
|
||
# Standard gates library mapping, multicontrolled gates not included since they're | ||
# variable width | ||
name_mapping = get_standard_gate_name_mapping() | ||
target = None | ||
if custom_name_mapping is not None: | ||
name_mapping.update(custom_name_mapping) | ||
# Parse from properties if it exsits | ||
if properties is not None: | ||
qubit_properties = qubit_props_list_from_props(properties=properties) | ||
target = Target(num_qubits=configuration.n_qubits, qubit_properties=qubit_properties) | ||
# Parse instructions | ||
gates: Dict[str, Any] = {} | ||
for gate in properties.gates: | ||
name = gate.gate | ||
if name in name_mapping: | ||
if name not in gates: | ||
gates[name] = {} | ||
else: | ||
raise QiskitError( | ||
f"Operation name {name} does not have a known mapping. Use " | ||
"custom_name_mapping to map this name to an Operation object" | ||
) | ||
|
||
qubits = tuple(gate.qubits) | ||
gate_props = {} | ||
for param in gate.parameters: | ||
if param.name == "gate_error": | ||
gate_props["error"] = param.value | ||
if param.name == "gate_length": | ||
gate_props["duration"] = apply_prefix(param.value, param.unit) | ||
gates[name][qubits] = InstructionProperties(**gate_props) | ||
for gate, props in gates.items(): | ||
inst = name_mapping[gate] | ||
target.add_instruction(inst, props) | ||
# Create measurement instructions: | ||
measure_props = {} | ||
for qubit, _ in enumerate(properties.qubits): | ||
measure_props[(qubit,)] = InstructionProperties( | ||
duration=properties.readout_length(qubit), | ||
error=properties.readout_error(qubit), | ||
) | ||
target.add_instruction(Measure(), measure_props) | ||
# Parse from configuration because properties doesn't exist | ||
else: | ||
target = Target(num_qubits=configuration.n_qubits) | ||
for gate in configuration.gates: | ||
name = gate.name | ||
gate_props = ( | ||
{tuple(x): None for x in gate.coupling_map} # type: ignore[misc] | ||
if hasattr(gate, "coupling_map") | ||
else {None: None} | ||
) | ||
if name in name_mapping: | ||
target.add_instruction(name_mapping[name], gate_props) | ||
else: | ||
raise QiskitError( | ||
f"Operation name {name} does not have a known mapping. " | ||
"Use custom_name_mapping to map this name to an Operation object" | ||
) | ||
target.add_instruction(Measure()) | ||
# parse global configuration properties | ||
if hasattr(configuration, "dt"): | ||
target.dt = configuration.dt | ||
if hasattr(configuration, "timing_constraints"): | ||
target.granularity = configuration.timing_constraints.get("granularity") | ||
target.min_length = configuration.timing_constraints.get("min_length") | ||
target.pulse_alignment = configuration.timing_constraints.get("pulse_alignment") | ||
target.aquire_alignment = configuration.timing_constraints.get("acquire_alignment") | ||
# If a pulse defaults exists use that as the source of truth | ||
if defaults is not None: | ||
inst_map = defaults.instruction_schedule_map | ||
for inst in inst_map.instructions: | ||
for qarg in inst_map.qubits_with_instruction(inst): | ||
sched = inst_map.get(inst, qarg) | ||
if inst in target: | ||
try: | ||
qarg = tuple(qarg) | ||
except TypeError: | ||
qarg = (qarg,) | ||
if inst == "measure": | ||
for qubit in qarg: | ||
target[inst][(qubit,)].calibration = sched | ||
elif qarg in target[inst]: | ||
target[inst][qarg].calibration = sched | ||
combined_global_ops = set() | ||
if configuration.basis_gates: | ||
combined_global_ops.update(configuration.basis_gates) | ||
for op in combined_global_ops: | ||
if op not in target: | ||
if op in name_mapping: | ||
target.add_instruction( | ||
name_mapping[op], {(bit,): None for bit in range(target.num_qubits)} | ||
) | ||
else: | ||
raise QiskitError( | ||
f"Operation name '{op}' does not have a known mapping. Use " | ||
"custom_name_mapping to map this name to an Operation object" | ||
) | ||
if add_delay and "delay" not in target: | ||
target.add_instruction( | ||
name_mapping["delay"], {(bit,): None for bit in range(target.num_qubits)} | ||
) | ||
return target | ||
|
||
|
||
def qubit_props_list_from_props( | ||
properties: BackendProperties, | ||
) -> List[QubitProperties]: | ||
"""Uses BackendProperties to construct | ||
and return a list of QubitProperties. | ||
""" | ||
qubit_props: List[QubitProperties] = [] | ||
for qubit, _ in enumerate(properties.qubits): | ||
try: | ||
t_1 = properties.t1(qubit) | ||
except BackendPropertyError: | ||
t_1 = None | ||
try: | ||
t_2 = properties.t2(qubit) | ||
except BackendPropertyError: | ||
t_2 = None | ||
try: | ||
frequency = properties.frequency(qubit) | ||
except BackendPropertyError: | ||
frequency = None | ||
qubit_props.append( | ||
QubitProperties( # type: ignore[no-untyped-call] | ||
t1=t_1, | ||
t2=t_2, | ||
frequency=frequency, | ||
) | ||
) | ||
return qubit_props | ||
|
||
|
||
class BackendV2Converter(BackendV2): | ||
"""A converter class that takes a :class:`~.BackendV1` instance and wraps it in a | ||
:class:`~.BackendV2` interface. | ||
This class implements the :class:`~.BackendV2` interface and is used to enable | ||
common access patterns between :class:`~.BackendV1` and :class:`~.BackendV2`. This | ||
class should only be used if you need a :class:`~.BackendV2` and still need | ||
compatibility with :class:`~.BackendV1`. | ||
""" | ||
|
||
def __init__( | ||
self, | ||
backend: BackendV1, | ||
name_mapping: Optional[Dict[str, Any]] = None, | ||
add_delay: bool = False, | ||
): | ||
"""Initialize a BackendV2 converter instance based on a BackendV1 instance. | ||
Args: | ||
backend: The input :class:`~.BackendV1` based backend to wrap in a | ||
:class:`~.BackendV2` interface | ||
name_mapping: An optional dictionary that maps custom gate/operation names in | ||
``backend`` to an :class:`~.Operation` object representing that | ||
gate/operation. By default most standard gates names are mapped to the | ||
standard gate object from :mod:`qiskit.circuit.library` this only needs | ||
to be specified if the input ``backend`` defines gates in names outside | ||
that set. | ||
add_delay: If set to true a :class:`~qiskit.circuit.Delay` operation | ||
will be added to the target as a supported operation for all | ||
qubits | ||
""" | ||
self._backend = backend | ||
self._config = self._backend.configuration() | ||
super().__init__( | ||
provider=backend.provider, | ||
name=backend.name(), | ||
description=self._config.description, | ||
online_date=self._config.online_date, | ||
backend_version=self._config.backend_version, | ||
) | ||
self._options = self._backend._options | ||
self._properties = None | ||
if hasattr(self._backend, "properties"): | ||
self._properties = self._backend.properties() | ||
self._defaults = None | ||
self._target = None | ||
self._name_mapping = name_mapping | ||
self._add_delay = add_delay | ||
|
||
@property | ||
def target(self): | ||
"""A :class:`qiskit.transpiler.Target` object for the backend. | ||
:rtype: Target | ||
""" | ||
if self._target is None: | ||
if self._defaults is None and hasattr(self._backend, "defaults"): | ||
self._defaults = self._backend.defaults() | ||
if self._properties is None and hasattr(self._backend, "properties"): | ||
self._properties = self._backend.properties() | ||
self._target = convert_to_target( | ||
self._config, | ||
self._properties, | ||
self._defaults, | ||
custom_name_mapping=self._name_mapping, | ||
add_delay=self._add_delay, | ||
) | ||
return self._target | ||
|
||
@property | ||
def max_circuits(self): | ||
return self._config.max_experiments | ||
|
||
@classmethod | ||
def _default_options(cls): | ||
return Options() | ||
|
||
@property | ||
def dtm(self) -> float: | ||
return self._config.dtm | ||
|
||
@property | ||
def meas_map(self) -> List[List[int]]: | ||
return self._config.dt | ||
|
||
def drive_channel(self, qubit: int): | ||
self._config.drive(qubit) | ||
|
||
def measure_channel(self, qubit: int): | ||
self._config.measure(qubit) | ||
|
||
def acquire_channel(self, qubit: int): | ||
self._config.acquire(qubit) | ||
|
||
def control_channel(self, qubits: Iterable[int]): | ||
self._config.control(qubits) | ||
|
||
def run(self, run_input, **options): | ||
return self._backend.run(run_input, **options) |
15 changes: 15 additions & 0 deletions
15
releasenotes/notes/backend-converter-05360f12f9042829.yaml
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
--- | ||
features: | ||
- | | ||
Added a new class, :class:`~BackendV2Converter`, which is used to wrap | ||
a :class:`~.BackendV1` instance in a :class:`~.BackendV2` interface. It | ||
enables you to have a :class:`~.BackendV2` instance from any | ||
:class:`~.BackendV1`. This enables standardizing access patterns on the | ||
newer :class:`~.BackendV2` interface even if you still support | ||
:class:`~.BackendV1`. | ||
- | | ||
Added a new function :func:`~.convert_to_target` which is used to take | ||
a :class:`~.BackendConfiguration`, and optionally a | ||
:class:`~.BackendProperties` and :class:`~.PulseDefaults` and create | ||
a :class:`~.Target` object equivalent to the contents of those objects. |
Oops, something went wrong.