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

Curve analysis with uncertainties package #551

Closed
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
cd67ca7
add fit function wrapper
nkanazawa1989 Nov 30, 2021
a733d9a
offload error propagation to uncertainties package
nkanazawa1989 Dec 1, 2021
d875ba7
remove FitVal from experiments (replaced with ufloat)
nkanazawa1989 Dec 1, 2021
4050a5f
use better alias
nkanazawa1989 Dec 1, 2021
b204540
replace np function with unp
nkanazawa1989 Dec 1, 2021
2b19a10
keep fit parameter correlation
nkanazawa1989 Dec 1, 2021
2ad7511
cleanup
nkanazawa1989 Dec 1, 2021
394b564
lint
nkanazawa1989 Dec 2, 2021
d5a234a
minor fix
nkanazawa1989 Dec 2, 2021
2d97ba5
fix import
nkanazawa1989 Dec 2, 2021
87ba919
reno
nkanazawa1989 Dec 2, 2021
c1ee1f6
Merge branch 'main' into upgrade/curve_analysis_uncertainties
nkanazawa1989 Dec 6, 2021
9aaeab4
Update qiskit_experiments/curve_analysis/curve_data.py
nkanazawa1989 Dec 6, 2021
47e92ed
Update qiskit_experiments/curve_analysis/fit_function.py
nkanazawa1989 Dec 6, 2021
13724db
Update qiskit_experiments/curve_analysis/fit_function.py
nkanazawa1989 Dec 6, 2021
9405937
Update releasenotes/notes/upgrade-curve-fit-4dc01b1db55ee398.yaml
nkanazawa1989 Dec 6, 2021
a238d97
add error if finite
nkanazawa1989 Dec 6, 2021
602bf79
add safeguard for nan stdev
nkanazawa1989 Dec 6, 2021
9e9715b
update decorator name
nkanazawa1989 Dec 6, 2021
ab23437
Merge branch 'upgrade/curve_analysis_uncertainties' of github.com:nka…
nkanazawa1989 Dec 6, 2021
6e149e9
tag issue to TODO
nkanazawa1989 Dec 6, 2021
707ffec
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into upgr…
nkanazawa1989 Dec 6, 2021
af3f489
black
nkanazawa1989 Dec 6, 2021
3b822b6
update tutorials
nkanazawa1989 Dec 6, 2021
320e444
fix bug
nkanazawa1989 Dec 6, 2021
3066dbc
black
nkanazawa1989 Dec 6, 2021
f8922fc
fix bug
nkanazawa1989 Dec 6, 2021
432529b
add 3 sigma area
nkanazawa1989 Dec 8, 2021
ef79416
replace util function name
nkanazawa1989 Dec 8, 2021
97113d3
update docs
nkanazawa1989 Dec 8, 2021
2a9b9a4
Merge branch 'main' into upgrade/curve_analysis_uncertainties
nkanazawa1989 Dec 8, 2021
670d4be
change until function name to be more precise
nkanazawa1989 Dec 8, 2021
776af49
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into upgr…
nkanazawa1989 Dec 10, 2021
7b4e8e8
rerun tutorials
nkanazawa1989 Dec 10, 2021
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
192 changes: 108 additions & 84 deletions docs/tutorials/randomized_benchmarking.ipynb

Large diffs are not rendered by default.

60 changes: 32 additions & 28 deletions docs/tutorials/t1.ipynb

Large diffs are not rendered by default.

118 changes: 53 additions & 65 deletions docs/tutorials/t2ramsey_characterization.ipynb

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions qiskit_experiments/curve_analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,13 @@
plot_curve_fit
plot_errorbar
plot_scatter

Utilities
*********
.. autosummary::
:toctree: ../stubs/

