Source code for qiskit_experiments.framework.backend_data

# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# 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 data access helper class

Since `BackendV1` and `BackendV2` do not share the same interface, this
class unifies data access for various data fields.
"""
from qiskit.providers.models import PulseBackendConfiguration
from qiskit.providers import BackendV1, BackendV2
from qiskit.providers.fake_provider import FakeBackend
from qiskit.utils.deprecation import deprecate_func

try:
    # Removed in Qiskit 1.0.
    from qiskit.providers.fake_provider.fake_backend import FakeBackendV2
except ImportError:

    class FakeBackendV2:
        """Dummy class for when FakeBackendV2 import fails

        This class is only used in isinstance checks. If the import fails, then
        there won't be an instance of the class either so any dummy class is
        fine.
        """

        pass


try:
    # Removed in Qiskit 1.0. Different from the other FakeBackendV2's
    from qiskit.providers.fake_provider import QiskitFakeBackendV2
except ImportError:

    class QiskitFakeBackendV2:
        """Dummy class for when FakeBackendV2 import fails

        This class is only used in isinstance checks. If the import fails, then
        there won't be an instance of the class either so any dummy class is
        fine.
        """

        pass


try:
    # A copy of qiskit.providers.fake_provider.fake_backend.FakeBackendV2, at
    # least as of qiskit-ibm-runtime 0.18.0 and Qiskit 1.0
    from qiskit_ibm_runtime.fake_provider.fake_backend import FakeBackendV2 as RuntimeFakeBackendV2
except ImportError:

    class RuntimeFakeBackendV2:
        """Dummy class for when FakeBackendV2 import fails

        This class is only used in isinstance checks. If the import fails, then
        there won't be an instance of the class either so any dummy class is
        fine.
        """

        pass


[docs] class BackendData: """Class for providing joint interface for accessing backend data""" def __init__(self, backend): """Inits the backend and verifies version""" self._backend = backend self._v1 = isinstance(backend, BackendV1) self._v2 = isinstance(backend, BackendV2) if self._v2: self._parse_additional_data() def _parse_additional_data(self): # data specific parsing not done yet in upstream qiskit if hasattr(self._backend, "_conf_dict") and self._backend._conf_dict["open_pulse"]: if "u_channel_lo" not in self._backend._conf_dict: self._backend._conf_dict["u_channel_lo"] = [] # to avoid qiskit bug self._pulse_conf = PulseBackendConfiguration.from_dict(self._backend._conf_dict) @property def name(self): """Returns the backend name""" if self._v1: return self._backend.name() elif self._v2: return self._backend.name return str(self._backend)
[docs] def control_channel(self, qubits): """Returns the backend control channel for the given qubits""" try: if self._v1: return self._backend.configuration().control(qubits) elif self._v2: try: return self._backend.control_channel(qubits) except NotImplementedError: return self._pulse_conf.control(qubits) except AttributeError: return [] return []
[docs] def drive_channel(self, qubit): """Returns the backend drive channel for the given qubit""" try: if self._v1: return self._backend.configuration().drive(qubit) elif self._v2: try: return self._backend.drive_channel(qubit) except NotImplementedError: return self._pulse_conf.drive(qubit) except AttributeError: return None return None
[docs] def measure_channel(self, qubit): """Returns the backend measure channel for the given qubit""" try: if self._v1: return self._backend.configuration().measure(qubit) elif self._v2: try: return self._backend.measure_channel(qubit) except NotImplementedError: return self._pulse_conf.measure(qubit) except AttributeError: return None return None
[docs] def acquire_channel(self, qubit): """Returns the backend acquire channel for the given qubit""" try: if self._v1: return self._backend.configuration().acquire(qubit) elif self._v2: try: return self._backend.acquire_channel(qubit) except NotImplementedError: return self._pulse_conf.acquire(qubit) except AttributeError: return None return None
@property def granularity(self): """Returns the backend's time constraint granularity""" try: if self._v1: return self._backend.configuration().timing_constraints.get("granularity", 1) elif self._v2: return self._backend.target.granularity except AttributeError: return 1 return 1 @property def min_length(self): """Returns the backend's time constraint minimum duration""" try: if self._v1: return self._backend.configuration().timing_constraints.get("min_length", 0) elif self._v2: return self._backend.target.min_length except AttributeError: return 0 return 0 @property def pulse_alignment(self): """Returns the backend's time constraint pulse alignment""" try: if self._v1: return self._backend.configuration().timing_constraints.get("pulse_alignment", 1) elif self._v2: return self._backend.target.pulse_alignment except AttributeError: return 1 return 1 @property def acquire_alignment(self): """Returns the backend's time constraint acquire alignment""" try: if self._v1: return self._backend.configuration().timing_constraints.get("acquire_alignment", 1) elif self._v2: return self._backend.target.acquire_alignment except AttributeError: return 1 return 1 @property def dt(self): """Returns the backend's input time resolution""" if self._v1: try: return self._backend.configuration().dt except AttributeError: return None elif self._v2: return self._backend.dt return None @property def max_circuits(self): """Returns the backend's max experiments value""" if self._v1: return getattr(self._backend.configuration(), "max_experiments", None) elif self._v2: return self._backend.max_circuits return None @property def coupling_map(self): """Returns the backend's coupling map""" if self._v1: return getattr(self._backend.configuration(), "coupling_map", []) elif self._v2: coupling_map = self._backend.coupling_map if coupling_map is None: return coupling_map return list(coupling_map.get_edges()) return [] @property def version(self): """Returns the backend's version""" if self._v1: return getattr(self._backend, "version", None) elif self._v2: return self._backend.version return None @property def provider(self): """Returns the backend's provider""" try: if self._v1: return self._backend.provider() elif self._v2: return self._backend.provider except AttributeError: return None return None @property def drive_freqs(self): """Returns the backend's qubit drive frequencies""" if self._v1: return getattr(self._backend.defaults(), "qubit_freq_est", []) elif self._v2: if self._backend.target.qubit_properties is None: return [] return [property.frequency for property in self._backend.target.qubit_properties] return [] @property def meas_freqs(self): """Returns the backend's measurement stimulus frequencies. .. note:: The qiskit base classes do not provide this information as a standard backend property, but it is available from some providers in the data returned by the ``Backend.defaults()`` method. """ if not hasattr(self._backend, "defaults"): return [] return getattr(self._backend.defaults(), "meas_freq_est", []) @property def num_qubits(self): """Returns the backend's number of qubits""" if self._v1: return self._backend.configuration().num_qubits elif self._v2: # meas_freq_est is currently not part of the BackendV2 return self._backend.num_qubits return None @property @deprecate_func( is_property=True, since="0.6", additional_msg=( "is_simulator is deprecated because BackendV2 does not provide a " "standard way for checking this property. Calling code must " "determine if a backend uses a simulator from context." ), package_name="qiskit-experiments", ) # Note: remove all FakeBackend imports when removing this code def is_simulator(self): """Returns True given an indication the backend is a simulator .. note:: For `BackendV2` we sometimes cannot be sure, because it lacks a `simulator` field, as was present in `BackendV1`'s configuration. We still check whether the backend inherits `FakeBackendV2`, for either of its existing implementations in Qiskit. """ if self._v1: if self._backend.configuration().simulator or isinstance(self._backend, FakeBackend): return True if self._v2: if isinstance( self._backend, (FakeBackendV2, QiskitFakeBackendV2, RuntimeFakeBackendV2) ): return True return False
[docs] def qubit_t1(self, qubit: int) -> float: """Return the T1 value for a qubit from the backend properties Args: qubit: the qubit index to return T1 for Returns: The T1 value """ if self._v1: return self._backend.properties().qubit_property(qubit)["T1"][0] if self._v2: return self._backend.qubit_properties(qubit).t1 return float("nan")