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

Resonator spectroscopy #583

Merged
merged 82 commits into from
Feb 4, 2022
Merged
Show file tree
Hide file tree
Changes from 66 commits
Commits
Show all changes
82 commits
Select commit Hold shift + click to select a range
117fdb1
* First draft of new resonator spec.
eggerdj Dec 17, 2021
b6821f5
* Resonator spectroscopy.
eggerdj Jan 11, 2022
4b00b55
* Added IQ plotting.
eggerdj Jan 11, 2022
f568738
* Adding methodology to handle the Classical bit.
eggerdj Jan 13, 2022
660766a
* Switched to a parallelizable methodology for the classical bit.
eggerdj Jan 13, 2022
c36d329
* Data processing for the resonator spectroscopy experiment.
eggerdj Jan 14, 2022
6786c83
Merge branch 'main' into resonator_spec
eggerdj Jan 14, 2022
3db95ee
* Added a test for the ToAbs node.
eggerdj Jan 14, 2022
e1de77f
* Black and tests for resonator spectroscopy.
eggerdj Jan 17, 2022
a807b10
* Reno and lint.
eggerdj Jan 17, 2022
d3674a1
* Docs.
eggerdj Jan 17, 2022
7f920fd
* Change the sqrt in ToAbs node to the unp version.
eggerdj Jan 19, 2022
7331ae0
* Refactored processor library.
eggerdj Jan 20, 2022
2c1c124
* processor_library docstring.
eggerdj Jan 20, 2022
fb3eaf1
* Add ResSpecAnalysis to tree.
eggerdj Jan 20, 2022
a0072d8
Update qiskit_experiments/library/characterization/resonator_spectros…
eggerdj Jan 20, 2022
2355825
* Simplified resonator spectroscopy doc.
eggerdj Jan 20, 2022
c42dcf8
Merge branch 'resonator_spec' of github.com:eggerdj/qiskit-experiment…
eggerdj Jan 20, 2022
5515f2d
* Added key word arguments to init for one liner execution.
eggerdj Jan 20, 2022
5a07e94
* Release note text.
eggerdj Jan 20, 2022
bde77fb
* Create the Spectroscopy base class for spectroscopy experiments.
eggerdj Jan 20, 2022
a2bc3da
* made center_frequency private.
eggerdj Jan 20, 2022
8bdce88
* Center frequency refactor.
eggerdj Jan 20, 2022
e9f7ae4
* circuit metadata refactor.
eggerdj Jan 20, 2022
dfe3149
* Added acquire delay and set default amplitude to 1 for resonator spec.
eggerdj Jan 21, 2022
b5bce85
* Reonator spectroscopy test Lorentzian resonance.
eggerdj Jan 21, 2022
6d487cc
* IQ plot robustness.
eggerdj Jan 21, 2022
c438b15
Merge branch 'main' into resonator_spec
eggerdj Jan 24, 2022
be7b264
Merge branch 'main' of github.com:Qiskit/qiskit-experiments into reso…
eggerdj Jan 24, 2022
26dfd60
Merge branch 'resonator_spec' of github.com:eggerdj/qiskit-experiment…
eggerdj Jan 24, 2022
0ef184b
* Refactored ResonatorSpectroscopyAnalysis
eggerdj Jan 24, 2022
bab4a73
* Test fix.
eggerdj Jan 24, 2022
13d5bb2
* Units, corresponding test fix, and black.
eggerdj Jan 24, 2022
cd436f8
* Lint and default frequencies.
eggerdj Jan 24, 2022
58f9535
* Fix factor omission.
eggerdj Jan 24, 2022
256c3f6
* Align tests.
eggerdj Jan 24, 2022
86001af
* Simplified resonator spec options.
eggerdj Jan 25, 2022
67865cd
* Reno.
eggerdj Jan 25, 2022
7c44dd1
* More robust IQ plotting.
eggerdj Jan 25, 2022
b8b99d6
* Y-axis label.
eggerdj Jan 25, 2022
26848e3
* Resonator spec doc string.
eggerdj Jan 25, 2022
373dfd3
* Resonator spec doc.
eggerdj Jan 25, 2022
7d7fa8e
* Docstring.
eggerdj Jan 25, 2022
a357bf0
* Metadata always has absolute frequencies.
eggerdj Jan 25, 2022
62ce701
* Moved projector type.
eggerdj Jan 25, 2022
7524359
* Refactor resonance analysis to use a Lorentzian and renamed the exi…
eggerdj Jan 25, 2022
8796b32
* Lorentz normalization, small bug fixes, black.
eggerdj Jan 25, 2022
723202c
* Test normalozation.
eggerdj Jan 25, 2022
dac1681
* Reno.
eggerdj Jan 25, 2022
4461880
Merge branch 'main' into resonator_spec
eggerdj Jan 25, 2022
ead7c55
* Removed center_frequency.
eggerdj Jan 25, 2022
a994631
* black.
eggerdj Jan 25, 2022
e7ccdf0
* Qubit spec test align.
eggerdj Jan 25, 2022
9dbe059
* Device components.
eggerdj Jan 26, 2022
3e258cb
* Resonator kappa in result.
eggerdj Jan 26, 2022
fd97988
Update qiskit_experiments/library/characterization/analysis/resonator…
eggerdj Jan 26, 2022
5d9f0b4
* Resonator spectroscopy kappa and fit function.
eggerdj Jan 26, 2022
4b7715d
* Adapted tests.
eggerdj Jan 27, 2022
73eaa4e
* Update default options.
eggerdj Jan 27, 2022
d38558e
Merge branch 'main' into resonator_spec
eggerdj Jan 27, 2022
8eb9d20
* Moved qubit spectroscopy to units of seconds.
eggerdj Jan 27, 2022
c09f9bf
Merge branch 'resonator_spec' of github.com:eggerdj/qiskit-experiment…
eggerdj Jan 27, 2022
9a6417f
* set backend
eggerdj Jan 27, 2022
6e7c915
Merge branch 'main' into resonator_spec
eggerdj Jan 27, 2022
a999744
* Lint
eggerdj Jan 27, 2022
40dd598
Merge branch 'resonator_spec' of github.com:eggerdj/qiskit-experiment…
eggerdj Jan 27, 2022
fac4995
* Square root lorentzian.
eggerdj Jan 28, 2022
9358a2b
* Remove acquire delay and duration.
eggerdj Jan 28, 2022
da67d69
* Doc fix.
eggerdj Jan 28, 2022
35e5af4
Update qiskit_experiments/library/characterization/analysis/resonator…
eggerdj Jan 28, 2022
45f6dac
Update test/test_resonator_spectroscopy.py
eggerdj Feb 2, 2022
c947b2a
Merge branch 'main' into resonator_spec
eggerdj Feb 2, 2022
83a1b3e
* Docs
eggerdj Feb 3, 2022
9990736
* Reno
eggerdj Feb 3, 2022
77e8fb4
* Added more tests values.
eggerdj Feb 3, 2022
322ed86
* Black
eggerdj Feb 3, 2022
072c40d
Merge branch 'main' into resonator_spec
eggerdj Feb 3, 2022
ba5d9ac
* Docs.
eggerdj Feb 3, 2022
30e0898
Merge branch 'resonator_spec' of github.com:eggerdj/qiskit-experiment…
eggerdj Feb 3, 2022
0664a21
* Added warning in resonator spectroscopy docstring.
eggerdj Feb 3, 2022
2904da9
Update qiskit_experiments/library/characterization/resonator_spectros…
eggerdj Feb 4, 2022
cd795b3
Merge branch 'main' into resonator_spec
eggerdj Feb 4, 2022
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
3 changes: 3 additions & 0 deletions qiskit_experiments/curve_analysis/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
DumpedOscillationAnalysis
OscillationAnalysis
ResonanceAnalysis
GaussianAnalysis
ErrorAmplificationAnalysis

