Skip to content

Commit 4224bd5

Browse files
committed
add fixed parameters to the curve analysis fit option and remove class attribute __fixed_parameters__
1 parent d27fe41 commit 4224bd5

File tree

15 files changed

+370
-186
lines changed

15 files changed

+370
-186
lines changed

qiskit_experiments/curve_analysis/curve_analysis.py

Lines changed: 54 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
ExperimentData,
4848
AnalysisResultData,
4949
Options,
50+
AnalysisConfig,
5051
)
5152

5253
PARAMS_ENTRY_PREFIX = "@Parameters_"
@@ -233,13 +234,22 @@ class AnalysisExample(CurveAnalysis):
233234
#: List[SeriesDef]: List of mapping representing a data series
234235
__series__ = list()
235236

236-
#: List[str]: Fixed parameter in fit function. Value should be set to the analysis options.
237-
__fixed_parameters__ = list()
238-
239237
def __init__(self):
240238
"""Initialize data fields that are privately accessed by methods."""
241239
super().__init__()
242240

241+
if hasattr(self, "__fixed_parameters__"):
242+
warnings.warn(
243+
"The class attribute __fixed_parameters__ has been deprecated and will be removed. "
244+
"Now this attribute is absorbed in analysis options as fixed_parameters.",
245+
DeprecationWarning,
246+
stacklevel=2,
247+
)
248+
# pylint: disable=no-member
249+
self._options.fixed_parameters = {
250+
p: self.options.get(p, None) for p in self.__fixed_parameters__
251+
}
252+
243253
#: Dict[str, Any]: Experiment metadata
244254
self.__experiment_metadata = None
245255

@@ -271,21 +281,12 @@ def _fit_params(cls) -> List[str]:
271281
)
272282

273283
# remove the first function argument. this is usually x, i.e. not a fit parameter.
274-
fit_params = list(list(fsigs)[0].parameters.keys())[1:]
275-
276-
# remove fixed parameters
277-
if cls.__fixed_parameters__ is not None:
278-
for fixed_param in cls.__fixed_parameters__:
279-
try:
280-
fit_params.remove(fixed_param)
281-
except ValueError as ex:
282-
raise AnalysisError(
283-
f"Defined fixed parameter {fixed_param} is not a fit function argument."
284-
"Update series definition to ensure the parameter name is defined with "
285-
f"fit functions. Currently available parameters are {fit_params}."
286-
) from ex
287-
288-
return fit_params
284+
return list(list(fsigs)[0].parameters.keys())[1:]
285+
286+
@property
287+
def parameters(self) -> List[str]:
288+
"""Return parameters of this curve analysis."""
289+
return [s for s in self._fit_params() if s not in self.options.fixed_parameters]
289290

290291
@classmethod
291292
def _default_options(cls) -> Options:
@@ -339,6 +340,9 @@ def _default_options(cls) -> Options:
339340
as extra information.
340341
curve_fitter_options (Dict[str, Any]) Options that are passed to the
341342
specified curve fitting function.
343+
fixed_parameters (Dict[str, Any]): Fitting model parameters that are fixed
344+
during the curve fitting. This should be provided with default value
345+
keyed on one of the parameter names in the series definition.
342346
"""
343347
options = super()._default_options()
344348

@@ -360,11 +364,9 @@ def _default_options(cls) -> Options:
360364
options.style = PlotterStyle()
361365
options.extra = dict()
362366
options.curve_fitter_options = dict()
363-
364-
# automatically populate initial guess and boundary
365-
fit_params = cls._fit_params()
366-
options.p0 = {par_name: None for par_name in fit_params}
367-
options.bounds = {par_name: None for par_name in fit_params}
367+
options.p0 = {}
368+
options.bounds = {}
369+
options.fixed_parameters = {}
368370

369371
return options
370372

@@ -754,16 +756,15 @@ def _run_analysis(
754756
#
755757

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

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

@@ -815,7 +816,7 @@ def _run_analysis(
815816

816817
# Generate algorithmic initial guesses and boundaries
817818
default_fit_opt = FitOptions(
818-
parameters=self._fit_params(),
819+
parameters=self.parameters,
819820
default_p0=self.options.p0,
820821
default_bounds=self.options.bounds,
821822
**self.options.curve_fitter_options,
@@ -964,6 +965,31 @@ def _run_analysis(
964965

965966
return analysis_results, figures
966967

968+
@classmethod
969+
def from_config(cls, config: Union[AnalysisConfig, Dict]) -> "CurveAnalysis":
970+
instance = super().from_config(config)
971+
972+
# When fixed param value is hard-coded as options. This is deprecated data structure.
973+
loaded_opts = instance.options.__dict__
974+
975+
# pylint: disable=no-member
976+
deprecated_fixed_params = {
977+
p: loaded_opts[p] for p in instance.parameters if p in loaded_opts
978+
}
979+
if any(deprecated_fixed_params):
980+
warnings.warn(
981+
"Fixed parameter value should be defined in options.fixed_parameters as "
982+
"a dictionary values, rather than a standalone analysis option. "
983+
"Please re-save this experiment to be loaded after deprecation period.",
984+
DeprecationWarning,
985+
stacklevel=2,
986+
)
987+
new_fixed_params = instance.options.fixed_parameters
988+
new_fixed_params.update(deprecated_fixed_params)
989+
instance.set_options(fixed_parameters=new_fixed_params)
990+
991+
return instance
992+
967993

968994
def is_error_not_significant(
969995
val: Union[float, uncertainties.UFloat],

qiskit_experiments/curve_analysis/standard_analysis/error_amplification_analysis.py

Lines changed: 9 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -105,24 +105,15 @@ def _default_options(cls):
105105
descriptions of analysis options.
106106
107107
Analysis Options:
108-
angle_per_gate (float): The ideal angle per repeated gate.
109-
The user must set this option as it defaults to None.
110-
phase_offset (float): A phase offset for the analysis. This phase offset will be
111-
:math:`\pi/2` if the square-root of X gate is added before the repeated gates.
112-
This is decided for the user in :meth:`set_schedule` depending on whether the
113-
sx gate is included in the experiment.
114108
max_good_angle_error (float): The maximum angle error for which the fit is
115109
considered as good. Defaults to :math:`\pi/2`.
116110
"""
117111
default_options = super()._default_options()
118112
default_options.result_parameters = ["d_theta"]
119113
default_options.xlabel = "Number of gates (n)"
120114
default_options.ylabel = "Population"
121-
default_options.angle_per_gate = None
122-
default_options.phase_offset = 0.0
123-
default_options.max_good_angle_error = np.pi / 2
124-
default_options.amp = 1.0
125115
default_options.ylim = [0, 1.0]
116+
default_options.max_good_angle_error = np.pi / 2
126117

