diff --git a/qiskit_experiments/calibration_management/calibrations.py b/qiskit_experiments/calibration_management/calibrations.py index 6c8ecf6efc..17c6a9275e 100644 --- a/qiskit_experiments/calibration_management/calibrations.py +++ b/qiskit_experiments/calibration_management/calibrations.py @@ -37,7 +37,7 @@ ) from qiskit.pulse.channels import PulseChannel from qiskit.circuit import Parameter, ParameterExpression -from qiskit.providers.backend import BackendV1 as Backend +from qiskit.providers.backend import Backend from qiskit_experiments.exceptions import CalibrationError from qiskit_experiments.calibration_management.basis_gate_library import BasisGateLibrary @@ -49,6 +49,7 @@ ParameterValueType, ScheduleKey, ) +from qiskit_experiments.framework import BackendData class Calibrations: @@ -257,26 +258,27 @@ def from_backend( Returns: An instance of Calibrations instantiated from a backend. """ - if hasattr(backend, "name") and hasattr(backend.name, "__call__"): - backend_name = backend.name() - else: - backend_name = None + backend_data = BackendData(backend) + + control_channel_map = {} + for qargs in backend_data.coupling_map: + control_channel_map[tuple(qargs)] = backend_data.control_channel(qargs) cals = Calibrations( - getattr(backend.configuration(), "coupling_map", []), - getattr(backend.configuration(), "control_channels", None), + backend_data.coupling_map, + control_channel_map, library, libraries, add_parameter_defaults, - backend_name, - getattr(backend, "version", None), + backend_data.name, + backend_data.version, ) if add_parameter_defaults: - for qubit, freq in enumerate(getattr(backend.defaults(), "qubit_freq_est", [])): + for qubit, freq in enumerate(backend_data.drive_freqs): cals.add_parameter_value(freq, cals.drive_freq, qubit, update_inst_map=False) - for meas, freq in enumerate(getattr(backend.defaults(), "meas_freq_est", [])): + for meas, freq in enumerate(backend_data.meas_freqs): cals.add_parameter_value(freq, cals.meas_freq, meas, update_inst_map=False) # Update the instruction schedule map after adding all parameter values. diff --git a/qiskit_experiments/framework/__init__.py b/qiskit_experiments/framework/__init__.py index f875632f7f..347719afb2 100644 --- a/qiskit_experiments/framework/__init__.py +++ b/qiskit_experiments/framework/__init__.py @@ -251,6 +251,7 @@ .. _create-experiment: """ from qiskit.providers.options import Options +from qiskit_experiments.framework.backend_data import BackendData from qiskit_experiments.framework.analysis_result import AnalysisResult from qiskit_experiments.framework.experiment_data import ( ExperimentStatus, diff --git a/qiskit_experiments/framework/backend_data.py b/qiskit_experiments/framework/backend_data.py new file mode 100644 index 0000000000..73a2241cc3 --- /dev/null +++ b/qiskit_experiments/framework/backend_data.py @@ -0,0 +1,254 @@ +# 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 fake_backend, FakeBackendV2, FakeBackend + + +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 qiskit-terra + 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 terra 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) + + 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 None + return None + + 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 + + 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 + + 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: + # currently has a typo in terra + if hasattr(self._backend.target, "acquire_alignment"): + return self._backend.target.acquire_alignment + return self._backend.target.aquire_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: + return list(self._backend.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""" + if self._v1: + return getattr(self._backend, "provider", None) + elif self._v2: + return self._backend.provider + 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: + 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:: + Currently BackendV2 does not have access to this data. + """ + if self._v1: + return getattr(self._backend.defaults(), "meas_freq_est", []) + elif self._v2: + # meas_freq_est is currently not part of the BackendV2 + return [] + return [] + + @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 + def is_simulator(self): + """Returns True given an indication the backend is a simulator + .. note:: + 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 Terra. + """ + if self._v1: + if self._backend.configuration().simulator or isinstance(self._backend, FakeBackend): + return True + if self._v2: + if isinstance(self._backend, (FakeBackendV2, fake_backend.FakeBackendV2)): + return True + + return False diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index e0656eb986..6c0eb21748 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -24,6 +24,7 @@ from qiskit.exceptions import QiskitError from qiskit.qobj.utils import MeasLevel from qiskit.providers.options import Options +from qiskit_experiments.framework import BackendData from qiskit_experiments.framework.store_init_args import StoreInitArgs from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.experiment_data import ExperimentData @@ -140,6 +141,7 @@ def _set_backend(self, backend: Backend): properties from the supplied backend if required. """ self._backend = backend + self._backend_data = BackendData(backend) def copy(self) -> "BaseExperiment": """Return a copy of the experiment""" @@ -331,11 +333,11 @@ def _finalize(self): def _run_jobs(self, circuits: List[QuantumCircuit], **run_options) -> List[Job]: """Run circuits on backend as 1 or more jobs.""" # Run experiment jobs - max_experiments = getattr(self.backend.configuration(), "max_experiments", None) - if max_experiments and len(circuits) > max_experiments: + max_circuits = self._backend_data.max_circuits + if max_circuits and len(circuits) > max_circuits: # Split jobs for backends that have a maximum job size job_circuits = [ - circuits[i : i + max_experiments] for i in range(0, len(circuits), max_experiments) + circuits[i : i + max_circuits] for i in range(0, len(circuits), max_circuits) ] else: # Run as single job diff --git a/qiskit_experiments/framework/composite/parallel_experiment.py b/qiskit_experiments/framework/composite/parallel_experiment.py index 72cd96405d..bf384cda96 100644 --- a/qiskit_experiments/framework/composite/parallel_experiment.py +++ b/qiskit_experiments/framework/composite/parallel_experiment.py @@ -86,7 +86,7 @@ def _combined_circuits(self, device_layout: bool) -> List[QuantumCircuit]: # Work around for backend coupling map circuit inflation coupling_map = getattr(self.transpile_options, "coupling_map", None) if coupling_map is None and self.backend: - coupling_map = self.backend.configuration().coupling_map + coupling_map = self._backend_data.coupling_map if coupling_map is not None: num_qubits = 1 + max(*self.physical_qubits, np.max(coupling_map)) else: diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index ba2c8b8249..b91773ecd4 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -49,6 +49,7 @@ ThreadSafeList, ) from qiskit_experiments.framework.analysis_result import AnalysisResult +from qiskit_experiments.framework import BackendData from qiskit_experiments.database_service.exceptions import ( ExperimentDataError, ExperimentEntryNotFound, @@ -535,31 +536,28 @@ def _set_backend(self, new_backend: Backend, recursive: bool = True) -> None: # defined independently from the setter to enable setting without autosave self._backend = new_backend - if hasattr(new_backend, "name"): - self._db_data.backend = new_backend.name() - else: + self._backend_data = BackendData(new_backend) + self._db_data.backend = self._backend_data.name + if self._db_data.backend is None: self._db_data.backend = str(new_backend) - if hasattr(new_backend, "provider"): - self._set_hgp_from_backend() + provider = self._backend_data.provider + if provider is not None: + self._set_hgp_from_provider(provider) if recursive: for data in self.child_data(): data._set_backend(new_backend) - def _set_hgp_from_backend(self): - if self.backend is not None and self.backend.provider() is not None: - try: - creds = self.backend.provider().credentials - hub = self._db_data.hub or creds.hub - group = self._db_data.group or creds.group - project = self._db_data.project or creds.project - self._db_data.hub = hub - self._db_data.group = group - self._db_data.project = project - except AttributeError: - LOG.warning( - "Unable to set hub/group/project backend %s ", - self.backend, - ) + def _set_hgp_from_provider(self, provider): + try: + creds = provider.credentials + hub = self._db_data.hub or creds.hub + group = self._db_data.group or creds.group + project = self._db_data.project or creds.project + self._db_data.hub = hub + self._db_data.group = group + self._db_data.project = project + except AttributeError: + return def _clear_results(self): """Delete all currently stored analysis results and figures""" @@ -719,18 +717,21 @@ def add_jobs( # Add futures for extracting finished job data timeout_ids = [] for job in jobs: - jid = job.job_id() - if self.backend is not None and self.backend.name() != job.backend().name(): - LOG.warning( - "Adding a job from a backend (%s) that is different " - "than the current backend (%s). " - "The new backend will be used, but " - "service is not changed if one already exists.", - job.backend(), - self.backend, - ) + if self.backend is not None: + backend_name = BackendData(self.backend).name + job_backend_name = BackendData(job.backend()).name + if self.backend and backend_name != job_backend_name: + LOG.warning( + "Adding a job from a backend (%s) that is different " + "than the current backend (%s). " + "The new backend will be used, but " + "service is not changed if one already exists.", + job.backend(), + self.backend, + ) self.backend = job.backend() + jid = job.job_id() if jid in self._jobs: LOG.warning( "Skipping duplicate job, a job with this ID already exists [Job ID: %s]", jid diff --git a/qiskit_experiments/framework/restless_mixin.py b/qiskit_experiments/framework/restless_mixin.py index 2dc365d004..b3d23d43f2 100644 --- a/qiskit_experiments/framework/restless_mixin.py +++ b/qiskit_experiments/framework/restless_mixin.py @@ -95,6 +95,7 @@ def enable_restless( """ try: if not rep_delay: + # BackendV1 only; BackendV2 does not support this rep_delay = self._backend.configuration().rep_delay_range[0] except AttributeError as error: raise DataProcessorError( diff --git a/qiskit_experiments/library/calibration/fine_frequency_cal.py b/qiskit_experiments/library/calibration/fine_frequency_cal.py index de256a8e86..3ab1d60ee1 100644 --- a/qiskit_experiments/library/calibration/fine_frequency_cal.py +++ b/qiskit_experiments/library/calibration/fine_frequency_cal.py @@ -77,7 +77,7 @@ def __init__( ) if self.backend is not None: - self.set_experiment_options(dt=getattr(self.backend.configuration(), "dt", None)) + self.set_experiment_options(dt=self._backend_data.dt) @classmethod def _default_experiment_options(cls): diff --git a/qiskit_experiments/library/characterization/cr_hamiltonian.py b/qiskit_experiments/library/characterization/cr_hamiltonian.py index 3544764aaf..b1cce00928 100644 --- a/qiskit_experiments/library/characterization/cr_hamiltonian.py +++ b/qiskit_experiments/library/characterization/cr_hamiltonian.py @@ -203,28 +203,24 @@ def _set_backend(self, backend: Backend): # This falls into CRPulseGate which requires pulse schedule # Extract control channel index - try: - cr_channels = backend.configuration().control(self.physical_qubits) + cr_channels = self._backend_data.control_channel(self.physical_qubits) + if cr_channels is not None: self._cr_channel = cr_channels[0].index - except AttributeError: + else: warnings.warn( - f"{backend.name()} doesn't provide cr channel mapping. " + f"{self._backend_data.name} doesn't provide cr channel mapping. " "Cannot find proper channel index to play the cross resonance pulse.", UserWarning, ) + # Extract pulse granularity - try: - self._granularity = backend.configuration().timing_constraints["granularity"] - except (AttributeError, KeyError): - # Probably no chunk size restriction on waveform memory. - pass + self._granularity = self._backend_data.granularity # Extract time resolution, this is anyways required for xvalue conversion - try: - self._dt = backend.configuration().dt - except AttributeError: + self._dt = self._backend_data.dt + if self._dt is None: warnings.warn( - f"{backend.name()} doesn't provide system time resolution dt. " + f"{self._backend_data.name} doesn't provide system time resolution dt. " "Cannot estimate Hamiltonian coefficients in SI units.", UserWarning, ) diff --git a/qiskit_experiments/library/characterization/qubit_spectroscopy.py b/qiskit_experiments/library/characterization/qubit_spectroscopy.py index e9897b428d..069bfe2ce4 100644 --- a/qiskit_experiments/library/characterization/qubit_spectroscopy.py +++ b/qiskit_experiments/library/characterization/qubit_spectroscopy.py @@ -60,7 +60,7 @@ def _backend_center_frequency(self) -> float: if self.backend is None: raise QiskitError("backend not set. Cannot determine the center frequency.") - return self.backend.defaults().qubit_freq_est[self.physical_qubits[0]] + return self._backend_data.drive_freqs[self.physical_qubits[0]] def _template_circuit(self, freq_param) -> QuantumCircuit: """Return the template quantum circuit.""" diff --git a/qiskit_experiments/library/characterization/ramsey_xy.py b/qiskit_experiments/library/characterization/ramsey_xy.py index 2faf140602..438b0c8ad8 100644 --- a/qiskit_experiments/library/characterization/ramsey_xy.py +++ b/qiskit_experiments/library/characterization/ramsey_xy.py @@ -18,7 +18,6 @@ from qiskit import QuantumCircuit from qiskit.circuit import Parameter from qiskit.providers.backend import Backend -from qiskit.providers.fake_provider import FakeBackend from qiskit_experiments.framework import BaseExperiment from qiskit_experiments.framework.restless_mixin import RestlessMixin @@ -125,7 +124,7 @@ def _set_backend(self, backend: Backend): super()._set_backend(backend) # Scheduling parameters - if not self._backend.configuration().simulator and not isinstance(backend, FakeBackend): + if not self._backend_data.is_simulator: timing_constraints = getattr(self.transpile_options, "timing_constraints", {}) if "acquire_alignment" not in timing_constraints: timing_constraints["acquire_alignment"] = 16 @@ -148,11 +147,10 @@ def circuits(self) -> List[QuantumCircuit]: Returns: A list of circuits with a variable delay. """ - if self.backend and hasattr(self.backend.configuration(), "dt"): - dt_unit = True - dt_factor = self.backend.configuration().dt - else: - dt_unit = False + dt_unit = False + if self.backend: + dt_factor = self._backend_data.dt + dt_unit = dt_factor is not None # Compute the rz rotation angle to add a modulation. p_delay_sec = Parameter("delay_sec") diff --git a/qiskit_experiments/library/characterization/resonator_spectroscopy.py b/qiskit_experiments/library/characterization/resonator_spectroscopy.py index df4ef21822..13d7219971 100644 --- a/qiskit_experiments/library/characterization/resonator_spectroscopy.py +++ b/qiskit_experiments/library/characterization/resonator_spectroscopy.py @@ -21,7 +21,7 @@ from qiskit.providers import Backend import qiskit.pulse as pulse -from qiskit_experiments.framework import Options +from qiskit_experiments.framework import Options, BackendData from qiskit_experiments.library.characterization.spectroscopy import Spectroscopy from .analysis.resonator_spectroscopy_analysis import ResonatorSpectroscopyAnalysis @@ -138,7 +138,7 @@ def __init__( "Cannot automatically compute absolute frequencies without a backend." ) - center_freq = backend.defaults().meas_freq_est[qubit] + center_freq = BackendData(backend).meas_freqs[qubit] frequencies += center_freq super().__init__(qubit, frequencies, backend, absolute, analysis, **experiment_options) @@ -156,7 +156,7 @@ def _backend_center_frequency(self) -> float: if self.backend is None: raise QiskitError("backend not set. Cannot call center_frequency.") - return self.backend.defaults().meas_freq_est[self.physical_qubits[0]] + return self._backend_data.meas_freqs[self.physical_qubits[0]] def _template_circuit(self) -> QuantumCircuit: """Return the template quantum circuit.""" diff --git a/qiskit_experiments/library/characterization/spectroscopy.py b/qiskit_experiments/library/characterization/spectroscopy.py index 4c045f0c93..644c5dd5eb 100644 --- a/qiskit_experiments/library/characterization/spectroscopy.py +++ b/qiskit_experiments/library/characterization/spectroscopy.py @@ -101,9 +101,8 @@ def _set_backend(self, backend: Backend): """Set the backend for the experiment and extract config information.""" super()._set_backend(backend) - self._dt = getattr(self.backend.configuration(), "dt", None) - constraints = getattr(self.backend.configuration(), "timing_constraints", {}) - self._granularity = constraints.get("granularity", None) + self._dt = self._backend_data.dt + self._granularity = self._backend_data.granularity if self._dt is None or self._granularity is None: raise QiskitError(f"{self.__class__.__name__} needs both dt and sample granularity.") diff --git a/qiskit_experiments/library/characterization/t1.py b/qiskit_experiments/library/characterization/t1.py index 63e9c43d44..f98f29e7e6 100644 --- a/qiskit_experiments/library/characterization/t1.py +++ b/qiskit_experiments/library/characterization/t1.py @@ -18,8 +18,6 @@ from qiskit import QuantumCircuit from qiskit.providers.backend import Backend -from qiskit.providers.fake_provider import FakeBackend - from qiskit_experiments.framework import BaseExperiment, Options from qiskit_experiments.library.characterization.analysis.t1_analysis import T1Analysis @@ -85,7 +83,7 @@ def _set_backend(self, backend: Backend): super()._set_backend(backend) # Scheduling parameters - if not self._backend.configuration().simulator and not isinstance(backend, FakeBackend): + if not self._backend_data.is_simulator: timing_constraints = getattr(self.transpile_options, "timing_constraints", {}) if "acquire_alignment" not in timing_constraints: timing_constraints["acquire_alignment"] = 16 @@ -101,11 +99,10 @@ def circuits(self) -> List[QuantumCircuit]: Returns: The experiment circuits """ - if self.backend and hasattr(self.backend.configuration(), "dt"): - dt_unit = True - dt_factor = self.backend.configuration().dt - else: - dt_unit = False + dt_unit = False + if self.backend: + dt_factor = self._backend_data.dt + dt_unit = dt_factor is not None circuits = [] for delay in self.experiment_options.delays: diff --git a/qiskit_experiments/library/characterization/t2hahn.py b/qiskit_experiments/library/characterization/t2hahn.py index 28515bae67..a9b3b337ee 100644 --- a/qiskit_experiments/library/characterization/t2hahn.py +++ b/qiskit_experiments/library/characterization/t2hahn.py @@ -18,7 +18,6 @@ from qiskit import QuantumCircuit, QiskitError from qiskit.providers.backend import Backend -from qiskit.providers.fake_provider import FakeBackend from qiskit_experiments.framework import BaseExperiment, Options from qiskit_experiments.library.characterization.analysis.t2hahn_analysis import T2HahnAnalysis @@ -116,7 +115,7 @@ def _set_backend(self, backend: Backend): super()._set_backend(backend) # Scheduling parameters - if not self._backend.configuration().simulator and not isinstance(backend, FakeBackend): + if not self._backend_data.is_simulator: timing_constraints = getattr(self.transpile_options, "timing_constraints", {}) if "acquire_alignment" not in timing_constraints: timing_constraints["acquire_alignment"] = 16 @@ -137,11 +136,10 @@ def circuits(self) -> List[QuantumCircuit]: The experiment circuits. """ - if self.backend and hasattr(self.backend.configuration(), "dt"): - dt_unit = True - dt_factor = self.backend.configuration().dt - else: - dt_unit = False + dt_unit = False + if self.backend: + dt_factor = self._backend_data.dt + dt_unit = dt_factor is not None circuits = [] for delay_gate in np.asarray(self.experiment_options.delays, dtype=float): diff --git a/qiskit_experiments/library/characterization/t2ramsey.py b/qiskit_experiments/library/characterization/t2ramsey.py index 7e46d42c1d..5b875f82d2 100644 --- a/qiskit_experiments/library/characterization/t2ramsey.py +++ b/qiskit_experiments/library/characterization/t2ramsey.py @@ -20,7 +20,6 @@ import qiskit from qiskit import QuantumCircuit from qiskit.providers.backend import Backend -from qiskit.providers.fake_provider import FakeBackend from qiskit_experiments.framework import BaseExperiment, Options from qiskit_experiments.library.characterization.analysis.t2ramsey_analysis import T2RamseyAnalysis @@ -100,7 +99,7 @@ def _set_backend(self, backend: Backend): super()._set_backend(backend) # Scheduling parameters - if not self._backend.configuration().simulator and not isinstance(backend, FakeBackend): + if not self._backend_data.is_simulator: timing_constraints = getattr(self.transpile_options, "timing_constraints", {}) if "acquire_alignment" not in timing_constraints: timing_constraints["acquire_alignment"] = 16 @@ -118,11 +117,10 @@ def circuits(self) -> List[QuantumCircuit]: Returns: The experiment circuits """ - if self.backend and hasattr(self.backend.configuration(), "dt"): - dt_unit = True - dt_factor = self.backend.configuration().dt - else: - dt_unit = False + dt_unit = False + if self.backend: + dt_factor = self._backend_data.dt + dt_unit = dt_factor is not None circuits = [] for delay in self.experiment_options.delays: diff --git a/qiskit_experiments/test/noisy_delay_aer_simulator.py b/qiskit_experiments/test/noisy_delay_aer_simulator.py index 307961cc0b..738849351c 100644 --- a/qiskit_experiments/test/noisy_delay_aer_simulator.py +++ b/qiskit_experiments/test/noisy_delay_aer_simulator.py @@ -20,6 +20,7 @@ from qiskit.providers.aer.jobs.aerjob import AerJob from qiskit.providers.aer.noise.passes import RelaxationNoisePass from qiskit.circuit import Delay +from qiskit_experiments.framework import BackendData class NoisyDelayAerBackend(AerSimulator): @@ -38,10 +39,11 @@ def __init__( super().__init__(**backend_options) self._t2 = t2 or [1e-4] self._t1 = t1 or [2e-4] - - if backend and hasattr(backend.configuration(), "dt"): + if backend: + dt = BackendData(backend).dt + if dt is not None: self._dt_unit = True - self._dt_factor = backend.configuration().dt + self._dt_factor = dt else: self._dt_unit = False self._dt_factor = dt or 1e-9 diff --git a/releasenotes/notes/backend_data_class-270cec767b463e97.yaml b/releasenotes/notes/backend_data_class-270cec767b463e97.yaml new file mode 100644 index 0000000000..375837f01d --- /dev/null +++ b/releasenotes/notes/backend_data_class-270cec767b463e97.yaml @@ -0,0 +1,11 @@ +--- +features: + - | + Adds a new class, :class:`.BackendData` which provides a common access inferface + for both :class:`.BackendV1` and :class:`.BackendV2` data fields, since those + classes do not share the same interface. The :class:`.BackendData` can be called + on a backend and used immediately, and it is also automatically stored as the + `_backend_data` field of :class:`Experiment`. Note that not all data fields + are currently accessible via :class:`.BackendData`; to access additional + fields, the corresponding method should be added to :class:`.BackendData` + with correct treatment for both V1 and V2 backends. diff --git a/requirements.txt b/requirements.txt index a7a32835bd..940f08981c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ numpy>=1.17 scipy>=1.4 -qiskit-terra>=0.20.1 +qiskit-terra>=0.21.1 qiskit-ibmq-provider>=0.16.0 -qiskit-ibm-experiment>=0.2.4 +qiskit-ibm-experiment>=0.2.5 matplotlib>=3.4 uncertainties lmfit diff --git a/test/calibration/experiments/test_fine_drag.py b/test/calibration/experiments/test_fine_drag.py index 25f2fa4fc1..f64ab29116 100644 --- a/test/calibration/experiments/test_fine_drag.py +++ b/test/calibration/experiments/test_fine_drag.py @@ -18,7 +18,7 @@ from qiskit import transpile from qiskit.circuit import Gate -from qiskit.providers.fake_provider import FakeArmonk +from qiskit.providers.fake_provider import FakeArmonkV2 import qiskit.pulse as pulse from qiskit_experiments.library import FineDrag, FineXDrag, FineDragCal @@ -45,7 +45,7 @@ def test_circuits(self): drag = FineDrag(0, Gate("Drag", num_qubits=1, params=[])) drag.set_experiment_options(schedule=self.schedule) - drag.backend = FakeArmonk() + drag.backend = FakeArmonkV2() for circuit in drag.circuits()[1:]: for idx, name in enumerate(["Drag", "rz", "Drag", "rz"]): self.assertEqual(circuit.data[idx][0].name, name) diff --git a/test/calibration/experiments/test_fine_frequency.py b/test/calibration/experiments/test_fine_frequency.py index b43c96a799..9e1014da51 100644 --- a/test/calibration/experiments/test_fine_frequency.py +++ b/test/calibration/experiments/test_fine_frequency.py @@ -16,13 +16,14 @@ import numpy as np from ddt import ddt, data -from qiskit.providers.fake_provider import FakeArmonk +from qiskit.providers.fake_provider import FakeArmonkV2 import qiskit.pulse as pulse from qiskit_experiments.library import ( FineFrequency, FineFrequencyCal, ) +from qiskit_experiments.framework import BackendData from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.test.mock_iq_backend import MockIQBackend @@ -45,7 +46,7 @@ def setUp(self): self.inst_map.add("sx", 0, sx_sched) - self.cals = Calibrations.from_backend(FakeArmonk(), libraries=[FixedFrequencyTransmon()]) + self.cals = Calibrations.from_backend(FakeArmonkV2(), libraries=[FixedFrequencyTransmon()]) @data(-0.5e6, -0.1e6, 0.1e6, 0.5e6) def test_end_to_end(self, freq_shift): @@ -77,7 +78,7 @@ def test_calibration_version(self): exp_helper.dt = backend.configuration().dt fine_freq = FineFrequencyCal(0, self.cals, backend) - armonk_freq = FakeArmonk().defaults().qubit_freq_est[0] + armonk_freq = BackendData(FakeArmonkV2()).drive_freqs[0] freq_before = self.cals.get_parameter_value(self.cals.__drive_freq_parameter__, 0) diff --git a/test/calibration/experiments/test_ramsey_xy.py b/test/calibration/experiments/test_ramsey_xy.py index 59966e8e04..7a97e0f646 100644 --- a/test/calibration/experiments/test_ramsey_xy.py +++ b/test/calibration/experiments/test_ramsey_xy.py @@ -14,11 +14,11 @@ import unittest from test.base import QiskitExperimentsTestCase -from qiskit.providers.fake_provider import FakeArmonk +from qiskit.providers.fake_provider import FakeArmonkV2 from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon -from qiskit_experiments.framework import BaseAnalysis, AnalysisStatus +from qiskit_experiments.framework import BaseAnalysis, AnalysisStatus, BackendData from qiskit_experiments.library import RamseyXY, FrequencyCal from qiskit_experiments.test.mock_iq_backend import MockIQBackend from qiskit_experiments.test.mock_iq_helpers import MockIQRamseyXYHelper as RamseyXYHelper @@ -32,7 +32,7 @@ def setUp(self): super().setUp() library = FixedFrequencyTransmon() - self.cals = Calibrations.from_backend(FakeArmonk(), libraries=[library]) + self.cals = Calibrations.from_backend(FakeArmonkV2(), libraries=[library]) def test_end_to_end(self): """Test that we can run on a mock backend and perform a fit. @@ -61,7 +61,7 @@ def test_update_calibrations(self): # Check qubit frequency before running the cal f01 = self.cals.get_parameter_value(freq_name, 0) self.assertTrue(len(self.cals.parameters_table(parameters=[freq_name])["data"]), 1) - self.assertEqual(f01, FakeArmonk().defaults().qubit_freq_est[0]) + self.assertEqual(f01, BackendData(FakeArmonkV2()).drive_freqs[0]) freq_shift = 4e6 osc_shift = 2e6 @@ -74,7 +74,7 @@ def test_update_calibrations(self): # Check that qubit frequency after running the cal is shifted by freq_shift, i.e. 4 MHz. f01 = self.cals.get_parameter_value(freq_name, 0) self.assertTrue(len(self.cals.parameters_table(parameters=[freq_name])["data"]), 2) - self.assertLess(abs(f01 - (freq_shift + FakeArmonk().defaults().qubit_freq_est[0])), tol) + self.assertLess(abs(f01 - (freq_shift + BackendData(FakeArmonkV2()).drive_freqs[0])), tol) def test_update_with_failed_analysis(self): """Test that calibration update handles analysis producing no results diff --git a/test/calibration/experiments/test_rough_amplitude.py b/test/calibration/experiments/test_rough_amplitude.py index 25a3467e98..99b6c43646 100644 --- a/test/calibration/experiments/test_rough_amplitude.py +++ b/test/calibration/experiments/test_rough_amplitude.py @@ -18,7 +18,7 @@ from qiskit import transpile import qiskit.pulse as pulse from qiskit.circuit import Parameter -from qiskit.providers.fake_provider import FakeArmonk +from qiskit.providers.fake_provider import FakeArmonkV2 from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon from qiskit_experiments.calibration_management import Calibrations @@ -35,7 +35,7 @@ def setUp(self): super().setUp() library = FixedFrequencyTransmon() - self.backend = FakeArmonk() + self.backend = FakeArmonkV2() self.cals = Calibrations.from_backend(self.backend, libraries=[library]) def test_circuits(self): @@ -86,7 +86,7 @@ def setUp(self): library = FixedFrequencyTransmon() - self.backend = FakeArmonk() + self.backend = FakeArmonkV2() self.cals = Calibrations.from_backend(self.backend, libraries=[library]) # Add some pulses on the 1-2 transition. diff --git a/test/calibration/experiments/test_rough_frequency.py b/test/calibration/experiments/test_rough_frequency.py index d973e03a5a..c3c1c67124 100644 --- a/test/calibration/experiments/test_rough_frequency.py +++ b/test/calibration/experiments/test_rough_frequency.py @@ -15,8 +15,9 @@ import numpy as np -from qiskit.providers.fake_provider import FakeArmonk +from qiskit.providers.fake_provider import FakeArmonkV2 +from qiskit_experiments.framework import BackendData from qiskit_experiments.library import RoughFrequencyCal from qiskit_experiments.calibration_management import Calibrations from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon @@ -31,7 +32,7 @@ def test_init(self): """Test that initialization.""" qubit = 1 - cals = Calibrations.from_backend(FakeArmonk()) + cals = Calibrations.from_backend(FakeArmonkV2()) frequencies = [1000, 2000, 3000] auto_update = False absolute = False @@ -48,7 +49,7 @@ def test_init(self): def test_update_calibrations(self): """Test that we can properly update an instance of Calibrations.""" - freq01 = FakeArmonk().defaults().qubit_freq_est[0] + freq01 = BackendData(FakeArmonkV2()).drive_freqs[0] backend = MockIQBackend( experiment_helper=SpectroscopyHelper( @@ -64,7 +65,7 @@ def test_update_calibrations(self): backend.defaults().qubit_freq_est = [freq01, freq01] library = FixedFrequencyTransmon(basis_gates=["x", "sx"]) - cals = Calibrations.from_backend(FakeArmonk(), libraries=[library]) + cals = Calibrations.from_backend(FakeArmonkV2(), libraries=[library]) prev_freq = cals.get_parameter_value(cals.__drive_freq_parameter__, (0,)) self.assertEqual(prev_freq, freq01) @@ -80,7 +81,7 @@ def test_update_calibrations(self): def test_experiment_config(self): """Test converting to and from config works""" - cals = Calibrations.from_backend(FakeArmonk()) + cals = Calibrations.from_backend(FakeArmonkV2()) frequencies = [1, 2, 3] exp = RoughFrequencyCal(0, cals, frequencies) loaded_exp = RoughFrequencyCal.from_config(exp.config()) diff --git a/test/calibration/test_calibrations.py b/test/calibration/test_calibrations.py index 192fe612d2..834ab3c95d 100644 --- a/test/calibration/test_calibrations.py +++ b/test/calibration/test_calibrations.py @@ -31,7 +31,8 @@ from qiskit import transpile, QuantumCircuit from qiskit.pulse.transforms import inline_subroutines, block_to_schedule import qiskit.pulse as pulse -from qiskit.providers.fake_provider import FakeArmonk, FakeBelem +from qiskit.providers.fake_provider import FakeArmonkV2, FakeBelemV2 +from qiskit_experiments.framework import BackendData from qiskit_experiments.calibration_management.calibrations import Calibrations, ParameterKey from qiskit_experiments.calibration_management.parameter_value import ParameterValue from qiskit_experiments.calibration_management.basis_gate_library import FixedFrequencyTransmon @@ -270,6 +271,18 @@ def test_qubit_input(self): with self.assertRaises(CalibrationError): self.cals.get_parameter_value("amp", "(1, a)", "xp") + def test_from_backend(self): + """Test that when generating calibrations from backend + the data is passed correctly""" + backend = FakeBelemV2() + cals = Calibrations.from_backend(backend) + config_args = cals.config()["kwargs"] + control_channel_map_size = len(config_args["control_channel_map"].chan_map) + coupling_map_size = len(config_args["coupling_map"]) + self.assertEqual(control_channel_map_size, 8) + self.assertEqual(coupling_map_size, 8) + self.assertEqual(cals.get_parameter_value("drive_freq", 0), 5090167234.445013) + class TestOverrideDefaults(QiskitExperimentsTestCase): """ @@ -1433,7 +1446,7 @@ def test_save_load_library(self): """ library = FixedFrequencyTransmon() - backend = FakeArmonk() + backend = FakeArmonkV2() cals = Calibrations.from_backend(backend, libraries=[library]) cals.parameters_table() @@ -1446,7 +1459,7 @@ def test_save_load_library(self): self.assertEqual(cals.get_parameter_value("amp", (0,), "x"), 0.5) self.assertEqual( cals.get_parameter_value("drive_freq", (0,)), - backend.defaults().qubit_freq_est[0], + BackendData(backend).drive_freqs[0], ) @@ -1457,7 +1470,7 @@ def test_setup_withLibrary(self): """Test that we can setup with a library.""" cals = Calibrations.from_backend( - FakeArmonk(), + FakeArmonkV2(), libraries=[ FixedFrequencyTransmon(basis_gates=["x", "sx"], default_values={"duration": 320}) ], @@ -1478,7 +1491,7 @@ def test_setup_withLibrary(self): def test_instruction_schedule_map_export(self): """Test that exporting the inst map works as planned.""" - backend = FakeBelem() + backend = FakeBelemV2() cals = Calibrations.from_backend( backend, @@ -1492,12 +1505,12 @@ def test_instruction_schedule_map_export(self): cals.add_schedule(cr, num_qubits=2) cals.update_inst_map({"cr"}) - for qubit in range(backend.configuration().num_qubits): + for qubit in range(BackendData(backend).num_qubits): self.assertTrue(cals.default_inst_map.has("sx", (qubit,))) # based on coupling map of Belem to keep the test robust. expected_pairs = [(0, 1), (1, 0), (1, 2), (2, 1), (1, 3), (3, 1), (3, 4), (4, 3)] - coupling_map = set(tuple(pair) for pair in backend.configuration().coupling_map) + coupling_map = set(tuple(pair) for pair in BackendData(backend).coupling_map) for pair in expected_pairs: self.assertTrue(pair in coupling_map) @@ -1507,7 +1520,7 @@ def test_inst_map_transpilation(self): """Test that we can use the inst_map to inject the cals into the circuit.""" cals = Calibrations.from_backend( - FakeArmonk(), + FakeArmonkV2(), libraries=[FixedFrequencyTransmon(basis_gates=["x"])], ) @@ -1557,7 +1570,7 @@ def test_inst_map_updates(self): """Test that updating a parameter will force an inst map update.""" cals = Calibrations.from_backend( - FakeBelem(), + FakeBelemV2(), libraries=[FixedFrequencyTransmon(basis_gates=["sx", "x"])], ) @@ -1606,7 +1619,7 @@ def test_cx_cz_case(self): a CX on qubits 0, 1 in the inst. map and a CZ on qubits 1, 2. """ - cals = Calibrations.from_backend(FakeBelem()) + cals = Calibrations.from_backend(FakeBelemV2()) sig = Parameter("σ") dur = Parameter("duration") @@ -1653,14 +1666,19 @@ def test_cx_cz_case(self): def test_alternate_initialization(self): """Test that we can initialize without a backend object.""" - backend = FakeBelem() + backend = FakeBelemV2() library = FixedFrequencyTransmon(basis_gates=["sx", "x"]) + backend_data = BackendData(backend) + control_channel_map = {} + for qargs in backend_data.coupling_map: + control_channel_map[tuple(qargs)] = backend_data.control_channel(qargs) + cals1 = Calibrations.from_backend(backend, libraries=[library]) cals2 = Calibrations( libraries=[library], - control_channel_map=backend.configuration().control_channels, - coupling_map=backend.configuration().coupling_map, + control_channel_map=control_channel_map, + coupling_map=backend_data.coupling_map, ) self.assertEqual(str(cals1.get_schedule("x", 1)), str(cals2.get_schedule("x", 1))) @@ -1672,7 +1690,7 @@ class TestSerialization(QiskitExperimentsTestCase): def test_serialization(self): """Test the serialization.""" - backend = FakeBelem() + backend = FakeBelemV2() library = FixedFrequencyTransmon(basis_gates=["sx", "x"]) cals = Calibrations.from_backend(backend, libraries=[library]) @@ -1682,7 +1700,7 @@ def test_serialization(self): def test_equality(self): """Test the equal method on calibrations.""" - backend = FakeBelem() + backend = FakeBelemV2() library = FixedFrequencyTransmon(basis_gates=["sx", "x"]) cals1 = Calibrations.from_backend( diff --git a/test/calibration/test_update_library.py b/test/calibration/test_update_library.py index 22979ae7c5..5aa11bd09d 100644 --- a/test/calibration/test_update_library.py +++ b/test/calibration/test_update_library.py @@ -17,7 +17,7 @@ from qiskit.circuit import Parameter from qiskit.qobj.utils import MeasLevel import qiskit.pulse as pulse -from qiskit.providers.fake_provider import FakeAthens +from qiskit.providers.fake_provider import FakeAthensV2 from qiskit_experiments.library import QubitSpectroscopy from qiskit_experiments.calibration_management.calibrations import Calibrations @@ -84,7 +84,7 @@ def test_frequency(self): self.assertEqual(result.quality, "good") # Test the integration with the Calibrations - cals = Calibrations.from_backend(FakeAthens()) + cals = Calibrations.from_backend(FakeAthensV2()) self.assertNotEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value) Frequency.update(cals, exp_data) self.assertEqual(cals.get_parameter_value(cals.__drive_freq_parameter__, qubit), value) diff --git a/test/database_service/test_db_experiment_data.py b/test/database_service/test_db_experiment_data.py index 31794c7ed0..b0e4e7d867 100644 --- a/test/database_service/test_db_experiment_data.py +++ b/test/database_service/test_db_experiment_data.py @@ -27,13 +27,14 @@ import matplotlib.pyplot as plt import numpy as np -from qiskit.providers.fake_provider import FakeMelbourne +from qiskit.providers.fake_provider import FakeMelbourneV2 from qiskit.result import Result from qiskit.providers import JobV1 as Job from qiskit.providers import JobStatus from qiskit_ibm_experiment import IBMExperimentService from qiskit_experiments.framework import ExperimentData from qiskit_experiments.framework import AnalysisResult +from qiskit_experiments.framework import BackendData from qiskit_experiments.database_service.exceptions import ( ExperimentDataError, ExperimentEntryNotFound, @@ -52,7 +53,7 @@ class TestDbExperimentData(QiskitExperimentsTestCase): def setUp(self): super().setUp() - self.backend = FakeMelbourne() + self.backend = FakeMelbourneV2() def test_db_experiment_data_attributes(self): """Test DB experiment data attributes.""" @@ -70,7 +71,7 @@ def test_db_experiment_data_attributes(self): metadata={"foo": "bar"}, **attrs, ) - self.assertEqual(exp_data.backend.name(), self.backend.name()) + self.assertEqual(exp_data.backend.name, self.backend.name) self.assertEqual(exp_data.experiment_type, "qiskit_test") self.assertEqual(exp_data.experiment_id, "1234") self.assertEqual(exp_data.tags, ["tag1", "tag2"]) @@ -1031,7 +1032,7 @@ def _job2_result(): def _get_job_result(self, circ_count, has_metadata=False): """Return a job result with random counts.""" job_result = { - "backend_name": self.backend.name(), + "backend_name": BackendData(self.backend).name, "backend_version": "1.1.1", "qobj_id": "1234", "job_id": "some_job_id", diff --git a/test/randomized_benchmarking/test_randomized_benchmarking.py b/test/randomized_benchmarking/test_randomized_benchmarking.py index 1036eeb411..64826c46d4 100644 --- a/test/randomized_benchmarking/test_randomized_benchmarking.py +++ b/test/randomized_benchmarking/test_randomized_benchmarking.py @@ -169,9 +169,9 @@ def test_poor_experiment_result(self): This is a special case that fit outcome is very sensitive to initial guess. Perhaps generated initial guess is close to a local minima. """ - from qiskit.providers.fake_provider import FakeVigo + from qiskit.providers.fake_provider import FakeVigoV2 - backend = AerSimulator.from_backend(FakeVigo(), seed_simulator=123) + backend = FakeVigoV2() exp = rb.StandardRB( qubits=(0,), lengths=[100, 200, 300, 400], diff --git a/test/test_cross_resonance_hamiltonian.py b/test/test_cross_resonance_hamiltonian.py index 328e4baf2c..a26baf6512 100644 --- a/test/test_cross_resonance_hamiltonian.py +++ b/test/test_cross_resonance_hamiltonian.py @@ -19,10 +19,11 @@ import numpy as np from ddt import ddt, data, unpack from qiskit import QuantumCircuit, pulse, quantum_info as qi -from qiskit.providers.fake_provider import FakeBogota +from qiskit.providers.fake_provider import FakeBogotaV2 from qiskit.extensions.hamiltonian_gate import HamiltonianGate from qiskit.providers.aer import AerSimulator from qiskit_experiments.library.characterization import cr_hamiltonian +from qiskit_experiments.framework import BackendData class SimulatableCRGate(HamiltonianGate): @@ -47,14 +48,7 @@ class TestCrossResonanceHamiltonian(QiskitExperimentsTestCase): def test_circuit_generation(self): """Test generated circuits.""" - backend = FakeBogota() - - # Add granularity to check duration optimization logic - setattr( - backend.configuration(), - "timing_constraints", - {"granularity": 16}, - ) + backend = FakeBogotaV2() expr = cr_hamiltonian.CrossResonanceHamiltonian( qubits=(0, 1), @@ -65,20 +59,20 @@ def test_circuit_generation(self): ) expr.backend = backend - nearlest_16 = 1248 + duration = 1256 with pulse.build(default_alignment="left", name="cr") as ref_cr_sched: pulse.play( pulse.GaussianSquare( - nearlest_16, + duration, amp=0.1, sigma=64, width=1000, ), pulse.ControlChannel(0), ) - pulse.delay(nearlest_16, pulse.DriveChannel(0)) - pulse.delay(nearlest_16, pulse.DriveChannel(1)) + pulse.delay(duration, pulse.DriveChannel(0)) + pulse.delay(duration, pulse.DriveChannel(1)) cr_gate = cr_hamiltonian.CrossResonanceHamiltonian.CRPulseGate(width=1000) expr_circs = expr.circuits() @@ -137,12 +131,7 @@ def test_circuit_generation_no_backend(self): def test_instance_with_backend_without_cr_gate(self): """Calling set backend method without setting cr gate.""" - backend = FakeBogota() - setattr( - backend.configuration(), - "timing_constraints", - {"granularity": 16}, - ) + backend = FakeBogotaV2() # not raise an error exp = cr_hamiltonian.CrossResonanceHamiltonian( @@ -150,12 +139,15 @@ def test_instance_with_backend_without_cr_gate(self): flat_top_widths=[1000], backend=backend, ) - ref_config = backend.configuration() - self.assertEqual(exp._dt, ref_config.dt) + backend_data = BackendData(backend) + ref_dt = backend_data.dt + self.assertEqual(exp._dt, ref_dt) # These properties are set when cr_gate is not provided - self.assertEqual(exp._cr_channel, ref_config.control((0, 1))[0].index) - self.assertEqual(exp._granularity, ref_config.timing_constraints["granularity"]) + ref_cr_channel = backend_data.control_channel((0, 1))[0].index + self.assertEqual(exp._cr_channel, ref_cr_channel) + ref_granularity = backend_data.granularity + self.assertEqual(exp._granularity, ref_granularity) @data( [1e6, 2e6, 1e3, -3e6, -2e6, 1e4], diff --git a/test/test_readout_error.py b/test/test_readout_error.py index 59beec27e0..8338561600 100644 --- a/test/test_readout_error.py +++ b/test/test_readout_error.py @@ -19,8 +19,7 @@ from test.base import QiskitExperimentsTestCase import numpy as np from qiskit.quantum_info.operators.predicates import matrix_equal -from qiskit.providers.aer import AerSimulator -from qiskit.providers.fake_provider import FakeParis +from qiskit.providers.fake_provider import FakeParisV2 from qiskit_ibm_experiment import IBMExperimentService from qiskit_experiments.library.characterization import LocalReadoutError, CorrelatedReadoutError from qiskit_experiments.framework import ExperimentData @@ -144,7 +143,7 @@ def test_circuit_generation(self): def test_parallel_running(self): """Test that parallel experiments work for this experiment""" - backend = AerSimulator.from_backend(FakeParis()) + backend = FakeParisV2() exp1 = CorrelatedReadoutError([0, 2]) exp2 = CorrelatedReadoutError([1, 3]) exp = ParallelExperiment([exp1, exp2]) @@ -158,7 +157,7 @@ def test_parallel_running(self): def test_database_save_and_load(self): """Tests saving and loading the mitigator from the DB""" qubits = [0, 1] - backend = AerSimulator.from_backend(FakeParis()) + backend = FakeParisV2() exp = LocalReadoutError(qubits) exp_data = exp.run(backend).block_for_results() exp_data.service = IBMExperimentService(local=True, local_save=False) @@ -173,7 +172,7 @@ def test_database_save_and_load(self): def test_json_serialization(self): """Verifies that mitigators can be serialized for DB storage""" qubits = [0, 1] - backend = AerSimulator.from_backend(FakeParis()) + backend = FakeParisV2() exp = LocalReadoutError(qubits) exp_data = exp.run(backend).block_for_results() mitigator = exp_data.analysis_results(0).value diff --git a/test/test_t1.py b/test/test_t1.py index 148fc35637..1139cc0ef0 100644 --- a/test/test_t1.py +++ b/test/test_t1.py @@ -16,6 +16,7 @@ from test.base import QiskitExperimentsTestCase import numpy as np from qiskit_ibm_experiment import IBMExperimentService +from qiskit.providers.fake_provider import FakeAthensV2 from qiskit_experiments.test.noisy_delay_aer_simulator import NoisyDelayAerBackend from qiskit_experiments.framework import ExperimentData, ParallelExperiment from qiskit_experiments.library import T1 @@ -252,3 +253,26 @@ def test_analysis_config(self): loaded = T1Analysis.from_config(analysis.config()) self.assertNotEqual(analysis, loaded) self.assertEqual(analysis.config(), loaded.config()) + + def test_circuits_with_backend(self): + """ + Test the circuits metadata when passing backend + """ + backend = FakeAthensV2() + delays = np.arange(1e-3, 40e-3, 3e-3) + exp = T1(0, delays, backend=backend) + circs = exp.circuits() + + self.assertEqual(len(circs), len(delays)) + + for delay, circ in zip(delays, circs): + xval = circ.metadata.pop("xval") + self.assertAlmostEqual(xval, delay) + self.assertEqual( + circ.metadata, + { + "experiment_type": "T1", + "qubit": 0, + "unit": "s", + }, + )