# 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,
filter_faulty: bool = False,
):
"""Uses configuration, properties and pulse defaults
to construct and return Target class.
In order to convert with a ``defaults.instruction_schedule_map``,
which has a custom calibration for an operation,
the operation name must be in ``configuration.basis_gates`` and
``custom_name_mapping`` must be supplied for the operation.
Otherwise, the operation will be dropped in the resulting ``Target`` object.
That suggests it is recommended to add custom calibrations **after** creating a target
with this function instead of adding them to ``defaults`` in advance. For example::
target.add_instruction(custom_gate, {(0, 1): InstructionProperties(calibration=custom_sched)})
"""
# 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)
faulty_qubits = set()
# Parse from properties if it exsits
if properties is not None:
if filter_faulty:
faulty_qubits = set(properties.faulty_qubits())
qubit_properties = qubit_props_list_from_props(properties=properties)
target = Target(
num_qubits=configuration.n_qubits,
qubit_properties=qubit_properties,
concurrent_measurements=getattr(configuration, "meas_map", None),
)
# 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)
if filter_faulty:
if any(not properties.is_qubit_operational(qubit) for qubit in qubits):
continue
if not properties.is_gate_operational(name, gate.qubits):
continue
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):
if filter_faulty:
if not properties.is_qubit_operational(qubit):
continue
try:
duration = properties.readout_length(qubit)
except BackendPropertyError:
duration = None
try:
error = properties.readout_error(qubit)
except BackendPropertyError:
error = None
measure_props[(qubit,)] = InstructionProperties(
duration=duration,
error=error,
)
target.add_instruction(Measure(), measure_props)
# Parse from configuration because properties doesn't exist
else:
target = Target(
num_qubits=configuration.n_qubits,
concurrent_measurements=getattr(configuration, "meas_map", None),
)
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.acquire_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):
try:
qargs = tuple(qarg)
except TypeError:
qargs = (qarg,)
# Do NOT call .get method. This parses Qpbj immediately.
# This operation is computationally expensive and should be bypassed.
calibration_entry = inst_map._get_calibration_entry(inst, qargs)
if inst in target:
if inst == "measure":
for qubit in qargs:
if filter_faulty and qubit in faulty_qubits:
continue
target[inst][(qubit,)].calibration = calibration_entry
elif qargs in target[inst]:
if filter_faulty and any(qubit in faulty_qubits for qubit in qargs):
continue
target[inst][qargs].calibration = calibration_entry
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], name=op)
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) if bit not in faulty_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`.
When using custom calibrations (or other custom workflows) it is **not** recommended
to mutate the ``BackendV1`` object before applying this converter. For example, in order to
convert a ``BackendV1`` object with a customized ``defaults().instruction_schedule_map``,
which has a custom calibration for an operation, the operation name must be in
``configuration().basis_gates`` and ``name_mapping`` must be supplied for the operation.
Otherwise, the operation will be dropped in the resulting ``BackendV2`` object.
Instead it is typically better to add custom calibrations **after** applying this converter
instead of updating ``BackendV1.defaults()`` in advance. For example::
backend_v2 = BackendV2Converter(backend_v1)
backend_v2.target.add_instruction(
custom_gate, {(0, 1): InstructionProperties(calibration=custom_sched)}
)
"""
def __init__(
self,
backend: BackendV1,
name_mapping: Optional[Dict[str, Any]] = None,
add_delay: bool = False,
filter_faulty: 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
filter_faulty: If the :class:`~.BackendProperties` object (if present) for
``backend`` has any qubits or gates flagged as non-operational filter
those from the output target.
"""
self._backend = backend
self._config = self._backend.configuration()
super().__init__(
provider=backend.provider,
name=backend.name(),
description=self._config.description,
online_date=getattr(self._config, "online_date", None),
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
self._filter_faulty = filter_faulty
@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,
filter_faulty=self._filter_faulty,
)
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.meas_map
[ドキュメント] def drive_channel(self, qubit: int):
return self._config.drive(qubit)
[ドキュメント] def measure_channel(self, qubit: int):
return self._config.measure(qubit)
[ドキュメント] def acquire_channel(self, qubit: int):
return self._config.acquire(qubit)
[ドキュメント] def control_channel(self, qubits: Iterable[int]):
return self._config.control(qubits)
[ドキュメント] def run(self, run_input, **options):
return self._backend.run(run_input, **options)