check_if_nominal_significant
"""
from .curve_analysis import CurveAnalysis
from .curve_data import CurveData, SeriesDef, FitData, ParameterRepr, FitOptions
Expand All @@ -110,6 +117,7 @@
process_curve_data,
process_multi_curve_data,
)
from .utils import check_if_nominal_significant
from .visualization import plot_curve_fit, plot_errorbar, plot_scatter, FitResultPlotters
from . import guess
from . import fit_function
Expand Down
4 changes: 2 additions & 2 deletions qiskit_experiments/curve_analysis/curve_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ def fitval(self, key: str) -> UFloat:
key: Name of parameters to extract.

Returns:
UFloat object. This object can be operated as standard
Python float object with automatic error propagation.
A UFloat object which functions as a standard Python float object
but with automatic error propagation.

Raises:
ValueError: When specified parameter is not defined.
Expand Down
5 changes: 4 additions & 1 deletion qiskit_experiments/curve_analysis/curve_fit.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,10 @@ def fit_func(x, *params):
# Keep parameter correlations in following analysis steps
fit_params = correlated_values(nom_values=popt, covariance_mat=pcov, tags=param_keys)
else:
fit_params = [ufloat(nom, np.nan) for nom in popt]
# Ignore correlations, add standard error if finite.
fit_params = [
ufloat(n, s if np.isfinite(s) else np.nan) for n, s in zip(popt, np.sqrt(np.diag(pcov)))
]

# Calculate the reduced chi-squared for fit
yfits = fit_func(xdata, *popt)
Expand Down
31 changes: 15 additions & 16 deletions qiskit_experiments/curve_analysis/fit_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,27 @@
# pylint: disable=invalid-name, no-member

import functools
from typing import Callable
from typing import Callable, Union

import numpy as np
from uncertainties import unumpy as unp
from uncertainties import unumpy as unp, UFloat


def calc_uncertainties(fit_func: Callable) -> Callable:
"""Decolator that typecast y values to float array if input parameters have no error.
def typecast_returns(fit_func: Callable) -> Callable:
"""A decorator to typecast y values to a float array if the input parameters have no error.

Args:
fit_func: Fit function that may return ufloat array.
fit_func: Fit function that returns a ufloat array or an array of float.

Returns:
Fit function with typecast.
"""

@functools.wraps(fit_func)
def _wrapper(x, *args, **kwargs) -> np.ndarray:
def _wrapper(x, *args, **kwargs) -> Union[float, UFloat, np.ndarray]:
yvals = fit_func(x, *args, **kwargs)
try:
if isinstance(x, float):
# single value
return float(yvals)
return yvals.astype(float)
except TypeError:
Expand All @@ -46,7 +45,7 @@ def _wrapper(x, *args, **kwargs) -> np.ndarray:
return _wrapper


