Skip to content
Closed
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
4 changes: 0 additions & 4 deletions docs/_ext/custom_styles/example/example_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,15 +62,11 @@ class DocumentedCurveAnalysis(CurveAnalysis):
desc: Description of parameter :math:`a`.
init_guess: Here you can describe how this analysis estimate initial guess of
parameter :math:`a`.
bounds: Here you can describe how this analysis bounds parameter :math:`a` value
during the fit.

defpar b:
desc: Description of parameter :math:`b`.
init_guess: Here you can describe how this analysis estimate initial guess of
parameter :math:`b`.
bounds: Here you can describe how this analysis bounds parameter :math:`b` value
during the fit.

Note that you cannot write text block (i.e. bullet lines, math mode, parsed literal, ...)
in the ``defpar`` syntax items. These are a single line description of parameters.
Expand Down
3 changes: 0 additions & 3 deletions docs/_ext/custom_styles/section_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ def load_fit_parameters(docstring_lines: List[str]) -> List[str]:
description_kind = {
"desc": re.compile(r"desc: (?P<s>.+)"),
"init_guess": re.compile(r"init_guess: (?P<s>.+)"),
"bounds": re.compile(r"bounds: (?P<s>.+)"),
}

# parse lines
Expand All @@ -52,7 +51,6 @@ def load_fit_parameters(docstring_lines: List[str]) -> List[str]:
parameter_desc[current_param] = {
"desc": "",
"init_guess": "",
"bounds": "",
}
continue

Expand Down Expand Up @@ -85,6 +83,5 @@ def write_description(header: str, kind: str):

write_description("Descriptions", "desc")
write_description("Initial Guess", "init_guess")
write_description("Boundaries", "bounds")

return _trim_empty_lines(section_lines)
26 changes: 6 additions & 20 deletions qiskit_experiments/curve_analysis/curve_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,6 @@ def _default_options(cls) -> Options:
normalization (bool) : Set ``True`` to normalize y values within range [-1, 1].
p0 (Dict[str, float]): Array-like or dictionary
of initial parameters.
bounds (Dict[str, Tuple[float, float]]): Array-like or dictionary
of (min, max) tuple of fit parameter boundaries.
x_key (str): Circuit metadata key representing a scanned value.
plot (bool): Set ``True`` to create figure for fit result.
axis (AxesSubplot): Optional. A matplotlib axis object to draw.
Expand All @@ -332,7 +330,6 @@ def _default_options(cls) -> Options:
data_processor=None,
normalization=False,
p0=None,
bounds=None,
x_key="xval",
plot=True,
axis=None,
Expand Down Expand Up @@ -473,21 +470,19 @@ def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]

if self._get_option("my_option1") == "abc":
p0 = my_guess_function(curve_data.x, curve_data.y, ...)
bounds = ...
else:
p0 = ...
bounds = ...

return {"p0": p0, "bounds": bounds}
return {"p0": p0}

Note that this subroutine can generate multiple fit options.
If multiple options are provided, fitter runs multiple times for each fit option,
and find the best result measured by the reduced chi-squared value.

.. code-block::

fit_1 = {"p0": p0_1, "bounds": bounds, "extra_fit_parameter": "option1"}
fit_2 = {"p0": p0_2, "bounds": bounds, "extra_fit_parameter": "option2"}
fit_1 = {"p0": p0_1, "extra_fit_parameter": "option1"}
fit_2 = {"p0": p0_2, "extra_fit_parameter": "option2"}

return [fit_1, fit_2]

