From 2d58a8cb76206a0821527b227342a816c54bf0cc Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 27 Jul 2021 19:03:34 +0900 Subject: [PATCH 1/7] add fit model metadata --- .../curve_analysis/curve_analysis.py | 5 + .../curve_analysis/curve_data.py | 3 + .../curve_analysis/fit_function.py | 2 +- .../calibration/analysis/drag_analysis.py | 5 +- .../analysis/fine_amplitude_analysis.py | 317 +++++++++--------- .../analysis/oscillation_analysis.py | 1 + .../characterization/resonance_analysis.py | 1 + .../interleaved_rb_analysis.py | 2 + .../randomized_benchmarking/rb_analysis.py | 1 + 9 files changed, 177 insertions(+), 160 deletions(-) diff --git a/qiskit_experiments/curve_analysis/curve_analysis.py b/qiskit_experiments/curve_analysis/curve_analysis.py index d43029201d..4a4b0ae76b 100644 --- a/qiskit_experiments/curve_analysis/curve_analysis.py +++ b/qiskit_experiments/curve_analysis/curve_analysis.py @@ -867,6 +867,11 @@ def _run_analysis( """ result_data = CurveAnalysisResultData() result_data["analysis_type"] = self.__class__.__name__ + + # add model description + result_data["fit_models"] = { + series_def.name: series_def.model_description for series_def in self.__series__ + } figures = list() # diff --git a/qiskit_experiments/curve_analysis/curve_data.py b/qiskit_experiments/curve_analysis/curve_data.py index d113995c55..7fa823d22a 100644 --- a/qiskit_experiments/curve_analysis/curve_data.py +++ b/qiskit_experiments/curve_analysis/curve_data.py @@ -41,6 +41,9 @@ class SeriesDef: # Whether to plot fit uncertainty for this line. plot_fit_uncertainty: bool = False + # Latex description of this fit model + model_description: str = "no description" + @dataclasses.dataclass(frozen=True) class CurveData: diff --git a/qiskit_experiments/curve_analysis/fit_function.py b/qiskit_experiments/curve_analysis/fit_function.py index c7f04c0888..849e7feea4 100644 --- a/qiskit_experiments/curve_analysis/fit_function.py +++ b/qiskit_experiments/curve_analysis/fit_function.py @@ -45,7 +45,7 @@ def sin( .. math:: y = {\rm amp} \sin\left(2 \pi {\fm freq} x + {\rm phase}\right) + {\rm baseline} """ - return amp * np.cos(2 * np.pi * freq * x + phase) + baseline + return amp * np.sin(2 * np.pi * freq * x + phase) + baseline def exponential_decay( diff --git a/qiskit_experiments/library/calibration/analysis/drag_analysis.py b/qiskit_experiments/library/calibration/analysis/drag_analysis.py index a085e83c13..aca7bd09a1 100644 --- a/qiskit_experiments/library/calibration/analysis/drag_analysis.py +++ b/qiskit_experiments/library/calibration/analysis/drag_analysis.py @@ -35,7 +35,7 @@ class DragCalAnalysis(CurveAnalysis): .. math:: - y = {\rm amp} \cos\left(2 \pi {\rm freq}_i x - 2 \pi {\rm beta}\right) + {\rm base} + y = {\rm amp} \cos\left(2 \pi {\rm freq}_i x - 2 \pi {\rm beta}\right) + b Fit Parameters - :math:`{\rm amp}`: Amplitude of all series. @@ -65,6 +65,7 @@ class DragCalAnalysis(CurveAnalysis): name="series-0", filter_kwargs={"series": 0}, plot_symbol="o", + model_description=r"{\rm amp} \cos(2 \pi f_0 (x - beta)) + b", ), SeriesDef( fit_func=lambda x, amp, freq0, freq1, freq2, beta, base: cos( @@ -74,6 +75,7 @@ class DragCalAnalysis(CurveAnalysis): name="series-1", filter_kwargs={"series": 1}, plot_symbol="^", + model_description=r"{\rm amp} \cos(2 \pi f_1 (x - beta)) + b", ), SeriesDef( fit_func=lambda x, amp, freq0, freq1, freq2, beta, base: cos( @@ -83,6 +85,7 @@ class DragCalAnalysis(CurveAnalysis): name="series-2", filter_kwargs={"series": 2}, plot_symbol="v", + model_description=r"{\rm amp} \cos(2 \pi f_2 (x - beta)) + b", ), ] diff --git a/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py b/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py index 3986b54fb8..4b56c73557 100644 --- a/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py +++ b/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py @@ -1,158 +1,159 @@ -# 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. - -"""Fine amplitude calibration analysis.""" - -from typing import Any, Dict, List, Union -import numpy as np - -from qiskit_experiments.exceptions import CalibrationError -from qiskit_experiments.curve_analysis import ( - CurveAnalysis, - CurveAnalysisResultData, - SeriesDef, - fit_function, - get_opt_value, -) - - -class FineAmplitudeAnalysis(CurveAnalysis): - r"""Fine amplitude analysis class based on a fit to a cosine function. - - Analyse a fine amplitude calibration experiment by fitting the data to a cosine function. - The user must also specify the intended rotation angle per gate, here labeled, - :math:`{\rm apg}`. The parameter of interest in the - fit is the deviation from the intended rotation angle per gate labeled :math:`{\rm d}\theta`. - The fit function is - - .. math:: - y = {\rm amp}/2\cos\left(x[{\rm d}\theta + {\rm apg} ]+{\rm phase\_offset}\right)+baseline - - Fit Parameters - - :math:`amp`: Amplitude of the oscillation. - - :math:`baseline`: Base line. - - :math:`{\rm d}\theta`: The angle offset in the gate that we wish to measure. - - Initial Guesses - - :math:`amp`: The maximum y value less the minimum y value. - - :math:`baseline`: The average of the data. - - :math:`{\rm d}\theta`: Zero. - - Bounds - - :math:`amp`: [-2, 2] scaled to the maximum signal value. - - :math:`baseline`: [-1, 1] scaled to the maximum signal value. - - :math:`{\rm d}\theta`: [-pi, pi]. - - Fixed-value parameters: - - :math:`{\rm apg}` The angle per gate is set by the user, for example pi for a pi-pulse. - - :math:`{\rm phase\_offset}` The phase offset in the cosine oscillation, for example, - :math:`\pi/2` if a square-root of X gate is added before the repeated gates. - """ - - __series__ = [ - SeriesDef( - fit_func=lambda x, amp, d_theta, phase_offset, baseline, angle_per_gate: fit_function.cos( - x, - amp=0.5 * amp, - freq=(d_theta + angle_per_gate) / (2 * np.pi), - phase=phase_offset, - baseline=baseline, - ), - plot_color="blue", - ) - ] - - # The intended angle per gat of the gate being calibrated, e.g. pi for a pi-pulse. - __fixed_parameters__ = ["angle_per_gate", "phase_offset"] - - @classmethod - def _default_options(cls): - """Return the default analysis options. - - See :meth:`~qiskit_experiment.curve_analysis.CurveAnalysis._default_options` for - descriptions of analysis options. - """ - default_options = super()._default_options() - default_options.p0 = {"amp": None, "d_theta": None, "phase": None, "baseline": None} - default_options.bounds = {"amp": None, "d_theta": None, "phase": None, "baseline": None} - default_options.fit_reports = {"d_theta": "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.number_guesses = 21 - default_options.max_good_angle_error = np.pi / 2 - - return default_options - - 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") - - if angle_per_gate is None: - raise CalibrationError("The angle_per_gate was not specified in the analysis options.") - - guess_range = max(abs(angle_per_gate), np.pi / 2) - - fit_options = [] - - for angle in np.linspace(-guess_range, guess_range, n_guesses): - fit_option = { - "p0": { - "amp": user_p0["amp"] or a_guess, - "d_theta": angle, - "baseline": 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), - "baseline": user_bounds.get("d_theta", None) or (-1 * max_abs_y, 1 * max_abs_y), - }, - } - - fit_options.append(fit_option) - - return fit_options - - def _post_analysis(self, result_data: CurveAnalysisResultData) -> CurveAnalysisResultData: - """Algorithmic criteria for whether the fit is good or bad. - - A good fit has: - - a reduced chi-squared lower than three, - - a measured angle error that is smaller than the allowed maximum good angle error. - This quantity is set in the analysis options. - """ - fit_d_theta = get_opt_value(result_data, "d_theta") - max_good_angle_error = self._get_option("max_good_angle_error") - - criteria = [ - result_data["reduced_chisq"] < 3, - abs(fit_d_theta) < abs(max_good_angle_error), - ] - - if all(criteria): - result_data["quality"] = "good" - else: - result_data["quality"] = "bad" - - return result_data +# 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. + +"""Fine amplitude calibration analysis.""" + +from typing import Any, Dict, List, Union +import numpy as np + +from qiskit_experiments.exceptions import CalibrationError +from qiskit_experiments.curve_analysis import ( + CurveAnalysis, + CurveAnalysisResultData, + SeriesDef, + fit_function, + get_opt_value, +) + + +class FineAmplitudeAnalysis(CurveAnalysis): + r"""Fine amplitude analysis class based on a fit to a cosine function. + + Analyse a fine amplitude calibration experiment by fitting the data to a cosine function. + The user must also specify the intended rotation angle per gate, here labeled, + :math:`{\rm apg}`. The parameter of interest in the + fit is the deviation from the intended rotation angle per gate labeled :math:`{\rm d}\theta`. + The fit function is + + .. math:: + y = {\rm amp}/2\cos\left(x[{\rm d}\theta + {\rm apg} ]+{\rm phase\_offset}\right)+b + + Fit Parameters + - :math:`amp`: Amplitude of the oscillation. + - :math:`baseline`: Base line. + - :math:`{\rm d}\theta`: The angle offset in the gate that we wish to measure. + + Initial Guesses + - :math:`amp`: The maximum y value less the minimum y value. + - :math:`baseline`: The average of the data. + - :math:`{\rm d}\theta`: Zero. + + Bounds + - :math:`amp`: [-2, 2] scaled to the maximum signal value. + - :math:`baseline`: [-1, 1] scaled to the maximum signal value. + - :math:`{\rm d}\theta`: [-pi, pi]. + + Fixed-value parameters: + - :math:`{\rm apg}` The angle per gate is set by the user, for example pi for a pi-pulse. + - :math:`{\rm phase\_offset}` The phase offset in the cosine oscillation, for example, + :math:`\pi/2` if a square-root of X gate is added before the repeated gates. + """ + + __series__ = [ + SeriesDef( + fit_func=lambda x, amp, d_theta, phase_offset, base, angle_per_gate: fit_function.cos( + x, + amp=0.5 * amp, + freq=(d_theta + angle_per_gate) / (2 * np.pi), + phase=phase_offset, + baseline=base, + ), + plot_color="blue", + model_description=r"{\rm amp} \cos(({\rm d}\theta + {\rm apg}) x " + r"+ \theta_{\rm offset}) + b", + ) + ] + + # The intended angle per gat of the gate being calibrated, e.g. pi for a pi-pulse. + __fixed_parameters__ = ["angle_per_gate", "phase_offset"] + + @classmethod + def _default_options(cls): + """Return the default analysis options. + + See :meth:`~qiskit_experiment.curve_analysis.CurveAnalysis._default_options` for + descriptions of analysis options. + """ + 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.fit_reports = {"d_theta": "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.number_guesses = 21 + default_options.max_good_angle_error = np.pi / 2 + + return default_options + + 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") + + if angle_per_gate is None: + raise CalibrationError("The angle_per_gate was not specified in the analysis options.") + + guess_range = max(abs(angle_per_gate), np.pi / 2) + + fit_options = [] + for angle in np.linspace(-guess_range, guess_range, n_guesses): + fit_option = { + "p0": { + "amp": user_p0["amp"] or a_guess, + "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("base", None) or (-1 * max_abs_y, 1 * max_abs_y), + }, + } + + fit_options.append(fit_option) + + return fit_options + + def _post_analysis(self, result_data: CurveAnalysisResultData) -> CurveAnalysisResultData: + """Algorithmic criteria for whether the fit is good or bad. + + A good fit has: + - a reduced chi-squared lower than three, + - a measured angle error that is smaller than the allowed maximum good angle error. + This quantity is set in the analysis options. + """ + fit_d_theta = get_opt_value(result_data, "d_theta") + max_good_angle_error = self._get_option("max_good_angle_error") + + criteria = [ + result_data["reduced_chisq"] < 3, + abs(fit_d_theta) < abs(max_good_angle_error), + ] + + if all(criteria): + result_data["quality"] = "good" + else: + result_data["quality"] = "bad" + + return result_data diff --git a/qiskit_experiments/library/calibration/analysis/oscillation_analysis.py b/qiskit_experiments/library/calibration/analysis/oscillation_analysis.py index d7b070f897..88006f7210 100644 --- a/qiskit_experiments/library/calibration/analysis/oscillation_analysis.py +++ b/qiskit_experiments/library/calibration/analysis/oscillation_analysis.py @@ -62,6 +62,7 @@ class OscillationAnalysis(CurveAnalysis): x, amp=amp, freq=freq, phase=phase, baseline=baseline ), plot_color="blue", + model_description=r"{\rm amp} \cos(2 \pi f x + \theta) + b", ) ] diff --git a/qiskit_experiments/library/characterization/resonance_analysis.py b/qiskit_experiments/library/characterization/resonance_analysis.py index 4e7421620c..0ca4597e46 100644 --- a/qiskit_experiments/library/characterization/resonance_analysis.py +++ b/qiskit_experiments/library/characterization/resonance_analysis.py @@ -71,6 +71,7 @@ class ResonanceAnalysis(CurveAnalysis): x, amp=a, sigma=sigma, x0=freq, baseline=b ), plot_color="blue", + model_description=r"a \exp(-(x-f)^2/(2\sigma^2)) + b", ) ] diff --git a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py index d2db5cf8c2..270f6527f3 100644 --- a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py @@ -107,6 +107,7 @@ class InterleavedRBAnalysis(RBAnalysis): plot_color="red", plot_symbol=".", plot_fit_uncertainty=True, + model_description=r"a \alpha^{x} + b", ), SeriesDef( name="Interleaved", @@ -117,6 +118,7 @@ class InterleavedRBAnalysis(RBAnalysis): plot_color="orange", plot_symbol="^", plot_fit_uncertainty=True, + model_description=r"a (\alpha_c\alpha)^{x} + b", ), ] diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py index 4e7abfbe1d..d7751dc2c1 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py @@ -63,6 +63,7 @@ class RBAnalysis(curve.CurveAnalysis): ), plot_color="blue", plot_fit_uncertainty=True, + model_description=r"a \alpha^x + b", ) ] From 78c00dfe5cead715d4f69a7165ee9ec906069c93 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 27 Jul 2021 21:12:57 +0900 Subject: [PATCH 2/7] update curve fit test --- test/curve_analysis/test_curve_fit.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test/curve_analysis/test_curve_fit.py b/test/curve_analysis/test_curve_fit.py index dabdfe7f88..9c55529d87 100644 --- a/test/curve_analysis/test_curve_fit.py +++ b/test/curve_analysis/test_curve_fit.py @@ -86,6 +86,7 @@ def setUp(self): x, amp=p0, lamb=p1, baseline=p4 ), filter_kwargs={"type": 1, "valid": True}, + model_description=r"p_0 * \exp(p_1 x) + p4", ), SeriesDef( name="curve2", @@ -93,6 +94,7 @@ def setUp(self): x, amp=p0, lamb=p2, baseline=p4 ), filter_kwargs={"type": 2, "valid": True}, + model_description=r"p_0 * \exp(p_2 x) + p4", ), SeriesDef( name="curve3", @@ -100,6 +102,7 @@ def setUp(self): x, amp=p0, lamb=p3, baseline=p4 ), filter_kwargs={"type": 3, "valid": True}, + model_description=r"p_0 * \exp(p_3 x) + p4", ), ], ) @@ -282,6 +285,7 @@ def test_run_single_curve_analysis(self): fit_func=lambda x, p0, p1, p2, p3: fit_function.exponential_decay( x, amp=p0, lamb=p1, x0=p2, baseline=p3 ), + model_description=r"p_0 \exp(p_1 x + p_2) + p_3", ) ], ) @@ -308,6 +312,7 @@ def test_run_single_curve_analysis(self): self.assertEqual(result["dof"], 46) self.assertListEqual(result["xrange"], [0.1, 1.0]) self.assertListEqual(result["popt_keys"], ["p0", "p1", "p2", "p3"]) + self.assertDictEqual(result["fit_models"], {"curve1": r"p_0 \exp(p_1 x + p_2) + p_3"}) def test_run_single_curve_fail(self): """Test analysis returns status when it fails.""" @@ -342,7 +347,7 @@ def test_run_single_curve_fail(self): self.assertFalse(result["success"]) - ref_result_keys = ["analysis_type", "error_message", "success", "raw_data"] + ref_result_keys = ["analysis_type", "fit_models", "error_message", "success", "raw_data"] self.assertSetEqual(set(result.keys()), set(ref_result_keys)) def test_run_two_curves_with_same_fitfunc(self): From f2310fd4483816890d69a3c6b3c84d0e282d50a1 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 27 Jul 2021 21:42:42 +0900 Subject: [PATCH 3/7] update ub test --- test/randomized_benchmarking/test_rb_analysis.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/randomized_benchmarking/test_rb_analysis.py b/test/randomized_benchmarking/test_rb_analysis.py index 1e5c7b1e30..82db1d4958 100644 --- a/test/randomized_benchmarking/test_rb_analysis.py +++ b/test/randomized_benchmarking/test_rb_analysis.py @@ -175,6 +175,8 @@ def _validate_fitting_parameters( calculated_analysis_sample_data.data()[key], expected_analysis_samples_data[idx][key], ) + elif key == "fit_models": + pass else: self.assertTrue( np.allclose( From 54cd51e34514aa940ad96eb0db0e98f253b5d14e Mon Sep 17 00:00:00 2001 From: knzwnao Date: Tue, 27 Jul 2021 21:51:38 +0900 Subject: [PATCH 4/7] black --- .../library/calibration/analysis/fine_amplitude_analysis.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py b/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py index 4b56c73557..deb23d2c96 100644 --- a/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py +++ b/qiskit_experiments/library/calibration/analysis/fine_amplitude_analysis.py @@ -69,7 +69,7 @@ class FineAmplitudeAnalysis(CurveAnalysis): ), plot_color="blue", model_description=r"{\rm amp} \cos(({\rm d}\theta + {\rm apg}) x " - r"+ \theta_{\rm offset}) + b", + r"+ \theta_{\rm offset}) + b", ) ] From cc06001cab66d8706b1713b3f0614157a46c7cd4 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 28 Jul 2021 02:41:33 +0900 Subject: [PATCH 5/7] auto fit model documentation --- .../example/example_experiment.py | 8 ++++- docs/_ext/custom_styles/formatter.py | 3 +- docs/_ext/custom_styles/styles.py | 17 +++++++++-- docs/_ext/custom_styles/utils.py | 30 +++++++++++++++++++ docs/_static/gallery.css | 4 +++ 5 files changed, 57 insertions(+), 5 deletions(-) diff --git a/docs/_ext/custom_styles/example/example_experiment.py b/docs/_ext/custom_styles/example/example_experiment.py index 1fa3a0ac4a..f43db6595e 100644 --- a/docs/_ext/custom_styles/example/example_experiment.py +++ b/docs/_ext/custom_styles/example/example_experiment.py @@ -44,7 +44,13 @@ class DocumentedCurveAnalysis(CurveAnalysis): In addition to above sections, analysis template provides following extra sections. # section: fit_model - Here you can describe your fitting model. + Optional. Here you can describe your fitting model. + If you are documenting a CurveAnalysis subclass, Sphinx automatically generates + the fit model descriptions based on descriptions in the SeriesDef. + However you can still override this section. + If you want to provide the fit model description for other analysis classes, + please write this section. + Standard reStructuredText directives can be used. For example: .. math:: diff --git a/docs/_ext/custom_styles/formatter.py b/docs/_ext/custom_styles/formatter.py index 40c61892ac..869505d074 100644 --- a/docs/_ext/custom_styles/formatter.py +++ b/docs/_ext/custom_styles/formatter.py @@ -181,8 +181,7 @@ def format_fit_model(self, lines: List[str]) -> List[str]: format_lines = [ ".. rubric:: Fit Model", "", - "This is the curve fitting analysis. ", - "Following equation(s) are used to represent curve(s).", + "Following fit models are used to represent the experimental result.", "", ] format_lines.extend(lines) diff --git a/docs/_ext/custom_styles/styles.py b/docs/_ext/custom_styles/styles.py index 91d2dfaa3c..262ee7fb39 100644 --- a/docs/_ext/custom_styles/styles.py +++ b/docs/_ext/custom_styles/styles.py @@ -21,6 +21,7 @@ from qiskit_experiments.framework.base_analysis import BaseAnalysis from qiskit_experiments.framework.base_experiment import BaseExperiment +from qiskit_experiments.curve_analysis import CurveAnalysis from sphinx.config import Config as SphinxConfig from .formatter import ( @@ -29,7 +30,11 @@ DocstringSectionFormatter, ) from .section_parsers import load_standard_section, load_fit_parameters -from .utils import _generate_options_documentation, _format_default_options +from .utils import ( + _generate_options_documentation, + _generate_fit_model_documentation, + _format_default_options, +) section_regex = re.compile(r"# section: (?P\S+)") @@ -111,7 +116,8 @@ def add_new_section(section: str, lines: List[str]): add_new_section(current_section, temp_lines) for section, lines in self._extra_sections().items(): - sectioned_docstrings[section] = lines + if section not in sectioned_docstrings: + sectioned_docstrings[section] = lines return sectioned_docstrings @@ -320,4 +326,11 @@ def _extra_sections(self) -> Dict[str, List[str]]: if analysis_option: parsed_sections["analysis_opts"] = analysis_option + # generate fit model from series definitions if curve analysis + if issubclass(self._target_cls, CurveAnalysis): + parsed_sections["fit_model"] = _generate_fit_model_documentation( + series_defs=self._target_cls.__series__, + indent=self._indent, + ) + return parsed_sections diff --git a/docs/_ext/custom_styles/utils.py b/docs/_ext/custom_styles/utils.py index 46e7e63381..8576f99373 100644 --- a/docs/_ext/custom_styles/utils.py +++ b/docs/_ext/custom_styles/utils.py @@ -21,6 +21,8 @@ from sphinx.ext.napoleon.docstring import GoogleDocstring from sphinx.util.docstrings import prepare_docstring +from qiskit_experiments.curve_analysis import SeriesDef + def _trim_empty_lines(docstring_lines: List[str]) -> List[str]: """A helper function to remove redundant line feeds.""" @@ -123,6 +125,34 @@ def _generate_options_documentation( return options_docstring_lines +def _generate_fit_model_documentation(series_defs: List[SeriesDef], indent: str = "") -> List[str]: + """Automatically generate fit model documentation from the series definition.""" + n_curves = len(series_defs) + + fit_model_docstring_lines = [] + for idx, series_def in enumerate(series_defs): + if series_def.model_description is None: + continue + if n_curves > 1: + fit_model_docstring_lines.extend([ + f"- Fit model for the curve ``{series_def.name}``:", + "" + ]) + math_block = [ + ".. math::", + "", + indent + f"F(x) = {series_def.model_description} \\tag{idx + 1}", + "", + ] + fit_model_docstring_lines.extend(math_block) + + fit_model_docstring_lines.append( + "The information about the fit model is also stored in the analysis result metadata." + ) + + return fit_model_docstring_lines + + def _format_default_options(defaults: Dict[str, Any], indent: str = "") -> List[str]: """Format default options to docstring lines.""" docstring_lines = [ diff --git a/docs/_static/gallery.css b/docs/_static/gallery.css index e5f79a8c5a..b170ae2338 100644 --- a/docs/_static/gallery.css +++ b/docs/_static/gallery.css @@ -193,3 +193,7 @@ p.sphx-glr-signature a.reference.external { a.sphx-glr-backref-instance { text-decoration: none; } + +span.eqno { + float: right; +} From b1e30a5273f5faff584740c03106be27956b8590 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 28 Jul 2021 04:33:28 +0900 Subject: [PATCH 6/7] update rb analysis as example --- .../randomized_benchmarking/__init__.py | 5 ++ .../interleaved_rb_analysis.py | 68 +++++++------------ .../randomized_benchmarking/rb_analysis.py | 54 +++++++-------- 3 files changed, 57 insertions(+), 70 deletions(-) diff --git a/qiskit_experiments/library/randomized_benchmarking/__init__.py b/qiskit_experiments/library/randomized_benchmarking/__init__.py index 01ffad9bc4..5b8eb51afd 100644 --- a/qiskit_experiments/library/randomized_benchmarking/__init__.py +++ b/qiskit_experiments/library/randomized_benchmarking/__init__.py @@ -31,9 +31,14 @@ .. autosummary:: :toctree: ../stubs/ + :template: autosummary/analysis.rst RBAnalysis InterleavedRBAnalysis + +.. autosummary:: + :toctree: ../stubs/ + RBUtils """ from .rb_experiment import StandardRB diff --git a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py index 270f6527f3..894b0144a8 100644 --- a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py @@ -29,14 +29,13 @@ class InterleavedRBAnalysis(RBAnalysis): r"""A class to analyze interleaved randomized benchmarking experiment. - Overview + # section: overview This analysis takes only two series for standard and interleaved RB curve fitting. From the fit :math:`\alpha` and :math:`\alpha_c` value this analysis estimates the error per Clifford (EPC) of the interleaved gate. The EPC estimate is obtained using the equation - .. math:: r_{\mathcal{C}}^{\text{est}} = @@ -57,44 +56,30 @@ class InterleavedRBAnalysis(RBAnalysis): See Ref. [1] for more details. + # section: fit_parameters + defpar a: + desc: Height of decay curve. + init_guess: Determined by the average :math:`a` of the standard and interleaved RB. + bounds: [0, 1] + defpar b: + desc: Base line. + init_guess: Determined by the average :math:`b` of the standard and interleaved RB. + Usually equivalent to :math:`(1/2)^n` where :math:`n` is number of qubit. + bounds: [0, 1] + defpar \alpha: + desc: Depolarizing parameter. + init_guess: Determined by the slope of :math:`(y - b)^{-x}` of the first and the + second data point of the standard RB. + bounds: [0, 1] + defpar \alpha_c: + desc: Ratio of the depolarizing parameter of interleaved RB to standard RB curve. + init_guess: Estimate :math:`\alpha' = \alpha_c \alpha` from the + interleaved RB curve, then divide this by the initial guess of :math:`\alpha`. + bounds: [0, 1] + + # section: reference + .. ref_arxiv:: 1 1203.4550 - - Fit Model - The fit is based on the following decay functions: - - .. math:: - - F_1(x_1) &= a \alpha^{x_1} + b \quad {\rm for standard RB} \\ - F_2(x_2) &= a (\alpha_c \alpha)^{x_2} + b \quad {\rm for interleaved RB} - - Fit Parameters - - :math:`a`: Height of decay curve. - - :math:`b`: Base line. - - :math:`\alpha`: Depolarizing parameter. - - :math:`\alpha_c`: Ratio of the depolarizing parameter of - interleaved RB to standard RB curve. - - Initial Guesses - - :math:`a`: Determined by the average :math:`a` of the standard and interleaved RB. - - :math:`b`: Determined by the average :math:`b` of the standard and interleaved RB. - Usually equivalent to :math:`(1/2)**n` where :math:`n` is number of qubit. - - :math:`\alpha`: Determined by the slope of :math:`(y_1 - b)**(-x_1)` of the first and the - second data point of the standard RB. - - :math:`\alpha_c`: Estimate :math:`\alpha' = \alpha_c * \alpha` from the - interleaved RB curve, then divide this by the initial guess of :math:`\alpha`. - - Bounds - - :math:`a`: [0, 1] - - :math:`b`: [0, 1] - - :math:`\alpha`: [0, 1] - - :math:`\alpha_c`: [0, 1] - - References - 1. Easwar Magesan, Jay M. Gambetta, B. R. Johnson, Colm A. Ryan, Jerry M. Chow, - Seth T. Merkel, Marcus P. da Silva, George A. Keefe, Mary B. Rothwell, Thomas A. Ohki, - Mark B. Ketchen, M. Steffen, Efficient measurement of quantum gate error by - interleaved randomized benchmarking, - `arXiv:quant-ph/1203.4550 `_ """ __series__ = [ @@ -124,10 +109,7 @@ class InterleavedRBAnalysis(RBAnalysis): @classmethod def _default_options(cls): - """Return default data processing options. - - See :meth:`~qiskit_experiment.curve_analysis.CurveAnalysis._default_options` for - descriptions of analysis options. + """Return default analysis options. """ default_options = super()._default_options() default_options.p0 = {"a": None, "alpha": None, "alpha_c": None, "b": None} diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py index d7751dc2c1..9ed7f182c2 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py @@ -25,34 +25,26 @@ class RBAnalysis(curve.CurveAnalysis): r"""A class to analyze randomized benchmarking experiments. - Overview + # section: overview This analysis takes only single series. This series is fit by the exponential decay function. From the fit :math:`\alpha` value this analysis estimates the error per Clifford (EPC). - Fit Model - The fit is based on the following decay function: - - .. math:: - - F(x) = a \alpha^x + b - - Fit Parameters - - :math:`a`: Height of decay curve. - - :math:`b`: Base line. - - :math:`\alpha`: Depolarizing parameter. This is the fit parameter of main interest. - - Initial Guesses - - :math:`a`: Determined by :math:`(y_0 - b) / \alpha^x_0` - where :math:`b` and :math:`\alpha` are initial guesses. - - :math:`b`: Determined by :math:`(1/2)^n` where :math:`n` is the number of qubit. - - :math:`\alpha`: Determined by the slope of :math:`(y - b)^{-x}` of the first and the - second data point. - - Bounds - - :math:`a`: [0, 1] - - :math:`b`: [0, 1] - - :math:`\alpha`: [0, 1] + # section: fit_parameters + defpar a: + desc: Height of decay curve. + init_guess: Determined by :math:`(y - b) / \alpha^x`. + bounds: [0, 1] + defpar b: + desc: Base line. + init_guess: Determined by the average :math:`b` of the standard and interleaved RB. + Usually equivalent to :math:`(1/2)^n` where :math:`n` is number of qubit. + bounds: [0, 1] + defpar \alpha: + desc: Depolarizing parameter. + init_guess: Determined by the slope of :math:`(y - b)^{-x}` of the first and the + second data point. + bounds: [0, 1] """ @@ -69,10 +61,18 @@ class RBAnalysis(curve.CurveAnalysis): @classmethod def _default_options(cls): - """Return default options. + """Return default analysis options. + + Analysis Options: + error_dict (Dict[Tuple[Iterable[int], str], float]): Optional. + Error estimates for gates from the backend properties. + epg_1_qubit (Dict[int, Dict[str, float]]) : Optional. + EPG data for the 1-qubit gate involved, + assumed to have been obtained from previous experiments. + This is used to estimate the 2-qubit EPG. + gate_error_ratio (Dict[str, float]): An estimate for the ratios + between errors on different gates. - See :meth:`~qiskit_experiment.curve_analysis.CurveAnalysis._default_options` for - descriptions of analysis options. """ default_options = super()._default_options() default_options.p0 = {"a": None, "alpha": None, "b": None} From 7ff6b623387b65255012f003b7d1e94a839c7fd1 Mon Sep 17 00:00:00 2001 From: knzwnao Date: Wed, 28 Jul 2021 05:07:39 +0900 Subject: [PATCH 7/7] black --- .../library/randomized_benchmarking/interleaved_rb_analysis.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py index 894b0144a8..38e0a9c8a1 100644 --- a/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/interleaved_rb_analysis.py @@ -109,8 +109,7 @@ class InterleavedRBAnalysis(RBAnalysis): @classmethod def _default_options(cls): - """Return default analysis options. - """ + """Return default analysis options.""" default_options = super()._default_options() default_options.p0 = {"a": None, "alpha": None, "alpha_c": None, "b": None} default_options.bounds = {