Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Forcing experiment configuration for restless experiments. #1338

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions docs/manuals/measurement/restless_measurements.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/framework/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
66 changes: 50 additions & 16 deletions qiskit_experiments/framework/restless_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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 "
Expand All @@ -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)
Expand All @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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.

Expand All @@ -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(
Expand All @@ -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()
2 changes: 1 addition & 1 deletion qiskit_experiments/library/characterization/drag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/library/characterization/fine_drag.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/library/characterization/rabi.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/library/characterization/ramsey_xy.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 3 additions & 1 deletion test/data_processing/test_restless_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
6 changes: 4 additions & 2 deletions test/fake_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)
Expand Down
2 changes: 1 addition & 1 deletion test/framework/test_composite.py
Original file line number Diff line number Diff line change
Expand Up @@ -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())

Expand Down
83 changes: 83 additions & 0 deletions test/framework/test_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -33,6 +38,8 @@
BaseAnalysis,
AnalysisResultData,
AnalysisStatus,
RestlessMixin,
Options,
)
from qiskit_experiments.test.fake_backend import FakeBackend
from qiskit_experiments.test.utils import FakeJob
Expand Down Expand Up @@ -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)
Loading