Source code for qiskit_experiments.visualization.plotters.curve_plotter

# This code is part of Qiskit.
#
# (C) Copyright IBM 2022, 2023.
#
# 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.
"""Plotter for curve fits, specifically from :class:`.CurveAnalysis`."""
from typing import List

from uncertainties import UFloat

from qiskit_experiments.curve_analysis.utils import analysis_result_to_repr
from qiskit_experiments.framework import Options

from .base_plotter import BasePlotter


[docs] class CurvePlotter(BasePlotter): """A plotter class to plot results from :class:`.CurveAnalysis`. ``CurvePlotter`` plots results from curve fits, which includes - Raw results as a scatter plot. - Processed results with standard deviations/confidence intervals. - Interpolated fit results from the curve analysis. - Confidence interval for the fit results. - A report on the performance of the fit. """
[docs] @classmethod def expected_series_data_keys(cls) -> List[str]: """Returns the expected series data keys supported by this plotter. Data Keys: x: X-values for raw results. y: Y-values for raw results. Goes with ``x``. x_formatted: X-values for processed results. y_formatted: Y-values for processed results. Goes with ``x_formatted``. y_formatted_err: Error in ``y_formatted``, to be plotted as error-bars. x_interp: Interpolated X-values for a curve fit. y_interp: Y-values corresponding to the fit for ``y_interp`` X-values. y_interp_err: The standard deviations of the fit for each X-value in ``y_interp``. This data key relates to the option ``plot_sigma``. """ return [ "x", "y", "x_formatted", "y_formatted", "y_formatted_err", "x_interp", "y_interp", "y_interp_err", ]
[docs] @classmethod def expected_supplementary_data_keys(cls) -> List[str]: """Returns the expected figures data keys supported by this plotter. This plotter generates a single text box, i.e. fit report, by digesting the provided supplementary data. The style and position of the report is controlled by ``textbox_rel_pos`` and ``textbox_text_size`` style parameters in :class:`PlotStyle`. Data Keys: primary_results: A list of :class:`.AnalysisResultData` objects to be shown in the fit report window. Typically, these are fit parameter values or secondary quantities computed from multiple fit parameters. fit_red_chi: The best reduced-chi squared value of the fit curves. If the fit consists of multiple sub-fits, this will be a dictionary keyed on the analysis name. Otherwise, this is a single float value of a particular analysis. """ return [ "primary_results", "fit_red_chi", ]
@classmethod def _default_options(cls) -> Options: """Return curve-plotter specific default plotter options. Options: plot_sigma (List[Tuple[float, float]]): A list of two number tuples showing the configuration to write confidence intervals for the fit curve. The first argument is the relative sigma (n_sigma), and the second argument is the transparency of the interval plot in ``[0, 1]``. Multiple n_sigma intervals can be drawn for the same curve. """ options = super()._default_options() options.plot_sigma = [(1.0, 0.7), (3.0, 0.3)] return options @classmethod def _default_figure_options(cls) -> Options: r"""Return curve-plotter specific default figure options. Figure Options: report_red_chi2_label (str): The label for the reduced-chi squared entry of the fit report. Defaults to the Python string literal ``"reduced-$\\chi^2$"``, corresponding to the formatted string reduced-:math:`\chi^2`. """ fig_opts = super()._default_figure_options() fig_opts.report_red_chi2_label = "reduced-$\\chi^2$" return fig_opts def _plot_figure(self): """Plots a curve fit figure.""" for ser in self.series: # Scatter plot with error-bars plotted_formatted_data = False if self.data_exists_for(ser, ["x_formatted", "y_formatted", "y_formatted_err"]): x, y, yerr = self.data_for(ser, ["x_formatted", "y_formatted", "y_formatted_err"]) self.drawer.scatter(x, y, y_err=yerr, name=ser, zorder=2, legend=True) plotted_formatted_data = True # Scatter plot if self.data_exists_for(ser, ["x", "y"]): x, y = self.data_for(ser, ["x", "y"]) options = { "zorder": 1, } # If we plotted formatted data, differentiate scatter points by setting normal X-Y # markers to gray. if plotted_formatted_data: options["color"] = "gray" # If we didn't plot formatted data, the X-Y markers should be used for the legend. We add # it to ``options`` so it's easier to pass to ``scatter``. if not plotted_formatted_data: options["legend"] = True self.drawer.scatter( x, y, name=ser, **options, ) # Line plot for fit if self.data_exists_for(ser, ["x_interp", "y_interp"]): x, y = self.data_for(ser, ["x_interp", "y_interp"]) self.drawer.line(x, y, name=ser, zorder=3) # Confidence interval plot if self.data_exists_for(ser, ["x_interp", "y_interp", "y_interp_err"]): x, y_interp, y_interp_err = self.data_for( ser, ["x_interp", "y_interp", "y_interp_err"] ) for n_sigma, alpha in self.options.plot_sigma: self.drawer.filled_y_area( x, y_interp + n_sigma * y_interp_err, y_interp - n_sigma * y_interp_err, name=ser, alpha=alpha, zorder=5, ) # Fit report report = self._write_report() if len(report) > 0: self.drawer.textbox(report) def _write_report(self) -> str: """Write fit report with supplementary_data. Subclass can override this method to customize fit report. By default, this writes important fit parameters and chi-squared value of the fit in the fit report. The ``report_red_chi2_label`` figure option controls the label for the chi-squared entries in the report. Returns: Fit report. """ report = "" if "primary_results" in self.supplementary_data: lines = [] for outcome in self.supplementary_data["primary_results"]: if isinstance(outcome.value, (float, UFloat)): lines.append(analysis_result_to_repr(outcome)) report += "\n".join(lines) if "fit_red_chi" in self.supplementary_data: red_chi = self.supplementary_data["fit_red_chi"] if len(report) > 0: report += "\n" if isinstance(red_chi, float): report += f"{self.figure_options.report_red_chi2_label} = {red_chi: .4g}" else: # Composite curve analysis reporting multiple chi-sq values. # This is usually given by a dict keyed on fit group name. # Add gap between primary-results and reduced-chi squared as # we have multiple values to display. This is easier to read. if len(report) > 0: report += "\n" # Created indented text of reduced-chi squared results. report += f"{self.figure_options.report_red_chi2_label} per fit\n" lines = [] for mod_name, mod_chi in red_chi.items(): lines.append(f" * {mod_name}: {mod_chi: .4g}") report += "\n".join(lines) return report