Expand All @@ -502,7 +497,7 @@ def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]
Returns:
List of FitOptions that are passed to fitter function.
"""
fit_options = {"p0": self._get_option("p0"), "bounds": self._get_option("bounds")}
fit_options = {"p0": self._get_option("p0")}
fit_options.update(options)

return fit_options
Expand Down Expand Up @@ -709,15 +704,6 @@ def _dictionarize(parameter_name):
# p0 should be defined
raise AnalysisError("Initial guess p0 is not provided to the fitting options.")

if fitter_options.get("bounds", None):
if isinstance(fitter_options["bounds"], dict):
_check_keys("bounds")
else:
fitter_options["bounds"] = _dictionarize("bounds")
else:
# bounds are optional
fitter_options["bounds"] = {par: (-np.inf, np.inf) for par in self.__fit_params}

return fitter_options

@property
Expand Down Expand Up @@ -1023,8 +1009,8 @@ def _run_analysis(
fit_results.append(fit_result)
if len(fit_results) == 0:
raise AnalysisError(
"All initial guesses and parameter boundaries failed to fit the data. "
"Please provide better initial guesses or fit parameter boundaries."
"All initial guesses failed to fit the data. "
"Please provide better initial guesses."
)
# Sort by chi squared value
fit_result = sorted(fit_results, key=lambda r: r.reduced_chisq)[0]
Expand Down
24 changes: 2 additions & 22 deletions qiskit_experiments/curve_analysis/curve_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ def curve_fit(
ydata: np.ndarray,
p0: Union[Dict[str, float], np.ndarray],
sigma: Optional[np.ndarray] = None,
bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None,
**kwargs,
) -> FitData:
r"""Perform a non-linear least squares to fit
Expand All @@ -49,8 +48,6 @@ def curve_fit(
p0: initial guess for optimization parameters.
sigma: Optional, a 1D array of standard deviations in ydata
in absolute units.
bounds: Optional, lower and upper bounds for optimization
parameters.
kwargs: additional kwargs for :func:`scipy.optimize.curve_fit`.

Returns:
Expand Down Expand Up @@ -79,25 +76,13 @@ def curve_fit(
param_keys = list(p0.keys())
param_p0 = list(p0.values())

# Convert bounds
if bounds:
lower = [bounds[key][0] for key in param_keys]
upper = [bounds[key][1] for key in param_keys]
param_bounds = (lower, upper)
else:
param_bounds = ([-np.inf] * len(param_keys), [np.inf] * len(param_keys))

# Convert fit function
def fit_func(x, *params):
return func(x, **dict(zip(param_keys, params)))

else:
param_keys = None
param_p0 = p0
if bounds:
param_bounds = bounds
else:
param_bounds = ([-np.inf] * len(p0), [np.inf] * len(p0))
fit_func = func

# Check the degrees of freedom is greater than 0
Expand Down Expand Up @@ -125,9 +110,7 @@ def fit_func(x, *params):
# Run curve fit
try:
# pylint: disable = unbalanced-tuple-unpacking
popt, pcov = opt.curve_fit(
fit_func, xdata, ydata, sigma=sigma, p0=param_p0, bounds=param_bounds, **kwargs
)
popt, pcov = opt.curve_fit(fit_func, xdata, ydata, sigma=sigma, p0=param_p0, **kwargs)
except Exception as ex:
raise AnalysisError(
"scipy.optimize.curve_fit failed with error: {}".format(str(ex))
Expand Down Expand Up @@ -166,7 +149,6 @@ def multi_curve_fit(
p0: np.ndarray,
sigma: Optional[np.ndarray] = None,
weights: Optional[np.ndarray] = None,
bounds: Optional[Union[Dict[str, Tuple[float, float]], Tuple[np.ndarray, np.ndarray]]] = None,
**kwargs,
) -> FitData:
r"""Perform a linearized multi-objective non-linear least squares fit.
Expand Down Expand Up @@ -195,8 +177,6 @@ def multi_curve_fit(
in absolute units.
weights: Optional, a 1D float list of weights :math:`w_k` for each
component function :math:`f_k`.
bounds: Optional, lower and upper bounds for optimization
parameters.
kwargs: additional kwargs for :func:`scipy.optimize.curve_fit`.

Returns:
Expand Down Expand Up @@ -248,7 +228,7 @@ def f(x, *args, **kwargs):
return y

# Run linearized curve_fit
result_data = curve_fit(f, xdata, ydata, p0, sigma=wsigma, bounds=bounds, **kwargs)
result_data = curve_fit(f, xdata, ydata, p0, sigma=wsigma, **kwargs)

return result_data

Expand Down
21 changes: 0 additions & 21 deletions qiskit_experiments/library/calibration/analysis/drag_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,22 +37,18 @@ class DragCalAnalysis(curve.CurveAnalysis):
defpar \rm amp:
desc: Amplitude of all series.
init_guess: The maximum y value less the minimum y value. 0.5 is also tried.
bounds: [-2, 2] scaled to the maximum signal value.

defpar \rm base:
desc: Base line of all series.
init_guess: The average of the data. 0.5 is also tried.
bounds: [-1, 1] scaled to the maximum signal value.

defpar {\rm freq}_i:
desc: Frequency of the :math:`i` th oscillation.
init_guess: The frequency with the highest power spectral density.
bounds: [0, inf].

defpar \beta:
desc: Common beta offset. This is the parameter of interest.
init_guess: Linearly spaced between the maximum and minimum scanned beta.
bounds: [-min scan range, max scan range].
"""

__series__ = [
Expand Down Expand Up @@ -101,14 +97,6 @@ def _default_options(cls):
"beta": None,
"base": None,
}
default_options.bounds = {
"amp": None,
"freq0": None,
"freq1": None,
"freq2": None,
"beta": None,
"base": None,
}
default_options.result_parameters = ["beta"]
default_options.xlabel = "Beta"
default_options.ylabel = "Signal (arb. units)"
Expand All @@ -118,7 +106,6 @@ def _default_options(cls):
def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
"""Compute the initial guesses."""
user_p0 = self._get_option("p0")
user_bounds = self._get_option("bounds")

# Use a fast Fourier transform to guess the frequency.
x_data = self._data("series-0").x
Expand Down Expand Up @@ -166,14 +153,6 @@ def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]
"beta": p_guess,
"base": b_guess,
},
"bounds": {
"amp": user_bounds.get("amp", None) or (-2 * max_abs_y, 2 * max_abs_y),
"freq0": user_bounds.get("freq0", None) or (0, np.inf),
"freq1": user_bounds.get("freq1", None) or (0, np.inf),
"freq2": user_bounds.get("freq2", None) or (0, np.inf),
"beta": user_bounds.get("beta", None) or (-freq_bound, freq_bound),
"base": user_bounds.get("base", None) or (-1 * max_abs_y, 1 * max_abs_y),
},
}