@calc_uncertainties
@typecast_returns
def cos(
x: np.ndarray,
amp: float = 1.0,
Expand All @@ -63,7 +62,7 @@ def cos(
return amp * unp.cos(2 * np.pi * freq * x + phase) + baseline


@calc_uncertainties
@typecast_returns
def sin(
x: np.ndarray,
amp: float = 1.0,
Expand All @@ -80,7 +79,7 @@ def sin(
return amp * unp.sin(2 * np.pi * freq * x + phase) + baseline


@calc_uncertainties
@typecast_returns
def exponential_decay(
x: np.ndarray,
amp: float = 1.0,
Expand All @@ -97,7 +96,7 @@ def exponential_decay(
return amp * base ** (-lamb * x + x0) + baseline


@calc_uncertainties
@typecast_returns
def gaussian(
x: np.ndarray, amp: float = 1.0, sigma: float = 1.0, x0: float = 0.0, baseline: float = 0.0
) -> np.ndarray:
Expand All @@ -109,7 +108,7 @@ def gaussian(
return amp * unp.exp(-((x - x0) ** 2) / (2 * sigma ** 2)) + baseline


@calc_uncertainties
@typecast_returns
def cos_decay(
x: np.ndarray,
amp: float = 1.0,
Expand All @@ -127,7 +126,7 @@ def cos_decay(
return exponential_decay(x, lamb=1 / tau) * cos(x, amp=amp, freq=freq, phase=phase) + baseline


@calc_uncertainties
@typecast_returns
def sin_decay(
x: np.ndarray,
amp: float = 1.0,
Expand All @@ -145,7 +144,7 @@ def sin_decay(
return exponential_decay(x, lamb=1 / tau) * sin(x, amp=amp, freq=freq, phase=phase) + baseline


@calc_uncertainties
@typecast_returns
def bloch_oscillation_x(
x: np.ndarray, px: float = 0.0, py: float = 0.0, pz: float = 0.0, baseline: float = 0.0
):
Expand All @@ -163,7 +162,7 @@ def bloch_oscillation_x(
return (-pz * px + pz * px * unp.cos(w * x) + w * py * unp.sin(w * x)) / (w ** 2) + baseline


@calc_uncertainties
@typecast_returns
def bloch_oscillation_y(
x: np.ndarray, px: float = 0.0, py: float = 0.0, pz: float = 0.0, baseline: float = 0.0
):
Expand All @@ -181,7 +180,7 @@ def bloch_oscillation_y(
return (pz * py - pz * py * unp.cos(w * x) - w * px * unp.sin(w * x)) / (w ** 2) + baseline


@calc_uncertainties
@typecast_returns
def bloch_oscillation_z(
x: np.ndarray, px: float = 0.0, py: float = 0.0, pz: float = 0.0, baseline: float = 0.0
):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:

criteria = [
fit_data.reduced_chisq < 3,
tau.std_dev is None or tau.std_dev < tau.nominal_value,
curve.check_if_nominal_significant(tau),
]

if all(criteria):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
criteria = [
fit_data.reduced_chisq < 3,
1.0 / 4.0 < fit_freq.nominal_value < 10.0,
(np.isnan(fit_freq.std_dev) or (fit_freq.std_dev < fit_freq.nominal_value)),
curve.check_if_nominal_significant(fit_freq),
]

if all(criteria):
Expand Down Expand Up @@ -263,8 +263,8 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:

criteria = [
fit_data.reduced_chisq < 3,
np.isnan(tau.std_dev) or tau.std_dev < tau.nominal_value,
np.isnan(freq.std_dev) or freq.std_dev < freq.nominal_value,
curve.check_if_nominal_significant(tau),
curve.check_if_nominal_significant(freq),
]

if all(criteria):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
1.5 * freq_increment < fit_sigma.nominal_value,
fit_width_ratio < 0.25,
fit_data.reduced_chisq < 3,
(np.isnan(fit_sigma.std_dev) or fit_sigma.std_dev < fit_sigma.nominal_value),
curve.check_if_nominal_significant(fit_sigma),
snr > 2,
]

Expand Down
47 changes: 47 additions & 0 deletions qiskit_experiments/curve_analysis/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# 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.

"""
A set of utility functions.
"""

from typing import Union, Optional

import numpy as np
from uncertainties.core import UFloat


def check_if_nominal_significant(
nkanazawa1989 marked this conversation as resolved.
Show resolved Hide resolved
val: Union[float, UFloat],
fraction: float = 1.0,
absolute: Optional[float] = None,
) -> bool:
"""Check if the nominal part of the given value is larger than the standard error.

Args:
val: Input value to evaluate.
fraction: Valid fraction of the nominal part to its standard error.
This function returns ``False`` if the nominal part is
smaller than the error by this fraction.
absolute: Use this value as a threshold if given.

Returns:
``True`` if the nominal part is significant.
"""
if isinstance(val, float):
return True

threshold = absolute if absolute is not None else fraction * val.nominal_value
if np.isnan(val.std_dev) or val.std_dev < threshold:
return True

return False
9 changes: 9 additions & 0 deletions qiskit_experiments/curve_analysis/visualization/curves.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,10 +72,19 @@ def plot_curve_fit(
# Confidence interval of 1 sigma
stdev_arr = unp.std_devs(ys_fit_with_error)
if np.isfinite(stdev_arr).all():
# 1 sigma plot
ax.fill_between(
xs,
y1=unp.nominal_values(ys_fit_with_error) - stdev_arr,
y2=unp.nominal_values(ys_fit_with_error) + stdev_arr,
alpha=0.3,
color=plot_opts["color"],
)
# 3 sigma plot
ax.fill_between(
xs,
y1=unp.nominal_values(ys_fit_with_error) - 3 * stdev_arr,
y2=unp.nominal_values(ys_fit_with_error) + 3 * stdev_arr,
alpha=0.1,
color=plot_opts["color"],
)
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/database_service/db_fitval.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class FitVal:
This data is serializable with the Qiskit Experiment json serializer.
"""

# TODO deprecate this (replace with UFloat)
# TODO deprecate this (replace with UFloat), see qiskit-experiments#559 for details.

value: float
stderr: Optional[float] = None
Expand Down
6 changes: 4 additions & 2 deletions qiskit_experiments/framework/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
"""

from abc import ABC, abstractmethod
from typing import List, Tuple
import copy
from collections import OrderedDict
from typing import List, Tuple, Union, Dict
from uncertainties.core import UFloat

from qiskit_experiments.database_service.device_component import Qubit
Expand Down Expand Up @@ -193,7 +195,7 @@ def _format_analysis_result(self, data, experiment_id, experiment_components=Non
device_components = experiment_components

# Convert ufloat to FitVal so that database service can parse
# TODO completely deprecate FitVal. We can store UFloat in database.
# TODO completely deprecate FitVal. We can store UFloat in database (qiskit-experiments#559)
if isinstance(data.value, UFloat):
value = FitVal(
value=data.value.nominal_value,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
criteria = [
fit_data.reduced_chisq < 3,
fit_beta.nominal_value < 1 / fit_freq0.nominal_value,
fit_beta.std_dev < abs(fit_beta.nominal_value),
curve.check_if_nominal_significant(fit_beta),
]

if all(criteria):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:

criteria = [
fit_data.reduced_chisq < 3,
fit_freq.std_dev < abs(fit_freq.nominal_value),
curve.check_if_nominal_significant(fit_freq),
]

if all(criteria):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
fit_data.reduced_chisq < 3,
abs(amp.nominal_value - 1.0) < 0.1,
abs(base.nominal_value) < 0.1,
amp.std_dev is None or amp.std_dev < 0.1,
tau.std_dev is None or tau.std_dev < tau.nominal_value,
base.std_dev is None or base.std_dev < 0.1,
curve.check_if_nominal_significant(amp, absolute=0.1),
curve.check_if_nominal_significant(tau),
curve.check_if_nominal_significant(base, absolute=0.1),
]

if all(criteria):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,9 +72,9 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:

criteria = [
fit_data.reduced_chisq < 3,
amp.std_dev is None or amp.std_dev < 0.1 * amp.nominal_value,
tau.std_dev is None or tau.std_dev < 0.1 * tau.nominal_value,
freq.std_dev is None or freq.std_dev < 0.1 * freq.nominal_value,
curve.check_if_nominal_significant(amp, fraction=0.1),
curve.check_if_nominal_significant(tau, fraction=0.1),
curve.check_if_nominal_significant(freq, fraction=0.1),
]

if all(criteria):
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/library/quantum_volume/qv_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
import numpy as np
from uncertainties import ufloat

from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData
from qiskit_experiments.framework import BaseAnalysis, AnalysisResultData, Options
from qiskit_experiments.curve_analysis import plot_scatter, plot_errorbar


Expand Down
10 changes: 8 additions & 2 deletions qiskit_experiments/library/randomized_benchmarking/rb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@

from typing import Tuple, Dict, Optional, List, Union, Sequence
import numpy as np
from uncertainties.core import UFloat
from uncertainties.core import UFloat, ufloat
from qiskit import QiskitError, QuantumCircuit
from qiskit.providers.backend import Backend
from qiskit_experiments.database_service.device_component import Qubit
from qiskit_experiments.database_service.db_fitval import FitVal
from qiskit_experiments.framework import DbAnalysisResultV1, AnalysisResultData


Expand Down Expand Up @@ -260,7 +261,12 @@ def calculate_2q_epg(
gate = result.name.replace("EPG_", "")

# This keeps variance of previous experiment to propagate
epg_1_qubit_dict[qubit][gate] = result.value
analysis_value = result.value
if isinstance(analysis_value, FitVal):
analysis_value = ufloat(
nominal_value=analysis_value.value, std_dev=analysis_value.stderr
)
epg_1_qubit_dict[qubit][gate] = analysis_value

for key in gate_error_ratio:
qubits, gate = key
Expand Down
11 changes: 6 additions & 5 deletions releasenotes/notes/upgrade-curve-fit-4dc01b1db55ee398.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
---
others:
- |
Curve analysis has been updated to consider the fit parameter covariance in the error propagation.
Now all result figures show the confidence interval of 1 sigma that is visualized
as a semitransparent filled-area around the bold fit line. The width of the area corresponds to the
standard deviation computed by the uncertainties of fit parameters with the covariance matrix.
All analysis outcomes derived from the fit parameters also have a standard error computed in the same way.
Curve analysis is updated to use the covariance between fit parameters in the error propagation.
Now, all result figures show a 1 sigma and 3 sigma confidence intervals with
semitransparent filled-area with different transparency around the bold fit line.
The width of the area corresponds to the standard deviation computed by the
uncertainties of fit parameters with the covariance matrix. All analysis outcomes derived from the
fit parameters also have a standard error computed in the same way.