Functions
Expand All @@ -73,6 +74,7 @@
fit_function.cos_decay
fit_function.exponential_decay
fit_function.gaussian
fit_function.lorentzian
fit_function.sin
fit_function.sin_decay
fit_function.bloch_oscillation_x
Expand Down Expand Up @@ -120,5 +122,6 @@
DumpedOscillationAnalysis,
OscillationAnalysis,
ResonanceAnalysis,
GaussianAnalysis,
ErrorAmplificationAnalysis,
)
11 changes: 11 additions & 0 deletions qiskit_experiments/curve_analysis/fit_function.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,17 @@ def gaussian(
return amp * np.exp(-((x - x0) ** 2) / (2 * sigma ** 2)) + baseline


def lorentzian(
eggerdj marked this conversation as resolved.
Show resolved Hide resolved
x: np.ndarray, amp: float = 1.0, kappa: float = 1.0, x0: float = 0.0, baseline: float = 0.0
) -> np.ndarray:
r"""Lorentzian function for spectroscopy

.. math::
y = {\rm amp}{\rm abs}\left(\frac{1}{1 + 2i(x - x0)/\kappa}\right) + {\rm baseline}
"""
return amp * np.abs(1 / (1 + 2.0j * (x - x0) / kappa)) + baseline


def cos_decay(
x: np.ndarray,
amp: float = 1.0,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@

from .oscillation import OscillationAnalysis, DumpedOscillationAnalysis
from .resonance import ResonanceAnalysis
from .gaussian import GaussianAnalysis
from .error_amplification_analysis import ErrorAmplificationAnalysis
from .decay import DecayAnalysis
156 changes: 156 additions & 0 deletions qiskit_experiments/curve_analysis/standard_analysis/gaussian.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
# 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.

"""Resonance analysis class based on a Gaussian fit."""

from typing import List, Union

import numpy as np

import qiskit_experiments.curve_analysis as curve
from qiskit_experiments.framework import Options


class GaussianAnalysis(curve.CurveAnalysis):
r"""A class to analyze a resonance, typically seen as a peak.

Overview
This analysis takes only single series. This series is fit by the Gaussian function.

Fit Model
The fit is based on the following Gaussian function.

.. math::

F(x) = a \exp(-(x-f)^2/(2\sigma^2)) + b

Fit Parameters
- :math:`a`: Peak height.
- :math:`b`: Base line.
- :math:`f`: Center frequency. This is the fit parameter of main interest.
- :math:`\sigma`: Standard deviation of Gaussian function.

Initial Guesses
- :math:`a`: Calculated by :func:`~qiskit_experiments.curve_analysis.guess.max_height`.
- :math:`b`: Calculated by :func:`~qiskit_experiments.curve_analysis.guess.\
constant_spectral_offset`.
- :math:`f`: Frequency at max height position calculated by
:func:`~qiskit_experiments.curve_analysis.guess.max_height`.
- :math:`\sigma`: Calculated from FWHM of peak :math:`w`
such that :math:`w / \sqrt{8} \ln{2}`, where FWHM is calculated by
:func:`~qiskit_experiments.curve_analysis.guess.full_width_half_max`.

Bounds
- :math:`a`: [-2, 2] scaled with maximum signal value.
- :math:`b`: [-1, 1] scaled with maximum signal value.
- :math:`f`: [min(x), max(x)] of frequency scan range.
- :math:`\sigma`: [0, :math:`\Delta x`] where :math:`\Delta x`
represents frequency scan range.

"""

__series__ = [
curve.SeriesDef(
fit_func=lambda x, a, sigma, freq, b: curve.fit_function.gaussian(
x, amp=a, sigma=sigma, x0=freq, baseline=b
),
plot_color="blue",
model_description=r"a \exp(-(x-f)^2/(2\sigma^2)) + b",
)
]

@classmethod
def _default_options(cls) -> Options:
options = super()._default_options()
options.result_parameters = [curve.ParameterRepr("freq", "f01", "Hz")]
options.normalization = True
options.xlabel = "Frequency"
options.ylabel = "Signal (arb. units)"
options.xval_unit = "Hz"
return options

def _generate_fit_guesses(
self, user_opt: curve.FitOptions
) -> Union[curve.FitOptions, List[curve.FitOptions]]:
"""Compute the initial guesses.

Args:
user_opt: Fit options filled with user provided guess and bounds.

Returns:
List of fit options that are passed to the fitter function.
"""
curve_data = self._data()
max_abs_y, _ = curve.guess.max_height(curve_data.y, absolute=True)

user_opt.bounds.set_if_empty(
a=(-2 * max_abs_y, 2 * max_abs_y),
sigma=(0, np.ptp(curve_data.x)),
freq=(min(curve_data.x), max(curve_data.x)),
b=(-max_abs_y, max_abs_y),
)
user_opt.p0.set_if_empty(b=curve.guess.constant_spectral_offset(curve_data.y))

y_ = curve_data.y - user_opt.p0["b"]

_, peak_idx = curve.guess.max_height(y_, absolute=True)
fwhm = curve.guess.full_width_half_max(curve_data.x, y_, peak_idx)

user_opt.p0.set_if_empty(
a=curve_data.y[peak_idx] - user_opt.p0["b"],
freq=curve_data.x[peak_idx],
sigma=fwhm / np.sqrt(8 * np.log(2)),
)

return user_opt

def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
"""Algorithmic criteria for whether the fit is good or bad.

A good fit has:
- a reduced chi-squared less than 3,
- a peak within the scanned frequency range,
- a standard deviation that is not larger than the scanned frequency range,
- a standard deviation that is wider than the smallest frequency increment,
- a signal-to-noise ratio, defined as the amplitude of the peak divided by the
square root of the median y-value less the fit offset, greater than a
threshold of two, and
- a standard error on the sigma of the Gaussian that is smaller than the sigma.
"""
curve_data = self._data()

max_freq = np.max(curve_data.x)
min_freq = np.min(curve_data.x)
freq_increment = np.mean(np.diff(curve_data.x))

fit_a = fit_data.fitval("a").value
fit_b = fit_data.fitval("b").value
fit_freq = fit_data.fitval("freq").value
fit_sigma = fit_data.fitval("sigma").value
fit_sigma_err = fit_data.fitval("sigma").stderr

snr = abs(fit_a) / np.sqrt(abs(np.median(curve_data.y) - fit_b))
fit_width_ratio = fit_sigma / (max_freq - min_freq)

criteria = [
min_freq <= fit_freq <= max_freq,
1.5 * freq_increment < fit_sigma,
fit_width_ratio < 0.25,
fit_data.reduced_chisq < 3,
(fit_sigma_err is None or fit_sigma_err < fit_sigma),
snr > 2,
]

if all(criteria):
return "good"

return "bad"
47 changes: 23 additions & 24 deletions qiskit_experiments/curve_analysis/standard_analysis/resonance.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,50 +21,49 @@


class ResonanceAnalysis(curve.CurveAnalysis):
r"""A class to analyze a resonance, typically seen as a peak.
r"""A class to analyze a resonance, typically seen as a Lorentzian peak.
eggerdj marked this conversation as resolved.
Show resolved Hide resolved

Overview
This analysis takes only single series. This series is fit by the Gaussian function.
This analysis takes only single series. This series is fit by the Lorentzian function.

Fit Model
The fit is based on the following Gaussian function.
The fit is based on the following Lorentzian function.

.. math::

F(x) = a \exp(-(x-f)^2/(2\sigma^2)) + b
F(x) = a{\rm abs}\left(\frac{1}{1 + 2i(x - x0)/\kappa}\right) + b

Fit Parameters
- :math:`a`: Peak height.
- :math:`b`: Base line.
- :math:`f`: Center frequency. This is the fit parameter of main interest.
- :math:`\sigma`: Standard deviation of Gaussian function.
- :math:`x0`: Center value. This is typically the fit parameter of interest.
- :math:`\kappa`: Linewidth.

Initial Guesses
- :math:`a`: Calculated by :func:`~qiskit_experiments.curve_analysis.guess.max_height`.
- :math:`b`: Calculated by :func:`~qiskit_experiments.curve_analysis.guess.\
constant_spectral_offset`.
- :math:`f`: Frequency at max height position calculated by
- :math:`x0`: The max height position is calculated by the function
:func:`~qiskit_experiments.curve_analysis.guess.max_height`.
- :math:`\sigma`: Calculated from FWHM of peak :math:`w`
such that :math:`w / \sqrt{8} \ln{2}`, where FWHM is calculated by
- :math:`\kappa`: Calculated from FWHM of the peak using
:func:`~qiskit_experiments.curve_analysis.guess.full_width_half_max`.

Bounds
- :math:`a`: [-2, 2] scaled with maximum signal value.
- :math:`b`: [-1, 1] scaled with maximum signal value.
- :math:`f`: [min(x), max(x)] of frequency scan range.
- :math:`\sigma`: [0, :math:`\Delta x`] where :math:`\Delta x`
represents frequency scan range.
- :math:`f`: [min(x), max(x)] of x-value scan range.
- :math:`\kappa`: [0, :math:`\Delta x`] where :math:`\Delta x`
represents the x-value scan range.

"""

__series__ = [
curve.SeriesDef(
fit_func=lambda x, a, sigma, freq, b: curve.fit_function.gaussian(
x, amp=a, sigma=sigma, x0=freq, baseline=b
fit_func=lambda x, a, kappa, freq, b: curve.fit_function.lorentzian(
x, amp=a, kappa=kappa, x0=freq, baseline=b
),
plot_color="blue",
model_description=r"a \exp(-(x-f)^2/(2\sigma^2)) + b",
model_description=r"a abs(1 / (1 + 2i * (x - x_0) / \kappa)) + b",
)
]

Expand Down Expand Up @@ -94,7 +93,7 @@ def _generate_fit_guesses(

user_opt.bounds.set_if_empty(
a=(-2 * max_abs_y, 2 * max_abs_y),
sigma=(0, np.ptp(curve_data.x)),
kappa=(0, np.ptp(curve_data.x)),
freq=(min(curve_data.x), max(curve_data.x)),
b=(-max_abs_y, max_abs_y),
)
Expand All @@ -106,9 +105,9 @@ def _generate_fit_guesses(
fwhm = curve.guess.full_width_half_max(curve_data.x, y_, peak_idx)

user_opt.p0.set_if_empty(
a=curve_data.y[peak_idx] - user_opt.p0["b"],
a=(curve_data.y[peak_idx] - user_opt.p0["b"]),
freq=curve_data.x[peak_idx],
sigma=fwhm / np.sqrt(8 * np.log(2)),
kappa=fwhm,
)

return user_opt
Expand All @@ -124,7 +123,7 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
- a signal-to-noise ratio, defined as the amplitude of the peak divided by the
square root of the median y-value less the fit offset, greater than a
threshold of two, and
- a standard error on the sigma of the Gaussian that is smaller than the sigma.
- a standard error on the kappa of the Lorentzian that is smaller than the kappa.
"""
curve_data = self._data()

Expand All @@ -135,18 +134,18 @@ def _evaluate_quality(self, fit_data: curve.FitData) -> Union[str, None]:
fit_a = fit_data.fitval("a").value
fit_b = fit_data.fitval("b").value
fit_freq = fit_data.fitval("freq").value
fit_sigma = fit_data.fitval("sigma").value
fit_sigma_err = fit_data.fitval("sigma").stderr
fit_kappa = fit_data.fitval("kappa").value
fit_kappa_err = fit_data.fitval("kappa").stderr

snr = abs(fit_a) / np.sqrt(abs(np.median(curve_data.y) - fit_b))
fit_width_ratio = fit_sigma / (max_freq - min_freq)
fit_width_ratio = fit_kappa / (max_freq - min_freq)

criteria = [
min_freq <= fit_freq <= max_freq,
1.5 * freq_increment < fit_sigma,
1.5 * freq_increment < fit_kappa,
fit_width_ratio < 0.25,
fit_data.reduced_chisq < 3,
(fit_sigma_err is None or fit_sigma_err < fit_sigma),
(fit_kappa_err is None or fit_kappa_err < fit_kappa),
snr > 2,
]

Expand Down
26 changes: 26 additions & 0 deletions qiskit_experiments/data_processing/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"""Different data analysis steps."""

from abc import abstractmethod
from enum import Enum
from numbers import Number
from typing import List, Union, Sequence

Expand Down Expand Up @@ -374,6 +375,22 @@ def _process(self, data: np.ndarray) -> np.ndarray:
return data[..., 1] * self.scale


class ToAbs(IQPart):
"""IQ data post-processing. Take the absolute value of the IQ point."""

def _process(self, data: np.array) -> np.array:
"""Take the absolute value of the IQ data.

Args:
data: An N-dimensional array of complex IQ point as [real, imaginary].

Returns:
A N-1 dimensional array, each entry is the absolute value of the given IQ data.
"""
# pylint: disable=no-member
return unp.sqrt(data[..., 0] ** 2 + data[..., 1] ** 2) * self.scale


class Probability(DataAction):
r"""Compute the mean probability of a single measurement outcome from counts.

Expand Down Expand Up @@ -555,3 +572,12 @@ def _process(self, data: np.ndarray) -> np.ndarray:
The data that has been processed.
"""
return 2 * (0.5 - data)


class ProjectorType(Enum):
"""Types of projectors for data dimensionality reduction."""

SVD = SVD
ABS = ToAbs
REAL = ToReal
IMAG = ToImag
Loading