Skip to content

Commit

Permalink
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into upgr…
Browse files Browse the repository at this point in the history
…ade/cleanup-curve-analysis-add-fit-model
  • Loading branch information
nkanazawa1989 committed Mar 14, 2022
2 parents 4685e4e + aa716c1 commit 282a1fb
Show file tree
Hide file tree
Showing 19 changed files with 877 additions and 57 deletions.
57 changes: 50 additions & 7 deletions qiskit_experiments/curve_analysis/curve_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
ExperimentData,
AnalysisResultData,
Options,
AnalysisConfig,
)

PARAMS_ENTRY_PREFIX = "@Parameters_"
Expand Down Expand Up @@ -236,9 +237,6 @@ class AnalysisExample(CurveAnalysis):
#: List[SeriesDef]: List of mapping representing a data series
__series__ = list()

#: List[str]: Fixed parameter in fit function. Value should be set to the analysis options.
__fixed_parameters__ = list()

# Automatically generated fitting functions of child class
_fit_model = None

Expand Down Expand Up @@ -276,6 +274,20 @@ def __init__(self):
"""Initialize data fields that are privately accessed by methods."""
super().__init__()

if hasattr(self, "__fixed_parameters__"):
warnings.warn(
"The class attribute __fixed_parameters__ has been deprecated and will be removed. "
"Now this attribute is absorbed in analysis options as fixed_parameters. "
"This warning will be dropped in v0.4 along with "
"the support for the deprecated attribute.",
DeprecationWarning,
stacklevel=2,
)
# pylint: disable=no-member
self._options.fixed_parameters = {
p: self.options.get(p, None) for p in self.__fixed_parameters__
}

#: Dict[str, Any]: Experiment metadata
self.__experiment_metadata = None

Expand Down Expand Up @@ -459,6 +471,9 @@ def _default_options(cls) -> Options:
as extra information.
curve_fitter_options (Dict[str, Any]) Options that are passed to the
specified curve fitting function.
fixed_parameters (Dict[str, Any]): Fitting model parameters that are fixed
during the curve fitting. This should be provided with default value
keyed on one of the parameter names in the series definition.
"""
options = super()._default_options()

Expand All @@ -479,10 +494,9 @@ def _default_options(cls) -> Options:
options.style = PlotterStyle()
options.extra = dict()
options.curve_fitter_options = dict()

# automatically populate initial guess and boundary
options.p0 = {par_name: None for par_name in cls._fit_model.signature}
options.bounds = {par_name: None for par_name in cls._fit_model.signature}
options.p0 = {}
options.bounds = {}
options.fixed_parameters = {}

return options

Expand Down Expand Up @@ -1080,6 +1094,35 @@ def _run_analysis(

return analysis_results, figures

@classmethod
def from_config(cls, config: Union[AnalysisConfig, Dict]) -> "CurveAnalysis":
# For backward compatibility. This will be removed in v0.4.

instance = super().from_config(config)

# When fixed param value is hard-coded as options. This is deprecated data structure.
loaded_opts = instance.options.__dict__

# pylint: disable=no-member
deprecated_fixed_params = {
p: loaded_opts[p] for p in instance.parameters if p in loaded_opts
}
if any(deprecated_fixed_params):
warnings.warn(
"Fixed parameter value should be defined in options.fixed_parameters as "
"a dictionary values, rather than a standalone analysis option. "
"Please re-save this experiment to be loaded after deprecation period. "
"This warning will be dropped in v0.4 along with "
"the support for the deprecated fixed parameter options.",
DeprecationWarning,
stacklevel=2,
)
new_fixed_params = instance.options.fixed_parameters
new_fixed_params.update(deprecated_fixed_params)
instance.set_options(fixed_parameters=new_fixed_params)

return instance


def is_error_not_significant(
val: Union[float, uncertainties.UFloat],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,24 +105,15 @@ def _default_options(cls):
descriptions of analysis options.
Analysis Options:
angle_per_gate (float): The ideal angle per repeated gate.
The user must set this option as it defaults to None.
phase_offset (float): A phase offset for the analysis. This phase offset will be
:math:`\pi/2` if the square-root of X gate is added before the repeated gates.
This is decided for the user in :meth:`set_schedule` depending on whether the
sx gate is included in the experiment.
max_good_angle_error (float): The maximum angle error for which the fit is
considered as good. Defaults to :math:`\pi/2`.
"""
default_options = super()._default_options()
default_options.result_parameters = ["d_theta"]
default_options.xlabel = "Number of gates (n)"
default_options.ylabel = "Population"
default_options.angle_per_gate = None
default_options.phase_offset = 0.0
default_options.max_good_angle_error = np.pi / 2
default_options.amp = 1.0
default_options.ylim = [0, 1.0]
default_options.max_good_angle_error = np.pi / 2

