diff --git a/qiskit_experiments/calibration_management/base_calibration_experiment.py b/qiskit_experiments/calibration_management/base_calibration_experiment.py index b6bf35512b..dd341534ef 100644 --- a/qiskit_experiments/calibration_management/base_calibration_experiment.py +++ b/qiskit_experiments/calibration_management/base_calibration_experiment.py @@ -21,19 +21,13 @@ from qiskit import QuantumCircuit from qiskit.providers.options import Options from qiskit.pulse import ScheduleBlock -from qiskit.transpiler import StagedPassManager, PassManager, Layout, CouplingMap -from qiskit.transpiler.passes import ( - EnlargeWithAncilla, - FullAncillaAllocation, - ApplyLayout, - SetLayout, -) from qiskit_experiments.calibration_management.calibrations import Calibrations from qiskit_experiments.calibration_management.update_library import BaseUpdater from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.base_experiment import BaseExperiment from qiskit_experiments.framework.experiment_data import ExperimentData +from qiskit_experiments.framework.transpilation import map_qubits, minimal_transpile from qiskit_experiments.exceptions import CalibrationError LOG = logging.getLogger(__name__) @@ -198,20 +192,6 @@ def _default_experiment_options(cls) -> Options: options.update_options(result_index=-1, group="default") return options - @classmethod - def _default_transpile_options(cls) -> Options: - """Return empty default transpile options as optimization_level is not used.""" - return Options() - - def set_transpile_options(self, **fields): - r"""Add a warning message. - - .. note:: - If your experiment has overridden `_transpiled_circuits` and needs - transpile options then please also override `set_transpile_options`. - """ - warnings.warn(f"Transpile options are not used in {self.__class__.__name__ }.") - def update_calibrations(self, experiment_data: ExperimentData): """Update parameter values in the :class:`.Calibrations` instance. @@ -295,42 +275,13 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]: Returns: A list of transpiled circuits. """ - transpiled = [] - for circ in self.circuits(): - circ = self._map_to_physical_qubits(circ) + circuits = [map_qubits(c, self.physical_qubits) for c in self.circuits()] + for circ in circuits: self._attach_calibrations(circ) - - transpiled.append(circ) + transpiled = minimal_transpile(circuits, self.backend, self.transpile_options) return transpiled - def _map_to_physical_qubits(self, circuit: QuantumCircuit) -> QuantumCircuit: - """Map program qubits to physical qubits. - - Args: - circuit: The quantum circuit to map to device qubits. - - Returns: - A quantum circuit that has the same number of qubits as the backend and where - the physical qubits of the experiment have been properly mapped. - """ - initial_layout = Layout.from_intlist(list(self.physical_qubits), *circuit.qregs) - - coupling_map = self._backend_data.coupling_map - if coupling_map is not None: - coupling_map = CouplingMap(self._backend_data.coupling_map) - - layout = PassManager( - [ - SetLayout(initial_layout), - FullAncillaAllocation(coupling_map), - EnlargeWithAncilla(), - ApplyLayout(), - ] - ) - - return StagedPassManager(["layout"], layout=layout).run(circuit) - @abstractmethod def _attach_calibrations(self, circuit: QuantumCircuit): """Attach the calibrations to the quantum circuit. diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 41240df41c..5b295f27be 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -13,18 +13,23 @@ Base Experiment class. """ -from abc import ABC, abstractmethod import copy +from abc import ABC, abstractmethod from collections import OrderedDict from typing import Sequence, Optional, Tuple, List, Dict, Union -from qiskit import transpile, QuantumCircuit +from qiskit import QuantumCircuit from qiskit.providers import Job, Backend 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.transpilation import ( + DEFAULT_TRANSPILE_OPTIONS, + map_qubits, + minimal_transpile, +) from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.experiment_data import ExperimentData from qiskit_experiments.framework.configs import ExperimentConfig @@ -373,9 +378,8 @@ def _transpiled_circuits(self) -> List[QuantumCircuit]: This function can be overridden to define custom transpilation. """ - transpile_opts = copy.copy(self.transpile_options.__dict__) - transpile_opts["initial_layout"] = list(self.physical_qubits) - transpiled = transpile(self.circuits(), self.backend, **transpile_opts) + circuits = [map_qubits(c, self.physical_qubits) for c in self.circuits()] + transpiled = minimal_transpile(circuits, self.backend, self.transpile_options) return transpiled @@ -418,11 +422,36 @@ def set_experiment_options(self, **fields): @classmethod def _default_transpile_options(cls) -> Options: - """Default transpiler options for transpilation of circuits""" + """Default transpiler options for transpilation of circuits + + Transpile Options: + optimization_level (int): Optimization level to pass to + :func:`qiskit.transpile`. + num_processes (int): Number of processes to use during + transpilation on Qiskit >= 1.0. + full_transpile (bool): If ``True``, + ``BaseExperiment._transpiled_circuits`` (called by + :meth:`BaseExperiment.run` if not overridden by a subclass) + will call :func:`qiskit.transpile` on the output of + :meth:`BaseExperiment.circuits` before executing the circuits. + If ``False``, ``BaseExperiment._transpiled_circuits`` will + reindex the qubits in the output of + :meth:`BaseExperiment.circuits` using the experiments' + :meth:`BaseExperiment.physical_qubits`. Then it will check if + the circuit operations are all defined in the + :class:`qiskit.transpiler.Target` of the experiment's backend + or in the indiivdual circuit calibrations. If not, it will use + :class:`qiskit.transpiler.passes.BasisTranslator` to map the + circuit instructions to the backend. Additionally, + the :class:`qiskit.transpiler.passes.PulseGates` transpiler + pass will be run if the :class:`qiskit.transpiler.Target` + contains any custom pulse gate calibrations. + + """ # Experiment subclasses can override this method if they need # to set specific default transpiler options to transpile the # experiment circuits. - return Options(optimization_level=0) + return copy.copy(DEFAULT_TRANSPILE_OPTIONS) @property def transpile_options(self) -> Options: diff --git a/qiskit_experiments/framework/experiment_data.py b/qiskit_experiments/framework/experiment_data.py index 21bc979ce4..9021e065d1 100644 --- a/qiskit_experiments/framework/experiment_data.py +++ b/qiskit_experiments/framework/experiment_data.py @@ -866,11 +866,10 @@ def _add_job_data( LOG.warning("Job was cancelled before completion [Job ID: %s]", jid) return jid, False if status == JobStatus.ERROR: - LOG.error( - "Job data not added for errored job [Job ID: %s]\nError message: %s", - jid, - job.error_message(), - ) + msg = f"Job data not added for errored job [Job ID: {jid}]" + if hasattr(job, "error_message"): + msg += f"\nError message: {job.error_message()}" + LOG.error(msg) return jid, False LOG.warning("Adding data from job failed [Job ID: %s]", job.job_id()) raise ex diff --git a/qiskit_experiments/framework/transpilation.py b/qiskit_experiments/framework/transpilation.py new file mode 100644 index 0000000000..30faa60a70 --- /dev/null +++ b/qiskit_experiments/framework/transpilation.py @@ -0,0 +1,225 @@ +# 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. +""" +Functions for preparing circuits for execution +""" + +from __future__ import annotations + +import importlib.metadata +import logging +from collections.abc import Sequence + +from qiskit import QuantumCircuit, QuantumRegister, transpile +from qiskit.exceptions import QiskitError +from qiskit.providers import Backend +from qiskit.providers.options import Options +from qiskit.pulse.calibration_entries import CalibrationPublisher +from qiskit.transpiler import Target + + +LOGGER = logging.getLogger(__file__) + +DEFAULT_TRANSPILE_OPTIONS = Options(optimization_level=0, full_transpile=False) +if importlib.metadata.version("qiskit").partition(".")[0] != "0": + DEFAULT_TRANSPILE_OPTIONS["num_processes"] = 1 + + +def map_qubits( + circuit: QuantumCircuit, + physical_qubits: Sequence[int], + n_qubits: int | None = None, +) -> QuantumCircuit: + """Generate a new version of a circuit with new qubit indices + + This function iterates through the instructions of ``circuit`` and copies + them into a new circuit with qubit indices replaced according to the + entries in ``physical_qubits``. So qubit 0's instructions are applied to + ``physical_qubits[0]`` and qubit 1's to ``physical_qubits[1]``, etc. + + This function behaves similarly to passing ``initial_layout`` to + :func:`qiskit.transpile` but does not use a Qiskit + :class:`~qiskit.transpiler.PassManager` and does not fill the circuit with + ancillas. + + Args: + circuit: The :class:`~qiskit.QuantumCircuit` to re-index. + physical_qubits: The list of new indices for ``circuit``'s qubit indices. + n_qubits: Optional qubit size to use for the output circuit. If + ``None``, then the maximum of ``physical_qubits`` will be used. + + Returns: + The quantum circuit with new qubit indices + """ + if len(physical_qubits) != circuit.num_qubits: + raise QiskitError( + f"Circuit to map has {circuit.num_qubits} qubits, but " + f"{len(physical_qubits)} physical qubits specified for mapping." + ) + + # if all(p == r for p, r in zip(physical_qubits, range(circuit.num_qubits))): + # # No mapping necessary + # return circuit + + circ_size = n_qubits if n_qubits is not None else (max(physical_qubits) + 1) + p_qregs = QuantumRegister(circ_size) + p_circ = QuantumCircuit( + p_qregs, + *circuit.cregs, + name=circuit.name, + metadata=circuit.metadata, + global_phase=circuit.global_phase, + ) + p_circ.compose( + circuit, + qubits=physical_qubits, + inplace=True, + copy=False, + ) + return p_circ + + +def _has_calibration(target: Target, name: str, qubits: tuple[int, ...]) -> bool: + """Wrapper to work around bug in Target.has_calibration""" + try: + has_cal = target.has_calibration(name, qubits) + except AttributeError: + has_cal = False + + return has_cal + + +def check_transpilation_needed( + circuits: Sequence[QuantumCircuit], + backend: Backend, +) -> bool: + """Test if circuits are already compatible with backend + + This function checks if circuits are able to be executed on ``backend`` + without transpilation. It loops through the circuits to check if any gate + instructions are not included in the backend's + :class:`~qiskit.transpiler.Target`. The :class:`~qiskit.transpiler.Target` + is also checked for custom pulse gate calibrations for circuit's + instructions. If all gates are included in the target and there are no + custom calibrations, the function returns ``False`` indicating that + transpilation is not needed. + + This function returns ``True`` if the version of ``backend`` is less than + 2. + + The motivation for this function is that when no transpilation is necessary + it is faster to check the circuits in this way than to run + :func:`~qiskit.transpile` and have it do nothing. + + Args: + circuits: The circuits to prepare for the backend. + backend: The backend for which the circuits should be prepared. + + Returns: + ``True`` if transpilation is needed. Otherwise, ``False``. + """ + transpilation_needed = False + + if getattr(backend, "version", 0) <= 1: + # Fall back to transpilation for BackendV1 + return True + + target = backend.target + + for circ in circuits: + for inst in circ.data: + if inst.operation.name == "barrier" or circ.has_calibration_for(inst): + continue + qubits = tuple(circ.find_bit(q).index for q in inst.qubits) + if not target.instruction_supported(inst.operation.name, qubits): + transpilation_needed = True + break + if _has_calibration(target, inst.operation.name, qubits): + cal = target.get_calibration(inst.operation.name, qubits, *inst.operation.params) + if ( + cal.metadata.get("publisher", CalibrationPublisher.QISKIT) + != CalibrationPublisher.BACKEND_PROVIDER + ): + transpilation_needed = True + break + if transpilation_needed: + break + + return transpilation_needed + + +def minimal_transpile( + circuits: Sequence[QuantumCircuit], + backend: Backend, + options: Options, +) -> list[QuantumCircuit]: + """Prepare circuits for execution on a backend + + This function is a wrapper around :func:`~qiskit.transpile` to prepare + circuits for execution ``backend`` that tries to do less work in the case + in which the ``circuits`` can already be executed on the backend without + modification. + + The instructions in ``circuits`` are checked to see if they can be executed + by the ``backend`` using :func:`check_transpilation_needed`. If the + circuits can not be executed, :func:`~qiskit.transpile` is called on them. + ``options`` is a set of options to pass to the :func:`~qiskit.transpile` + (see detailed description of ``options``). The special ``full_transpile`` + option can also be set to ``True`` to force calling + :func:`~qiskit.transpile`. + + Args: + circuits: The circuits to prepare for the backend. + backend: The backend for which the circuits should be prepared. + options: Options for the transpilation. ``full_transpile`` can be set + to ``True`` to force this function to pass the circuits to + :func:`~qiskit.transpile`. Other options are passed as arguments to + :func:`qiskit.transpile` if it is called. + + Returns: + The prepared circuits + """ + options = dict(options.items()) + + if "full_transpile" not in options: + LOGGER.debug( + "Performing full transpile because base transpile options " + "were overwritten and full_transpile was not specified." + ) + full_transpile = True + else: + full_transpile = options.pop("full_transpile", False) + if not full_transpile and set(options) - set(DEFAULT_TRANSPILE_OPTIONS): + # If an experiment specifies transpile options, it needs to go + # through transpile() + full_transpile = True + LOGGER.debug( + "Performing full transpile because non-default transpile options are specified." + ) + + if not full_transpile: + full_transpile = check_transpilation_needed(circuits, backend) + + import inspect + import unittest + try: + test_frame = next(f[0] for f in inspect.stack() if any(isinstance(l, unittest.TestCase) for n, l in f[0].f_locals.items())) + test = next(v for v in test_frame.f_locals.values()) + print(f"full_transpile={full_transpile} for {test.id()}") + except StopIteration: + pass + if full_transpile: + transpiled = transpile(circuits, backend, **options) + else: + transpiled = circuits + + return transpiled diff --git a/qiskit_experiments/library/calibration/fine_amplitude.py b/qiskit_experiments/library/calibration/fine_amplitude.py index 8e3ff17972..f9af13cf29 100644 --- a/qiskit_experiments/library/calibration/fine_amplitude.py +++ b/qiskit_experiments/library/calibration/fine_amplitude.py @@ -182,20 +182,6 @@ def __init__( } ) - @classmethod - def _default_transpile_options(cls): - """Default transpile options. - - Transpile Options: - basis_gates (list(str)): A list of basis gates needed for this experiment. - The schedules for these basis gates will be provided by the instruction - schedule map from the calibrations. - """ - options = super()._default_transpile_options() - options.basis_gates = ["x", "sx"] - - return options - def _pre_circuit(self, num_clbits: int) -> QuantumCircuit: """The preparation circuit is an sx gate to move to the equator of the Bloch sphere.""" circuit = QuantumCircuit(self.num_qubits, num_clbits) diff --git a/qiskit_experiments/test/mock_iq_backend.py b/qiskit_experiments/test/mock_iq_backend.py index f59eecac0d..3e5bab79d2 100644 --- a/qiskit_experiments/test/mock_iq_backend.py +++ b/qiskit_experiments/test/mock_iq_backend.py @@ -11,17 +11,14 @@ # that they have been altered from the originals. """A mock IQ backend for testing.""" -import datetime from abc import abstractmethod from typing import Sequence, List, Tuple, Dict, Union, Any import numpy as np from qiskit import QuantumCircuit -from qiskit.circuit.library import XGate, SXGate from qiskit.result import Result -from qiskit.providers import BackendV2, Provider, convert_to_target -from qiskit.providers.fake_provider import FakeOpenPulse2Q +from qiskit.providers.fake_provider import GenericBackendV2 from qiskit.qobj.utils import MeasLevel from qiskit_experiments.exceptions import QiskitError @@ -35,59 +32,16 @@ ) -class FakeOpenPulse2QV2(BackendV2): - """BackendV2 conversion of qiskit.providers.fake_provider.FakeOpenPulse2Q""" - - def __init__( - self, - provider: Provider = None, - name: str = None, - description: str = None, - online_date: datetime.datetime = None, - backend_version: str = None, - **fields, - ): - super().__init__(provider, name, description, online_date, backend_version, **fields) - - backend_v1 = FakeOpenPulse2Q() - # convert_to_target requires the description attribute - backend_v1._configuration.description = "A fake test backend with pulse defaults" - - self._target = convert_to_target( - backend_v1.configuration(), - backend_v1.properties(), - backend_v1.defaults(), - add_delay=True, - ) - # See commented out defaults() method below - self._defaults = backend_v1._defaults - - # This method is not defined in the base class as we would like to avoid - # relying on it as much as necessary. Individual tests should add it when - # necessary. - # def defaults(self): - # """Pulse defaults""" - # return self._defaults - - @property - def max_circuits(self): - return 300 - - @property - def target(self): - return self._target - - -class MockRestlessBackend(FakeOpenPulse2QV2): +class MockRestlessBackend(GenericBackendV2): """An abstract backend for testing that can mock restless data.""" def __init__(self, rng_seed: int = 0): """ Initialize the backend. """ - self._rng = np.random.default_rng(rng_seed) + self.__rng = np.random.default_rng(rng_seed) self._precomputed_probabilities = None - super().__init__() + super().__init__(num_qubits=2, calibrate_instructions=True, seed=rng_seed) @classmethod def _default_options(cls): @@ -135,10 +89,10 @@ def run(self, run_input, **options): self._compute_outcome_probabilities(run_input) - if run_input[0].num_qubits != 2: + if run_input[0].num_qubits != 1: raise DataProcessorError(f"{self.__class__.__name__} is a two qubit mock device.") - prev_outcome, state_strings = "00", self._get_state_strings(2) + prev_outcome, state_strings = "0", self._get_state_strings(1) # Setup the list of dicts where each dict corresponds to a circuit. sorted_memory = [{"memory": [], "metadata": circ.metadata} for circ in run_input] @@ -147,7 +101,7 @@ def run(self, run_input, **options): for circ_idx, _ in enumerate(run_input): probs = self._precomputed_probabilities[(circ_idx, prev_outcome)] # Generate the next shot dependent on the pre-computed probabilities. - outcome = self._rng.choice(state_strings, p=probs) + outcome = self.__rng.choice(state_strings, p=probs) # Append the single shot to the memory of the corresponding circuit. sorted_memory[circ_idx]["memory"].append(hex(int(outcome, 2))) @@ -155,7 +109,7 @@ def run(self, run_input, **options): for idx, circ in enumerate(run_input): counts = {} - for key1, key2 in zip(["00", "01", "10", "11"], ["0x0", "0x1", "0x2", "0x3"]): + for key1, key2 in zip(["0", "1"], ["0x0", "0x1"]): counts[key1] = sorted_memory[idx]["memory"].count(key2) run_result = { "shots": shots, @@ -192,9 +146,6 @@ def __init__( self._angle_per_gate = angle_per_gate super().__init__(rng_seed=rng_seed) - self.target.add_instruction(SXGate(), properties={(0,): None}) - self.target.add_instruction(XGate(), properties={(0,): None}) - def _compute_outcome_probabilities(self, circuits: List[QuantumCircuit]): """Compute the probabilities of being in the excited state or ground state for all circuits.""" @@ -215,11 +166,11 @@ def _compute_outcome_probabilities(self, circuits: List[QuantumCircuit]): prob_1 = np.sin(angle / 2) ** 2 prob_0 = 1 - prob_1 - self._precomputed_probabilities[(idx, "00")] = [prob_0, prob_1, 0, 0] - self._precomputed_probabilities[(idx, "01")] = [prob_1, prob_0, 0, 0] + self._precomputed_probabilities[(idx, "0")] = [prob_0, prob_1] + self._precomputed_probabilities[(idx, "1")] = [prob_1, prob_0] -class MockIQBackend(FakeOpenPulse2QV2): +class MockIQBackend(GenericBackendV2): """A mock backend for testing with IQ data.""" def __init__( @@ -238,9 +189,10 @@ def __init__( """ self._experiment_helper = experiment_helper - self._rng = np.random.default_rng(rng_seed) + # Can not be called _rng because GenericBackendV2 sets a _rng attribute + self.__rng = np.random.default_rng(rng_seed) - super().__init__() + super().__init__(num_qubits=2, calibrate_instructions=True, seed=rng_seed) @classmethod def _default_options(cls): @@ -323,7 +275,7 @@ def _get_normal_samples_for_shot( Returns: Ndarray: A numpy array with values that were produced from normal distribution. """ - samples = [self._rng.normal(0, 1, size=1) for qubit in qubits] + samples = [self.__rng.normal(0, 1, size=1) for qubit in qubits] # we squeeze the second dimension because samples is List[qubit_number][0][0\1] = I\Q # and we want to change it to be List[qubit_number][0\1] return np.squeeze(np.array(samples), axis=1) @@ -396,7 +348,7 @@ def _draw_iq_shots( shot_num = 0 for output_number, number_of_occurrences in enumerate( - self._rng.multinomial(shots, prob, size=1)[0] + self.__rng.multinomial(shots, prob, size=1)[0] ): state_str = str(format(output_number, "b").zfill(len(circ_qubits))) for _ in range(number_of_occurrences): @@ -451,7 +403,7 @@ def _generate_data( if meas_level == MeasLevel.CLASSIFIED: counts = {} - results = self._rng.multinomial(shots, prob_arr, size=1)[0] + results = self.__rng.multinomial(shots, prob_arr, size=1)[0] for result, num_occurrences in enumerate(results): result_in_str = str(format(result, "b").zfill(output_length)) counts[result_in_str] = num_occurrences @@ -551,6 +503,7 @@ def __init__( helper classes for each experiment. rng_seed: The random seed value. """ + self.__rng = np.random.default_rng(rng_seed) super().__init__(experiment_helper, rng_seed) @property @@ -634,7 +587,7 @@ def _parallel_draw_iq_shots( shot_num = 0 for output_number, number_of_occurrences in enumerate( - self._rng.multinomial(shots, prob, size=1)[0] + self.__rng.multinomial(shots, prob, size=1)[0] ): state_str = str(format(output_number, "b").zfill(len(qubits))) for _ in range(number_of_occurrences): diff --git a/test/calibration/test_base_calibration_experiment.py b/test/calibration/test_base_calibration_experiment.py index e924ff3992..3d56b141aa 100644 --- a/test/calibration/test_base_calibration_experiment.py +++ b/test/calibration/test_base_calibration_experiment.py @@ -349,7 +349,6 @@ def test_transpiled_circuits_no_coupling_map(self): # Build a circuit to be passed through transpilation pipeline qc = QuantumCircuit(1, 1) - qc.x(0) qc.measure(0, 0) exp = MockCalExperiment( diff --git a/test/data_processing/test_restless_experiment.py b/test/data_processing/test_restless_experiment.py index 7a02e3768e..ac53f81c5a 100644 --- a/test/data_processing/test_restless_experiment.py +++ b/test/data_processing/test_restless_experiment.py @@ -82,7 +82,7 @@ def test_end_to_end_restless_standard_processor(self, pi_ratio): amp_exp = FineXAmplitude([0], backend) # standard data processor. - standard_processor = DataProcessor("counts", [Probability("01")]) + standard_processor = DataProcessor("counts", [Probability("1")]) amp_exp.analysis.set_options(data_processor=standard_processor) # enable a restless measurement setting. amp_exp.enable_restless(rep_delay=1e-6, override_processor_by_restless=False) diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index c1667a3f60..2502a62672 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -66,7 +66,7 @@ def test_parallel_options(self): self.assertEqual(par_exp.experiment_options, par_exp._default_experiment_options()) self.assertEqual(par_exp.run_options, Options(meas_level=2)) - self.assertEqual(par_exp.transpile_options, Options(optimization_level=0)) + self.assertEqual(par_exp.transpile_options, par_exp._default_transpile_options()) self.assertEqual(par_exp.analysis.options, par_exp.analysis._default_options()) with self.assertWarns(UserWarning): diff --git a/test/library/calibration/test_drag.py b/test/library/calibration/test_drag.py index 1c1f651028..2b89a9017c 100644 --- a/test/library/calibration/test_drag.py +++ b/test/library/calibration/test_drag.py @@ -46,7 +46,7 @@ def setUp(self): pulse.play(Drag(duration=160, amp=0.208519, sigma=40, beta=beta), DriveChannel(0)) self.x_plus = xp - self.test_tol = 0.1 + self.test_tol = 0.25 @data( (None, None, None), @@ -63,7 +63,7 @@ def test_end_to_end(self, freq, betas, p0_opt): backend = MockIQBackend(drag_experiment_helper) drag = RoughDrag([1], self.x_plus) - drag.set_run_options(shots=200) + drag.set_run_options(shots=500) if betas is not None: drag.set_experiment_options(betas=betas) diff --git a/test/library/calibration/test_fine_amplitude.py b/test/library/calibration/test_fine_amplitude.py index ea87c992b0..1a135d7070 100644 --- a/test/library/calibration/test_fine_amplitude.py +++ b/test/library/calibration/test_fine_amplitude.py @@ -46,10 +46,9 @@ def test_end_to_end_under_rotation(self, pi_ratio): error = -np.pi * pi_ratio backend = MockIQBackend(FineAmpHelper(error, np.pi, "x")) - backend.target.add_instruction(XGate(), properties={(0,): None}) - backend.target.add_instruction(SXGate(), properties={(0,): None}) - expdata = amp_exp.run(backend) + # Needs extra shots to avoid chisq > 3 + expdata = amp_exp.run(backend, shots=1600) self.assertExperimentDone(expdata) result = expdata.analysis_results("d_theta") d_theta = result.value.n @@ -67,8 +66,6 @@ def test_end_to_end_over_rotation(self, pi_ratio): error = np.pi * pi_ratio backend = MockIQBackend(FineAmpHelper(error, np.pi, "x")) - backend.target.add_instruction(XGate(), properties={(0,): None}) - backend.target.add_instruction(SXGate(), properties={(0,): None}) expdata = amp_exp.run(backend) self.assertExperimentDone(expdata) result = expdata.analysis_results("d_theta") @@ -99,7 +96,8 @@ def test_end_to_end(self, pi_ratio): backend = MockIQBackend(FineAmpHelper(error, np.pi / 2, "szx")) backend.target.add_instruction(Gate("szx", 2, []), properties={(0, 1): None}) - expdata = amp_exp.run(backend) + # Needs extra shots to avoid chisq > 3 + expdata = amp_exp.run(backend, shots=1600) self.assertExperimentDone(expdata) result = expdata.analysis_results("d_theta") d_theta = result.value.n @@ -218,8 +216,6 @@ def setUp(self): library = FixedFrequencyTransmon() self.backend = MockIQBackend(FineAmpHelper(-np.pi * 0.07, np.pi, "xp")) - self.backend.target.add_instruction(SXGate(), properties={(0,): None}) - self.backend.target.add_instruction(XGate(), properties={(0,): None}) self.cals = Calibrations.from_backend(self.backend, libraries=[library]) def test_cal_options(self): diff --git a/test/library/calibration/test_ramsey_xy.py b/test/library/calibration/test_ramsey_xy.py index 693e8b48ca..c89338be45 100644 --- a/test/library/calibration/test_ramsey_xy.py +++ b/test/library/calibration/test_ramsey_xy.py @@ -56,7 +56,7 @@ def test_end_to_end(self, freq_shift: float): This test also checks that we can pickup frequency shifts with different signs. """ - test_tol = 0.03 + test_tol = 0.05 abs_tol = max(1e3, abs(freq_shift) * test_tol) exp_helper = RamseyXYHelper() diff --git a/test/library/characterization/test_half_angle.py b/test/library/characterization/test_half_angle.py index 7e2359b292..bc83c91265 100644 --- a/test/library/characterization/test_half_angle.py +++ b/test/library/characterization/test_half_angle.py @@ -13,9 +13,8 @@ """Test the half angle experiment.""" from test.base import QiskitExperimentsTestCase -import copy -from qiskit import pulse, transpile +from qiskit import pulse from qiskit.pulse import InstructionScheduleMap from qiskit_ibm_runtime.fake_provider import FakeAthens @@ -52,13 +51,11 @@ def test_circuits(self): for inst in ["sx", "x"]: inst_map.add(inst, (qubit,), pulse.Schedule(name=inst)) - hac = HalfAngle([qubit]) + hac = HalfAngle([qubit], backend=FakeAthens()) hac.set_transpile_options(inst_map=inst_map) # mimic what will happen in the experiment. - transpile_opts = copy.copy(hac.transpile_options.__dict__) - transpile_opts["initial_layout"] = list(hac._physical_qubits) - circuits = transpile(hac.circuits(), FakeAthens(), **transpile_opts) + circuits = hac._transpiled_circuits() for idx, circ in enumerate(circuits): self.assertEqual(circ.count_ops()["sx"], idx * 2 + 2) diff --git a/test/library/characterization/test_qubit_spectroscopy.py b/test/library/characterization/test_qubit_spectroscopy.py index ae8fc8bde8..c190b807ed 100644 --- a/test/library/characterization/test_qubit_spectroscopy.py +++ b/test/library/characterization/test_qubit_spectroscopy.py @@ -15,7 +15,6 @@ import numpy as np from qiskit.qobj.utils import MeasLevel -from qiskit.circuit.library import XGate from qiskit_ibm_runtime.fake_provider import FakeWashingtonV2 from qiskit_experiments.framework import ParallelExperiment @@ -42,7 +41,6 @@ def test_spectroscopy_end2end_classified(self): backend = MockIQBackend( experiment_helper=exp_helper, ) - backend.target.add_instruction(XGate(), properties={(0,): None}) qubit = 1 freq01 = BackendData(backend).drive_freqs[qubit] @@ -82,7 +80,6 @@ def test_spectroscopy_end2end_kerneled(self): backend = MockIQBackend( experiment_helper=exp_helper, ) - backend.target.add_instruction(XGate(), properties={(0,): None}) qubit = 0 freq01 = BackendData(backend).drive_freqs[qubit] @@ -128,7 +125,6 @@ def test_spectroscopy12_end2end_classified(self): iq_cluster_width=[0.2], ), ) - backend.target.add_instruction(XGate(), properties={(0,): None}) qubit = 0 freq01 = BackendData(backend).drive_freqs[qubit] frequencies = np.linspace(freq01 - 10.0e6, freq01 + 10.0e6, 21) @@ -174,7 +170,6 @@ def test_expdata_serialization(self): backend = MockIQBackend( experiment_helper=exp_helper, ) - backend.target.add_instruction(XGate(), properties={(0,): None}) qubit = 1 freq01 = BackendData(backend).drive_freqs[qubit] @@ -201,7 +196,6 @@ def test_kerneled_expdata_serialization(self): backend = MockIQBackend( experiment_helper=exp_helper, ) - backend.target.add_instruction(XGate(), properties={(0,): None}) qubit = 1 freq01 = BackendData(backend).drive_freqs[qubit] @@ -231,10 +225,6 @@ def test_parallel_experiment(self): experiment_helper=None, rng_seed=0, ) - parallel_backend.target.add_instruction( - XGate(), - properties={(0,): None, (1,): None}, - ) # experiment hyper parameters qubit1 = 0 diff --git a/test/library/characterization/test_resonator_spectroscopy.py b/test/library/characterization/test_resonator_spectroscopy.py index 38b53c82da..bd612ad3b6 100644 --- a/test/library/characterization/test_resonator_spectroscopy.py +++ b/test/library/characterization/test_resonator_spectroscopy.py @@ -12,6 +12,8 @@ """Spectroscopy tests for resonator spectroscopy experiment.""" +from __future__ import annotations + from test.base import QiskitExperimentsTestCase from typing import Any, List, Tuple @@ -34,6 +36,13 @@ ) +class MockDefaults: + """Just enough qiskit.providers.models.PulseDefaults for ResonatorSpectroscpy""" + + def __init__(self, meas_freq_est: list[float]): + self.meas_freq_est = meas_freq_est + + class MockIQBackendDefaults(MockIQBackend): """MockIQBackend with defaults() method""" @@ -45,7 +54,7 @@ def defaults(self): to Backend classes outside of this test module so that we do not introduce new dependencies on it. """ - return self._defaults + return MockDefaults(meas_freq_est=[7e9] * self.num_qubits) class MockIQParallelBackendDefaults(MockIQParallelBackend): @@ -59,7 +68,7 @@ def defaults(self): to Backend classes outside of this test module so that we do not introduce new dependencies on it. """ - return self._defaults + return MockDefaults(meas_freq_est=[7e9] * self.num_qubits) def data_valid_initial_circuits() -> List[Tuple[Any, str]]: @@ -307,10 +316,10 @@ def test_initial_circuit_transpiled(self): # Check depths and widths for transpiled circuits initial_circuit_depth = initial_circuit.depth() for circ in res_spec_no_initial._transpiled_circuits(): - self.assertEqual( + self.assertGreaterEqual( circ.width(), # Width is the number of qubits + 1 classical bit. - backend.num_qubits + 1, + 2, msg="Transpiled circuit width was not as expected.", ) self.assertEqual( @@ -319,10 +328,10 @@ def test_initial_circuit_transpiled(self): msg="Transpiled circuit depth was not as expected.", ) for circ in res_spec_initial._transpiled_circuits(): - self.assertEqual( + self.assertGreaterEqual( circ.width(), # Width is the number of qubits + 1 classical bit. - backend.num_qubits + 1, + 2, msg="Transpiled circuit, with initial_circuit, width was not as expected.", ) self.assertEqual( diff --git a/test/library/characterization/test_zz_ramsey.py b/test/library/characterization/test_zz_ramsey.py index 10b12950a0..754329d633 100644 --- a/test/library/characterization/test_zz_ramsey.py +++ b/test/library/characterization/test_zz_ramsey.py @@ -49,7 +49,7 @@ def compute_probabilities(self, circuits: List[QuantumCircuit]) -> List[Dict[str freq = (-1 * self.zz_freq) / 2 else: freq = self.zz_freq / 2 - rz, _, _ = next(i for i in circuit.data if i[0].name == "u1") + rz, _, _ = next(i for i in circuit.data if i[0].name == "rz") phase = float(rz.params[0]) prob1 = 0.5 - 0.5 * np.cos(2 * np.pi * freq * delay + phase)