Skip to content
This repository has been archived by the owner on Jul 13, 2022. It is now read-only.

Commit

Permalink
Replace __fixed_parameters__ with analysis option (qiskit-community…
Browse files Browse the repository at this point in the history
…#734)

Add fixed parameters to the curve analysis fit option and remove class attribute __fixed_parameters__
  • Loading branch information
nkanazawa1989 authored Mar 14, 2022
1 parent 8b8d65a commit 7b17674
Show file tree
Hide file tree
Showing 15 changed files with 376 additions and 186 deletions.
88 changes: 60 additions & 28 deletions qiskit_experiments/curve_analysis/curve_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
ExperimentData,
AnalysisResultData,
Options,
AnalysisConfig,
)

PARAMS_ENTRY_PREFIX = "@Parameters_"
Expand Down Expand Up @@ -233,13 +234,24 @@ 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()

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 @@ -271,21 +283,12 @@ def _fit_params(cls) -> List[str]:
)

# remove the first function argument. this is usually x, i.e. not a fit parameter.
fit_params = list(list(fsigs)[0].parameters.keys())[1:]

# remove fixed parameters
if cls.__fixed_parameters__ is not None:
for fixed_param in cls.__fixed_parameters__:
try:
fit_params.remove(fixed_param)
except ValueError as ex:
raise AnalysisError(
f"Defined fixed parameter {fixed_param} is not a fit function argument."
"Update series definition to ensure the parameter name is defined with "
f"fit functions. Currently available parameters are {fit_params}."
) from ex

return fit_params
return list(list(fsigs)[0].parameters.keys())[1:]

@property
def parameters(self) -> List[str]:
"""Return parameters of this curve analysis."""
return [s for s in self._fit_params() if s not in self.options.fixed_parameters]

@classmethod
def _default_options(cls) -> Options:
Expand Down Expand Up @@ -339,6 +342,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 @@ -360,11 +366,9 @@ def _default_options(cls) -> Options:
options.style = PlotterStyle()
options.extra = dict()
options.curve_fitter_options = dict()

# automatically populate initial guess and boundary
fit_params = cls._fit_params()
options.p0 = {par_name: None for par_name in fit_params}
options.bounds = {par_name: None for par_name in fit_params}
options.p0 = {}
options.bounds = {}
options.fixed_parameters = {}

return options

Expand Down Expand Up @@ -754,16 +758,15 @@ def _run_analysis(
#

# Update all fit functions in the series definitions if fixed parameter is defined.
# Fixed parameters should be provided by the analysis options.
if self.__fixed_parameters__:
assigned_params = {k: self.options.get(k, None) for k in self.__fixed_parameters__}
assigned_params = self.options.fixed_parameters

if assigned_params:
# Check if all parameters are assigned.
if any(v is None for v in assigned_params.values()):
raise AnalysisError(
f"Unassigned fixed-value parameters for the fit "
f"function {self.__class__.__name__}."
f"All values of fixed-parameters, i.e. {self.__fixed_parameters__}, "
f"All values of fixed-parameters, i.e. {assigned_params}, "
"must be provided by the analysis options to run this analysis."
)

Expand Down Expand Up @@ -815,7 +818,7 @@ def _run_analysis(

# Generate algorithmic initial guesses and boundaries
default_fit_opt = FitOptions(
parameters=self._fit_params(),
parameters=self.parameters,
default_p0=self.options.p0,
default_bounds=self.options.bounds,
**self.options.curve_fitter_options,
Expand Down Expand Up @@ -964,6 +967,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
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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Fine frequency experiment analysis."""

import warnings

import numpy as np

from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis
Expand All @@ -33,6 +35,16 @@ class FineFrequencyAnalysis(ErrorAmplificationAnalysis):

__fixed_parameters__ = ["angle_per_gate", "phase_offset"]

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
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@

"""Fine half angle calibration analysis."""

import warnings

import numpy as np
from qiskit_experiments.framework import Options
from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis, ParameterRepr
Expand All @@ -31,6 +33,16 @@ class FineHalfAngleAnalysis(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:
r"""Default analysis options.
Expand Down
20 changes: 12 additions & 8 deletions qiskit_experiments/library/characterization/fine_amplitude.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,9 +254,10 @@ def __init__(self, qubit: int, backend: Optional[Backend] = None):
super().__init__([qubit], XGate(), backend=backend)
# Set default analysis options
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 @@ -291,8 +292,10 @@ def __init__(self, qubit: int, backend: Optional[Backend] = None):
super().__init__([qubit], SXGate(), backend=backend)
# Set default analysis options
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 Expand Up @@ -354,9 +357,10 @@ def __init__(self, qubits: Sequence[int], backend: Optional[Backend] = None):
super().__init__(qubits, gate, backend=backend, measurement_qubits=[qubits[1]])
# Set default analysis options
self.analysis.set_options(
angle_per_gate=np.pi / 2,
phase_offset=np.pi,
amp=1,
fixed_parameters={
"angle_per_gate": np.pi / 2,
"phase_offset": np.pi,
},
outcome="1",
)

Expand Down
Loading

0 comments on commit 7b17674

Please sign in to comment.