return default_options

Expand All @@ -140,6 +131,8 @@ def _generate_fit_guesses(
Raises:
CalibrationError: When ``angle_per_gate`` is missing.
"""
fixed_params = self.options.fixed_parameters

curve_data = self._data()
max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True)
max_y, min_y = np.max(curve_data.y), np.min(curve_data.y)
Expand All @@ -152,16 +145,19 @@ def _generate_fit_guesses(
if "amp" in user_opt.p0:
user_opt.p0.set_if_empty(amp=max_y - min_y)
user_opt.bounds.set_if_empty(amp=(0, 2 * max_abs_y))
amp = user_opt.p0["amp"]
else:
# Fixed parameter
amp = fixed_params.get("amp", 1.0)

# Base the initial guess on the intended angle_per_gate and phase offset.
apg = self.options.angle_per_gate
phi = self.options.phase_offset
apg = user_opt.p0.get("angle_per_gate", fixed_params.get("angle_per_gate", 0.0))
phi = user_opt.p0.get("phase_offset", fixed_params.get("phase_offset", 0.0))

# Prepare logical guess for specific condition (often satisfied)
d_theta_guesses = []

offsets = apg * curve_data.x + phi
amp = user_opt.p0.get("amp", self.options.amp)
for i in range(curve_data.x.size):
xi = curve_data.x[i]
yi = curve_data.y[i]
Expand Down
175 changes: 175 additions & 0 deletions qiskit_experiments/framework/restless_mixin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
# 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.

"""Restless mixin class."""

from typing import Callable, Sequence, Optional

from qiskit.providers import Backend
from qiskit_experiments.data_processing.data_processor import DataProcessor
from qiskit_experiments.data_processing.exceptions import DataProcessorError
from qiskit_experiments.data_processing import nodes
from qiskit_experiments.framework.base_analysis import BaseAnalysis


class RestlessMixin:
"""A mixin to facilitate restless experiments.
This class defines the following methods
- :meth:`enable_restless`
- :meth:`_get_restless_processor`
- :meth:`_t1_check`
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
each measurement. Instead, the outcome of the previous quantum non-demolition
measurement is the initial state for the current circuit. Restless measurements
therefore require special data processing which is provided by sub-classes of
the :code:`RestlessNode`. Restless experiments are a fast alternative for
several calibration and characterization tasks, for details see
https://arxiv.org/pdf/2202.06981.pdf.
This class makes it possible for users to enter a restless run-mode without having
to manually set all the required run options and the data processor. The required options
are ``rep_delay``, ``init_qubits``, ``memory``, and ``meas_level``. Furthermore,
subclasses can override the :meth:`_get_restless_processor` method if they require more
complex restless data processing such as two-qubit calibrations. In addition, this
class makes it easy to determine if restless measurements are supported for a given
experiments.
"""

analysis: BaseAnalysis
set_run_options: Callable
backend: Backend
_physical_qubits: Sequence[int]
_num_qubits: int

def enable_restless(
self, rep_delay: Optional[float] = None, override_processor_by_restless: bool = True
):
"""Enables a restless experiment by setting the restless run options and the
restless data processor.
Args:
rep_delay: The repetition delay. This is the delay between a measurement
and the subsequent quantum circuit. Since the backends have
dynamic repetition rates, the repetition delay can be set to a small
value which is required for restless experiments. Typical values are
1 us or less.
override_processor_by_restless: If False, a data processor that is specified in the
analysis options of the experiment is not overridden by the restless data
processor. The default is True.
Raises:
DataProcessorError: if the attribute rep_delay_range is not defined for the backend.
DataProcessorError: if a data processor has already been set but
override_processor_by_restless is True.
DataProcessorError: if the experiment analysis does not have the data_processor
option.
DataProcessorError: if the rep_delay is equal to or greater than the
T1 time of one of the physical qubits in the experiment.
"""
try:
if not rep_delay:
rep_delay = self.backend.configuration().rep_delay_range[0]
except AttributeError as error:
raise DataProcessorError(
"The restless experiment can not be enabled because "
"the attribute rep_delay_range is not defined for this backend "
"and a minimum rep_delay can not be set."
) from error

# The excited state promotion readout analysis option is set to
# False because it is not compatible with a restless experiment.
if self._t1_check(rep_delay):
if not self.analysis.options.get("data_processor", None):
self.set_run_options(
rep_delay=rep_delay,
init_qubits=False,
memory=True,
meas_level=2,
use_measure_esp=False,
)
if hasattr(self.analysis.options, "data_processor"):
self.analysis.set_options(data_processor=self._get_restless_processor())
else:
raise DataProcessorError(
"The restless data processor can not be set since the experiment analysis"
"does not have the data_processor option."
)
else:
if not override_processor_by_restless:
self.set_run_options(
rep_delay=rep_delay,
init_qubits=False,
memory=True,
meas_level=2,
use_measure_esp=False,
)
else:
raise DataProcessorError(
"Cannot enable restless. Data processor has already been set and "
"override_processor_by_restless is True."
)
else:
raise DataProcessorError(
f"The specified repetition delay {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."
)

def _get_restless_processor(self) -> DataProcessor:
"""Returns the restless experiments data processor.
Notes:
Sub-classes can override this method if they need more complex data processing.
"""
outcome = self.analysis.options.get("outcome", "1" * self._num_qubits)
return DataProcessor(
"memory",
[
nodes.RestlessToCounts(self._num_qubits),
nodes.Probability(outcome),
],
)

def _t1_check(self, rep_delay: float) -> 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.
Raises:
DataProcessorError: if the T1 values are not defined for the qubits of
the used backend.
"""

try:
t1_values = [
self.backend.properties().qubit_property(physical_qubit)["T1"][0]
for physical_qubit in self._physical_qubits
]

if all(rep_delay / t1_value < 1.0 for t1_value in t1_values):
return True
except AttributeError as error:
raise DataProcessorError(
"The restless experiment can not be enabled since "
"T1 values are not defined for the qubits of the used backend."
) from error

return False
13 changes: 8 additions & 5 deletions qiskit_experiments/library/calibration/fine_amplitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,9 +171,10 @@ def __init__(
auto_update=auto_update,
)
self.analysis.set_options(
angle_per_gate=np.pi,
phase_offset=np.pi / 2,
amp=1,
fixed_parameters={
"angle_per_gate": np.pi,
"phase_offset": np.pi / 2,
}
)

@classmethod
Expand Down Expand Up @@ -222,8 +223,10 @@ def __init__(
auto_update=auto_update,
)
self.analysis.set_options(
angle_per_gate=np.pi / 2,
phase_offset=np.pi,
fixed_parameters={
"angle_per_gate": np.pi / 2,
"phase_offset": np.pi,
}
)

@classmethod
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,5 +58,3 @@ class FineAmplitudeAnalysis(ErrorAmplificationAnalysis):
filter_kwargs={"series": 1},
),
]

__fixed_parameters__ = ["angle_per_gate", "phase_offset"]
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Fine DRAG calibration analysis."""

import warnings

import numpy as np
from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis
from qiskit_experiments.framework import Options
Expand All @@ -32,6 +34,16 @@ class FineDragAnalysis(ErrorAmplificationAnalysis):

__fixed_parameters__ = ["angle_per_gate", "phase_offset", "amp"]

def __init__(self):
super().__init__()

warnings.warn(
f"{self.__class__.__name__} has been deprecated. Use ErrorAmplificationAnalysis "
"instance with the analysis options involving the fixed_parameters.",
DeprecationWarning,
stacklevel=2,
)

@classmethod
def _default_options(cls) -> Options:
"""Default analysis options."""
Expand Down
Loading

0 comments on commit 282a1fb

Please sign in to comment.