fit_option.update(options)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,15 @@ class FineAmplitudeAnalysis(curve.CurveAnalysis):
defpar \rm amp:
desc: Amplitude of the oscillation.
init_guess: The maximum y value less the minimum y value.
bounds: [-2, 2] scaled to the maximum signal value.

defpar \rm base:
desc: Base line.
init_guess: The average of the data.
bounds: [-1, 1] scaled to the maximum signal value.

defpar d\theta:
desc: The angle offset in the gate that we wish to measure.
init_guess: Multiple initial guesses are tried ranging from -a to a
where a is given by :code:`max(abs(angle_per_gate), np.pi / 2)`.
bounds: [-pi, pi].

# section: note

Expand Down Expand Up @@ -97,7 +94,6 @@ def _default_options(cls):
"""
default_options = super()._default_options()
default_options.p0 = {"amp": None, "d_theta": None, "phase": None, "base": None}
default_options.bounds = {"amp": None, "d_theta": None, "phase": None, "base": None}
default_options.result_parameters = ["d_theta"]
default_options.xlabel = "Number of gates (n)"
default_options.ylabel = "Population"
Expand All @@ -111,15 +107,12 @@ def _default_options(cls):
def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
"""Fitter options."""
user_p0 = self._get_option("p0")
user_bounds = self._get_option("bounds")
n_guesses = self._get_option("number_guesses")

max_y, min_y = np.max(self._data().y), np.min(self._data().y)
b_guess = (max_y + min_y) / 2
a_guess = max_y - min_y

max_abs_y = np.max(np.abs(self._data().y))

# Base the initial guess on the intended angle_per_gate.
angle_per_gate = self._get_option("angle_per_gate")

Expand All @@ -137,11 +130,6 @@ def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]
"d_theta": angle,
"base": b_guess,
},
"bounds": {
"amp": user_bounds.get("amp", None) or (-2 * max_abs_y, 2 * max_abs_y),
"d_theta": user_bounds.get("d_theta", None) or (-np.pi, np.pi),
"base": user_bounds.get("d_theta", None) or (-1 * max_abs_y, 1 * max_abs_y),
},
}

fit_options.append(fit_option)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,24 +33,20 @@ class OscillationAnalysis(curve.CurveAnalysis):
defpar \rm amp:
desc: Amplitude of the oscillation.
init_guess: Calculated by :func:`~qiskit_experiments.curve_analysis.guess.max_height`.
bounds: [-2, 2] scaled to the maximum signal value.

defpar \rm base:
desc: Base line.
init_guess: Calculated by :func:`~qiskit_experiments.curve_analysis.\
guess.constant_sinusoidal_offset`.
bounds: [-1, 1] scaled to the maximum signal value.

defpar \rm freq:
desc: Frequency of the oscillation. This is the fit parameter of interest.
init_guess: Calculated by :func:`~qiskit_experiments.curve_analysis.\
guess.frequency`.
bounds: [0, inf].

defpar \rm phase:
desc: Phase of the oscillation.
init_guess: Zero.
bounds: [-pi, pi].
"""

__series__ = [
Expand All @@ -71,7 +67,6 @@ def _default_options(cls):
"""
default_options = super()._default_options()
default_options.p0 = {"amp": None, "freq": None, "phase": None, "base": None}
default_options.bounds = {"amp": None, "freq": None, "phase": None, "base": None}
default_options.result_parameters = ["freq"]
default_options.xlabel = "Amplitude"
default_options.ylabel = "Signal (arb. units)"
Expand All @@ -81,12 +76,9 @@ def _default_options(cls):
def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]]]:
"""Fitter options."""
user_p0 = self._get_option("p0")
user_bounds = self._get_option("bounds")

curve_data = self._data()

max_abs_y = np.max(np.abs(curve_data.y))

f_guess = curve.guess.frequency(curve_data.x, curve_data.y)
b_guess = curve.guess.constant_sinusoidal_offset(curve_data.y)
a_guess, _ = curve.guess.max_height(curve_data.y - b_guess, absolute=True)
Expand All @@ -105,12 +97,6 @@ def _setup_fitting(self, **options) -> Union[Dict[str, Any], List[Dict[str, Any]
"phase": p_guess,
"base": user_p0["base"] or b_guess,
},
"bounds": {
"amp": user_bounds["amp"] or (-2 * max_abs_y, 2 * max_abs_y),
"freq": user_bounds["freq"] or (0, np.inf),
"phase": user_bounds["phase"] or (-np.pi, np.pi),
"base": user_bounds["base"] or (-1 * max_abs_y, 1 * max_abs_y),
},
}
fit_option.update(options)
fit_options.append(fit_option)
Expand Down
Loading