127118
return default_options
128119

@@ -140,6 +131,8 @@ def _generate_fit_guesses(
140131
Raises:
141132
CalibrationError: When ``angle_per_gate`` is missing.
142133
"""
134+
fixed_params = self.options.fixed_parameters
135+
143136
curve_data = self._data()
144137
max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True)
145138
max_y, min_y = np.max(curve_data.y), np.min(curve_data.y)
@@ -152,16 +145,19 @@ def _generate_fit_guesses(
152145
if "amp" in user_opt.p0:
153146
user_opt.p0.set_if_empty(amp=max_y - min_y)
154147
user_opt.bounds.set_if_empty(amp=(0, 2 * max_abs_y))
148+
amp = user_opt.p0["amp"]
149+
else:
150+
# Fixed parameter
151+
amp = fixed_params.get("amp", 1.0)
155152

156153
# Base the initial guess on the intended angle_per_gate and phase offset.
157-
apg = self.options.angle_per_gate
158-
phi = self.options.phase_offset
154+
apg = user_opt.p0.get("angle_per_gate", fixed_params.get("angle_per_gate", 0.0))
155+
phi = user_opt.p0.get("phase_offset", fixed_params.get("phase_offset", 0.0))
159156

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

163160
offsets = apg * curve_data.x + phi
164-
amp = user_opt.p0.get("amp", self.options.amp)
165161
for i in range(curve_data.x.size):
166162
xi = curve_data.x[i]
167163
yi = curve_data.y[i]

qiskit_experiments/library/calibration/fine_amplitude.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,9 +171,10 @@ def __init__(
171171
auto_update=auto_update,
172172
)
173173
self.analysis.set_options(
174-
angle_per_gate=np.pi,
175-
phase_offset=np.pi / 2,
176-
amp=1,
174+
fixed_parameters={
175+
"angle_per_gate": np.pi,
176+
"phase_offset": np.pi / 2,
177+
}
177178
)
178179

179180
@classmethod
@@ -222,8 +223,10 @@ def __init__(
222223
auto_update=auto_update,
223224
)
224225
self.analysis.set_options(
225-
angle_per_gate=np.pi / 2,
226-
phase_offset=np.pi,
226+
fixed_parameters={
227+
"angle_per_gate": np.pi / 2,
228+
"phase_offset": np.pi,
229+
}
227230
)
228231

229232
@classmethod

qiskit_experiments/library/characterization/analysis/fine_amplitude_analysis.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,3 @@ class FineAmplitudeAnalysis(ErrorAmplificationAnalysis):
5858
filter_kwargs={"series": 1},
5959
),
6060
]
61-
62-
__fixed_parameters__ = ["angle_per_gate", "phase_offset"]

qiskit_experiments/library/characterization/analysis/fine_drag_analysis.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
"""Fine DRAG calibration analysis."""
1414

15+
import warnings
16+
1517
import numpy as np
1618
from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis
1719
from qiskit_experiments.framework import Options
@@ -32,6 +34,16 @@ class FineDragAnalysis(ErrorAmplificationAnalysis):
3234

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

37+
def __init__(self):
38+
super().__init__()
39+
40+
warnings.warn(
41+
f"{self.__class__.__name__} has been deprecated. Use ErrorAmplificationAnalysis "
42+
"instance with the analysis options involving the fixed_parameters.",
43+
DeprecationWarning,
44+
stacklevel=2,
45+
)
46+
3547
@classmethod
3648
def _default_options(cls) -> Options:
3749
"""Default analysis options."""

qiskit_experiments/library/characterization/analysis/fine_frequency_analysis.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
"""Fine frequency experiment analysis."""
1414

15+
import warnings
16+
1517
import numpy as np
1618

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

3436
__fixed_parameters__ = ["angle_per_gate", "phase_offset"]
3537

38+
def __init__(self):
39+
super().__init__()
40+
41+
warnings.warn(
42+
f"{self.__class__.__name__} has been deprecated. Use ErrorAmplificationAnalysis "
43+
"instance with the analysis options involving the fixed_parameters.",
44+
DeprecationWarning,
45+
stacklevel=2,
46+
)
47+
3648
@classmethod
3749
def _default_options(cls) -> Options:
3850
"""Default analysis options."""

qiskit_experiments/library/characterization/analysis/fine_half_angle_analysis.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212

1313
"""Fine half angle calibration analysis."""
1414

15+
import warnings
16+
1517
import numpy as np
1618
from qiskit_experiments.framework import Options
1719
from qiskit_experiments.curve_analysis import ErrorAmplificationAnalysis, ParameterRepr
@@ -31,6 +33,16 @@ class FineHalfAngleAnalysis(ErrorAmplificationAnalysis):
3133

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

36+
def __init__(self):
37+
super().__init__()
38+
39+
warnings.warn(
40+
f"{self.__class__.__name__} has been deprecated. Use ErrorAmplificationAnalysis "
41+
"instance with the analysis options involving the fixed_parameters.",
42+
DeprecationWarning,
43+
stacklevel=2,
44+
)
45+
3446
@classmethod
3547
def _default_options(cls) -> Options:
3648
r"""Default analysis options.

qiskit_experiments/library/characterization/fine_amplitude.py

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ def __init__(self, qubit: int, backend: Optional[Backend] = None):
253253
super().__init__([qubit], XGate(), backend=backend)
254254
# Set default analysis options
255255
self.analysis.set_options(
256-
angle_per_gate=np.pi,
257-
phase_offset=np.pi / 2,
258-
amp=1,
256+
fixed_parameters={
257+
"angle_per_gate": np.pi,
258+
"phase_offset": np.pi / 2,
259+
}
259260
)
260261

261262
@classmethod
@@ -290,8 +291,10 @@ def __init__(self, qubit: int, backend: Optional[Backend] = None):
290291
super().__init__([qubit], SXGate(), backend=backend)
291292
# Set default analysis options
292293
self.analysis.set_options(
293-
angle_per_gate=np.pi / 2,
294-
phase_offset=np.pi,
294+
fixed_parameters={
295+
"angle_per_gate": np.pi / 2,
296+
"phase_offset": np.pi,
297+
}
295298
)
296299

297300
@classmethod
@@ -353,9 +356,10 @@ def __init__(self, qubits: Sequence[int], backend: Optional[Backend] = None):
353356
super().__init__(qubits, gate, backend=backend, measurement_qubits=[qubits[1]])
354357
# Set default analysis options
355358
self.analysis.set_options(
356-
angle_per_gate=np.pi / 2,
357-
phase_offset=np.pi,
358-
amp=1,
359+
fixed_parameters={
360+
"angle_per_gate": np.pi / 2,
361+
"phase_offset": np.pi,
362+
},
359363
outcome="1",
360364
)
361365

qiskit_experiments/library/characterization/fine_drag.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
from qiskit.circuit.library import XGate, SXGate
2121
from qiskit.providers.backend import Backend
2222
from qiskit_experiments.framework import BaseExperiment, Options
23-
from qiskit_experiments.library.characterization.analysis import (
24-
FineDragAnalysis,
25-
)
23+
from qiskit_experiments.curve_analysis.standard_analysis import ErrorAmplificationAnalysis
2624

2725

2826
class FineDrag(BaseExperiment):
@@ -126,7 +124,7 @@ class FineDrag(BaseExperiment):
126124
This is the correction formula in the FineDRAG Updater.
127125
128126
# section: analysis_ref
129-
:py:class:`FineDragAnalysis`
127+
:py:class:`~qiskit_experiments.curve_analysis.ErrorAmplificationAnalysis`
130128
131129
# section: see_also
132130
qiskit_experiments.library.calibration.drag.DragCal
@@ -161,7 +159,17 @@ def __init__(self, qubit: int, gate: Gate, backend: Optional[Backend] = None):
161159
gate: The gate that will be repeated.
162160
backend: Optional, the backend to run the experiment on.
163161
"""
164-
super().__init__([qubit], analysis=FineDragAnalysis(), backend=backend)
162+
analysis = ErrorAmplificationAnalysis()
163+
analysis.set_options(
164+
normalization=True,
165+
fixed_parameters={
166+
"angle_per_gate": 0.0,
167+
"phase_offset": np.pi / 2,
168+
"amp": 1.0,
169+
},
170+
)
171+
172+
super().__init__([qubit], analysis=analysis, backend=backend)
165173
self.set_experiment_options(gate=gate)
166174

167175
@staticmethod

0 commit comments

Comments
 (0)