diff --git a/docs/manuals/measurement/restless_measurements.rst b/docs/manuals/measurement/restless_measurements.rst index 86f2143357..d42821fe94 100644 --- a/docs/manuals/measurement/restless_measurements.rst +++ b/docs/manuals/measurement/restless_measurements.rst @@ -52,7 +52,8 @@ In Qiskit Experiments, the experiments that support restless measurements have a special method :meth:`~.RestlessMixin.enable_restless` to set the restless run options and define the data processor that will process the measured data. If you are an experiment developer, you can add the :class:`.RestlessMixin` -to your experiment class to add support for restless measurements. +to your experiment class to add support for restless measurements. For correct method resolution order, +:class:`.RestlessMixin` should be the first base class your class will inherit from. Here, we will show how to activate restless measurements using a fake backend and a rough DRAG experiment. Note however, that you will not observe any meaningful outcomes with fake backends since the circuit simulator @@ -89,9 +90,11 @@ they use always starts with the qubits in the ground state. As you can see, a restless data processor is automatically chosen for the experiment. This data processor post-processes the restless measured shots according to the order in which -they were acquired. Furthermore, the appropriate run options are also set. Note that -these run options might be unique to IBM Quantum providers. Therefore, execute may fail -on non-IBM Quantum providers if the required options are not supported. +tthey were acquired. Furthermore, the appropriate run options are also set. Those run options would also +override new run options that will be set afterward. To disable this override, one should set the +option `restless` to `False`. Note that these run options might be unique to IBM Quantum providers. +Therefore, execute may fail on non-IBM Quantum providers if the required options are not supported. + After calling :meth:`~.RestlessMixin.enable_restless` the experiment is ready to be run in a restless mode. With a hardware backend, this would be done by calling the diff --git a/qiskit_experiments/framework/base_experiment.py b/qiskit_experiments/framework/base_experiment.py index 41240df41c..3768797646 100644 --- a/qiskit_experiments/framework/base_experiment.py +++ b/qiskit_experiments/framework/base_experiment.py @@ -451,7 +451,7 @@ def set_transpile_options(self, **fields): @classmethod def _default_run_options(cls) -> Options: """Default options values for the experiment :meth:`run` method.""" - return Options(meas_level=MeasLevel.CLASSIFIED) + return Options(meas_level=MeasLevel.CLASSIFIED, restless=False) @property def run_options(self) -> Options: diff --git a/qiskit_experiments/framework/restless_mixin.py b/qiskit_experiments/framework/restless_mixin.py index 19d65c238f..3e24f15127 100644 --- a/qiskit_experiments/framework/restless_mixin.py +++ b/qiskit_experiments/framework/restless_mixin.py @@ -34,8 +34,10 @@ class RestlessMixin: This class defines the following methods: - :meth:`~.RestlessMixin.enable_restless` + - :meth:`~.RestlessMixin._enable_restless` - :meth:`~.RestlessMixin._get_restless_processor` - :meth:`~.RestlessMixin._t1_check` + - :meth:`~.RestlessMixin._finalize` A restless enabled experiment is an experiment that can be run in a restless measurement setting. In restless measurements, the qubit is not reset after @@ -61,19 +63,28 @@ class makes it easy to determine if restless measurements are supported for a gi analysis: BaseAnalysis _default_run_options: Options() + run_options: Options() set_run_options: Callable _backend: Backend _physical_qubits: Sequence[int] _num_qubits: int + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + # self._enable_restless = False + self.rep_delay = None + self.override_processor_by_restless = None + self.suppress_t1_error = None + def enable_restless( self, rep_delay: Optional[float] = None, override_processor_by_restless: bool = True, suppress_t1_error: bool = False, ): - """Enables a restless experiment by setting the restless run options and the - restless data processor. + """ + Set a flag to enable restless configuration when running the experiment. If the flag is set to + `True`, the program will run `_enable_restless` method in the `_finilized` step. Args: rep_delay: The repetition delay. This is the delay between a measurement @@ -88,6 +99,26 @@ def enable_restless( ``rep_delay`` is larger than the T1 times of the qubits. Instead, a warning will be logged as restless measurements may have a large amount of noise. + """ + + self.set_run_options(restless=True) + if rep_delay: + self.rep_delay = rep_delay + if override_processor_by_restless: + self.override_processor_by_restless = override_processor_by_restless + if suppress_t1_error: + self.suppress_t1_error = suppress_t1_error + + # Calling `_enable_restless()` so the run option will be visible to the user. + self._enable_restless() + # Setting override_processor_by_restless to false because + # the data processor was already configured. + self.override_processor_by_restless = False + + def _enable_restless(self): + """Enables a restless experiment by setting the restless run options and the + restless data processor. + Raises: DataProcessorError: If the attribute rep_delay_range is not defined for the backend. DataProcessorError: If a data processor has already been set but @@ -98,10 +129,11 @@ def enable_restless( T1 time of one of the physical qubits in the experiment and the flag ``ignore_t1_check`` is False. """ + LOG.debug("Enabling restless configuration. This will override current configuration.") try: - if not rep_delay: + if not self.rep_delay: # BackendV1 only; BackendV2 does not support this - rep_delay = self._backend.configuration().rep_delay_range[0] + self.rep_delay = self._backend.configuration().rep_delay_range[0] except AttributeError as error: raise DataProcessorError( "The restless experiment can not be enabled because " @@ -110,15 +142,15 @@ def enable_restless( ) from error # Check the rep_delay compared to the T1 time. - if not self._t1_check(rep_delay): + if not self._t1_check(): msg = ( - f"The specified repetition delay {rep_delay} is equal to or greater " + f"The specified repetition delay {self.rep_delay} is equal to or greater " f"than the T1 time of one of the physical qubits" f"{self._physical_qubits} in the experiment. Consider choosing " f"a smaller repetition delay for the restless experiment." ) - if suppress_t1_error: + if self.suppress_t1_error: LOG.warning(msg) else: raise DataProcessorError(msg) @@ -129,7 +161,7 @@ def enable_restless( meas_return = self._default_run_options().get("meas_return", MeasReturnType.SINGLE) if not self.analysis.options.get("data_processor", None): self.set_run_options( - rep_delay=rep_delay, + rep_delay=self.rep_delay, init_qubits=False, memory=True, meas_level=meas_level, @@ -146,9 +178,9 @@ def enable_restless( "does not have the data_processor option." ) else: - if not override_processor_by_restless: + if not self.override_processor_by_restless: self.set_run_options( - rep_delay=rep_delay, + rep_delay=self.rep_delay, init_qubits=False, memory=True, meas_level=meas_level, @@ -188,13 +220,9 @@ def _get_restless_processor(self, meas_level: int = MeasLevel.CLASSIFIED) -> Dat ], ) - def _t1_check(self, rep_delay: float) -> bool: + def _t1_check(self) -> bool: """Check that repetition delay < T1 of the physical qubits in the experiment. - Args: - rep_delay: The repetition delay. This is the delay between a measurement - and the subsequent quantum circuit. - Returns: True if the repetition delay is smaller than the qubit T1 times. @@ -209,7 +237,7 @@ def _t1_check(self, rep_delay: float) -> bool: for physical_qubit in self._physical_qubits ] - if all(rep_delay / t1_value < 1.0 for t1_value in t1_values): + if all(self.rep_delay / t1_value < 1.0 for t1_value in t1_values): return True except AttributeError as error: raise DataProcessorError( @@ -218,3 +246,9 @@ def _t1_check(self, rep_delay: float) -> bool: ) from error return False + + def _finalize(self): + # Calling again to self._enable_restless() to override experiment option for restless experiment. + if self.run_options.get("restless", None): + self._enable_restless() + super()._finalize() diff --git a/qiskit_experiments/library/characterization/drag.py b/qiskit_experiments/library/characterization/drag.py index 6a338212e8..2c00edda7d 100644 --- a/qiskit_experiments/library/characterization/drag.py +++ b/qiskit_experiments/library/characterization/drag.py @@ -26,7 +26,7 @@ from qiskit_experiments.library.characterization.analysis import DragCalAnalysis -class RoughDrag(BaseExperiment, RestlessMixin): +class RoughDrag(RestlessMixin, BaseExperiment): r"""An experiment that scans the DRAG parameter to find the optimal value. # section: overview diff --git a/qiskit_experiments/library/characterization/fine_amplitude.py b/qiskit_experiments/library/characterization/fine_amplitude.py index 69232e2e48..937bcc502a 100644 --- a/qiskit_experiments/library/characterization/fine_amplitude.py +++ b/qiskit_experiments/library/characterization/fine_amplitude.py @@ -25,7 +25,7 @@ from qiskit_experiments.library.characterization.analysis import FineAmplitudeAnalysis -class FineAmplitude(BaseExperiment, RestlessMixin): +class FineAmplitude(RestlessMixin, BaseExperiment): r"""An experiment to determine the optimal pulse amplitude by amplifying gate errors. # section: overview diff --git a/qiskit_experiments/library/characterization/fine_drag.py b/qiskit_experiments/library/characterization/fine_drag.py index 24dafb060c..a1fca017ba 100644 --- a/qiskit_experiments/library/characterization/fine_drag.py +++ b/qiskit_experiments/library/characterization/fine_drag.py @@ -24,7 +24,7 @@ from qiskit_experiments.curve_analysis.standard_analysis import ErrorAmplificationAnalysis -class FineDrag(BaseExperiment, RestlessMixin): +class FineDrag(RestlessMixin, BaseExperiment): r"""An experiment that performs fine characterizations of DRAG pulse coefficients. # section: overview diff --git a/qiskit_experiments/library/characterization/rabi.py b/qiskit_experiments/library/characterization/rabi.py index 3fb2a682d6..bc165f05f1 100644 --- a/qiskit_experiments/library/characterization/rabi.py +++ b/qiskit_experiments/library/characterization/rabi.py @@ -27,7 +27,7 @@ from qiskit_experiments.curve_analysis import ParameterRepr, OscillationAnalysis -class Rabi(BaseExperiment, RestlessMixin): +class Rabi(RestlessMixin, BaseExperiment): r"""An experiment that scans a pulse amplitude to calibrate rotations on the :math:`|0\rangle` <-> :math:`|1\rangle` transition. diff --git a/qiskit_experiments/library/characterization/ramsey_xy.py b/qiskit_experiments/library/characterization/ramsey_xy.py index ccb1987481..22f0dbae19 100644 --- a/qiskit_experiments/library/characterization/ramsey_xy.py +++ b/qiskit_experiments/library/characterization/ramsey_xy.py @@ -23,7 +23,7 @@ from qiskit_experiments.library.characterization.analysis import RamseyXYAnalysis -class RamseyXY(BaseExperiment, RestlessMixin): +class RamseyXY(RestlessMixin, BaseExperiment): r"""A sign-sensitive experiment to measure the frequency of a qubit. # section: overview diff --git a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py index 9a7795155a..40c2ee43d6 100644 --- a/qiskit_experiments/library/randomized_benchmarking/standard_rb.py +++ b/qiskit_experiments/library/randomized_benchmarking/standard_rb.py @@ -53,7 +53,7 @@ SequenceElementType = Union[Clifford, Integral, QuantumCircuit] -class StandardRB(BaseExperiment, RestlessMixin): +class StandardRB(RestlessMixin, BaseExperiment): """An experiment to characterize the error rate of a gate set on a device. # section: overview diff --git a/releasenotes/notes/inforcing-restless-options-9a659248108c2533.yaml b/releasenotes/notes/inforcing-restless-options-9a659248108c2533.yaml new file mode 100644 index 0000000000..5218f8dcd2 --- /dev/null +++ b/releasenotes/notes/inforcing-restless-options-9a659248108c2533.yaml @@ -0,0 +1,7 @@ +--- +features: + - | + `RestlessMixin` class will now override the experiment run option at `_finilize()` method. + `RestlessMixin` class should be the first base class that an experiment inherits from for correct + MRO (Method Resolution Order). To cancel this override, set `restless` to False in the experiment + options. diff --git a/test/data_processing/test_restless_experiment.py b/test/data_processing/test_restless_experiment.py index 7a02e3768e..87c21c4979 100644 --- a/test/data_processing/test_restless_experiment.py +++ b/test/data_processing/test_restless_experiment.py @@ -37,9 +37,11 @@ def test_enable_restless(self): error = -np.pi * 0.01 backend = MockRestlessFineAmp(error, np.pi, "x") + amp_exp = FineXAmplitude([0], backend) with self.assertRaises(DataProcessorError): - FineXAmplitude([0], backend).enable_restless(rep_delay=2.0) + amp_exp.enable_restless(rep_delay=2.0) + amp_exp.run() amp_exp = FineXAmplitude([0], backend) amp_exp.enable_restless(rep_delay=2.0, suppress_t1_error=True) diff --git a/test/fake_experiment.py b/test/fake_experiment.py index 80236e8e74..d992dd3bc1 100644 --- a/test/fake_experiment.py +++ b/test/fake_experiment.py @@ -69,13 +69,15 @@ def _default_experiment_options(cls) -> Options: options.dummyoption = None return options - def __init__(self, physical_qubits=None, backend=None, experiment_type=None): + def __init__(self, physical_qubits=None, backend=None, analysis=None, experiment_type=None): """Initialise the fake experiment.""" if physical_qubits is None: physical_qubits = [0] + if analysis is None: + analysis = FakeAnalysis() super().__init__( physical_qubits, - analysis=FakeAnalysis(), + analysis=analysis, backend=backend, experiment_type=experiment_type, ) diff --git a/test/framework/test_composite.py b/test/framework/test_composite.py index c1667a3f60..c43135014f 100644 --- a/test/framework/test_composite.py +++ b/test/framework/test_composite.py @@ -65,7 +65,7 @@ def test_parallel_options(self): par_exp = ParallelExperiment([exp0, exp2], flatten_results=False) 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.run_options, Options(meas_level=2, restless=False)) self.assertEqual(par_exp.transpile_options, Options(optimization_level=0)) self.assertEqual(par_exp.analysis.options, par_exp.analysis._default_options()) diff --git a/test/framework/test_framework.py b/test/framework/test_framework.py index 1a2ae395d3..904b6db793 100644 --- a/test/framework/test_framework.py +++ b/test/framework/test_framework.py @@ -22,7 +22,12 @@ from qiskit import QuantumCircuit from qiskit.providers.jobstatus import JobStatus from qiskit.exceptions import QiskitError +from qiskit.qobj.utils import MeasLevel +from qiskit.result import Result + + from qiskit_ibm_runtime.fake_provider import FakeVigoV2 +import qiskit_experiments.data_processing as dp from qiskit_experiments.database_service import Qubit from qiskit_experiments.exceptions import AnalysisError @@ -33,6 +38,8 @@ BaseAnalysis, AnalysisResultData, AnalysisStatus, + RestlessMixin, + Options, ) from qiskit_experiments.test.fake_backend import FakeBackend from qiskit_experiments.test.utils import FakeJob @@ -426,3 +433,79 @@ def circuits(self): self.assertEqual(exp2.experiment_type, "MyExp") exp2.experiment_type = "suieee" self.assertEqual(exp2.experiment_type, "suieee") + + def test_restless_experiment_options(self): + """Test override of experiment option in restless experiment.""" + # pylint: disable-next=redefined-outer-name, reimported + from qiskit_experiments.test.utils import FakeJob + import uuid + + class FakeRestlessBackend(FakeVigoV2): + """Fake backend for restless experiment""" + + def run(self, run_input, **options): + result = { + "backend_name": "fake_backend", + "backend_version": "0", + "qobj_id": uuid.uuid4().hex, + "job_id": uuid.uuid4().hex, + "success": True, + "results": [], + } + return FakeJob(backend=self, result=Result.from_dict(result)) + + class FakeRestlessAnalysis(FakeAnalysis): + """Fake analysis class for fake restless experiment. We need this for the data processor.""" + + @classmethod + def _default_options(cls) -> Options: + """Default analysis options.""" + options = super()._default_options() + + options.update_options( + data_processor=dp.DataProcessor("counts", [dp.Probability("1")]), + stark_coefficients="latest", + x_key="xval", + ) + return options + + class FakeRestlessExperiment(RestlessMixin, FakeExperiment): + """Fake restless experiment for testing.""" + + def __init__( + self, physical_qubits=None, backend=None, experiment_type="FakeRestlessExperiment" + ): + """Initialise the fake experiment.""" + if physical_qubits is None: + physical_qubits = [0] + super().__init__( + physical_qubits, + analysis=FakeRestlessAnalysis(), + backend=backend, + experiment_type=experiment_type, + ) + + @classmethod + def _default_run_options(cls) -> Options: + """Default option values for the experiment :meth:`run` method.""" + options = super()._default_run_options() + + options.meas_level = MeasLevel.KERNELED + options.meas_return = "single" + + return options + + backend = FakeRestlessBackend() + exp = FakeRestlessExperiment(backend=backend) + + exp.enable_restless( + override_processor_by_restless=False, rep_delay=250, suppress_t1_error=True + ) + exp.run_options.meas_level = MeasLevel.CLASSIFIED + self.assertNotEqual(exp.run_options.get("meas_level"), MeasLevel.KERNELED) + + # `run()` method makes a copy of the experiment if run option is passed through it, and we cannot + # access it for testing. + # pylint: disable-next=unused-variable + expdata = exp.run() + self.assertEqual(exp.run_options.get("meas_level"), MeasLevel.KERNELED)