From 3d043d9119b6b983fc10363204c74b05e44961bf Mon Sep 17 00:00:00 2001 From: Naoki Kanazawa Date: Tue, 26 Apr 2022 03:42:39 +0900 Subject: [PATCH] review comments Co-authored-by: Christopher J. Wood --- docs/tutorials/experiment_cloud_service.rst | 22 ++-- qiskit_experiments/curve_analysis/__init__.py | 6 +- .../curve_analysis/base_curve_analysis.py | 50 ++++---- .../curve_analysis/curve_analysis.py | 7 +- .../curve_analysis/curve_data.py | 108 ++++++++++-------- .../randomized_benchmarking/rb_analysis.py | 8 +- 6 files changed, 106 insertions(+), 95 deletions(-) diff --git a/docs/tutorials/experiment_cloud_service.rst b/docs/tutorials/experiment_cloud_service.rst index aba8b0681b..37282cee4b 100644 --- a/docs/tutorials/experiment_cloud_service.rst +++ b/docs/tutorials/experiment_cloud_service.rst @@ -19,9 +19,9 @@ experiment database. from qiskit_experiments.library.characterization import T1 import numpy as np - + t1_delays = np.arange(1e-6, 600e-6, 50e-6) - + # Create an experiment for qubit 0, # setting the unit to microseconds, # with the specified time intervals @@ -158,7 +158,7 @@ on by default at the experiment service level. .. jupyter-execute:: exp = T1(qubit=0, delays=t1_delays) - + t1_expdata = exp.run(backend=backend, shots=1000) t1_expdata.auto_save = True t1_expdata.block_for_results() @@ -189,7 +189,7 @@ Tags and notes can be added to experiments to help identify specific experiments For example, an experiment can be tagged and made public with the following code. .. jupyter-execute:: - + t1_expdata.tags = ['tag1', 'tag2'] t1_expdata.share_level = "public" t1_expdata.notes = "Example note." @@ -201,7 +201,7 @@ These fields can also be updated in the web interface from the menu on the right .. |web_tags_share.png| image:: ./experiment_cloud_service/web_tags_share.png -For more information about using the cloud database interface, please take a look at its `documentation `__. +For more information about using the cloud database interface, please take a look at its `documentation `__. Randomized Benchmarking experiment ---------------------------------- @@ -211,11 +211,11 @@ Let’s now do a standard RB experiment and save the results to ResultsDB. .. jupyter-execute:: from qiskit_experiments.library import randomized_benchmarking as rb - + lengths = list(range(1, 1000, 200)) num_samples = 10 seed = 1010 - + rb_exp = rb.StandardRB([0], lengths, num_samples=num_samples, seed=seed) rb_expdata = rb_exp.run(backend).block_for_results() rb_expdata.save() @@ -242,15 +242,15 @@ Let’s do state tomography on a Hadamard state. from qiskit_experiments.library import StateTomography import qiskit - + # Construct state by applying H gate qc_h = qiskit.QuantumCircuit(1) qc_h.h(0) - + qstexp = StateTomography(qc_h) qst_expdata = qstexp.run(backend).block_for_results() qst_expdata.save() - + for result in qst_expdata.analysis_results(): print(result) @@ -262,7 +262,7 @@ graphical interface, but the other analysis parameters are: .. image:: ./experiment_cloud_service/tomo_experiment.png | - + .. jupyter-execute:: import qiskit.tools.jupyter diff --git a/qiskit_experiments/curve_analysis/__init__.py b/qiskit_experiments/curve_analysis/__init__.py index 443858123f..f85b6da59d 100644 --- a/qiskit_experiments/curve_analysis/__init__.py +++ b/qiskit_experiments/curve_analysis/__init__.py @@ -272,13 +272,13 @@ class AnalysisB(CurveAnalysis): Typically curve analysis performs fitting as follows. This workflow is defined in the method :meth:`CurveAnalysis._run_analysis`. -1. Preparation +1. Initialization -Curve analysis calls :meth:`_preparation` method where it initializes +Curve analysis calls :meth:`_initialization` method where it initializes some internal states and optionally populate analysis options with the input experiment data. In some case it may train the data processor with fresh outcomes. -A developer can override this method to perform extra preparation. +A developer can override this method to perform initialization of analysis-specific variables. 2. Data processing diff --git a/qiskit_experiments/curve_analysis/base_curve_analysis.py b/qiskit_experiments/curve_analysis/base_curve_analysis.py index db439329f0..970df9e5c8 100644 --- a/qiskit_experiments/curve_analysis/base_curve_analysis.py +++ b/qiskit_experiments/curve_analysis/base_curve_analysis.py @@ -49,52 +49,57 @@ class BaseCurveAnalysis(BaseAnalysis, ABC): .. rubric:: _generate_fit_guesses - An abstract method to create initial guesses for the fit parameters. - This should be implemented by subclass. + This method creates initial guesses for the fit parameters. + This might be overridden by subclass. See :ref:`curve_analysis_init_guess` for details. .. rubric:: _format_data - A method to format curve data. By default, this method takes the average of y values - over the same x values and then sort the entire data by x values. + This method consumes the processed dataset and outputs the formatted dataset. + By default, this method takes the average of y values over + the same x values and then sort the entire data by x values. .. rubric:: _evaluate_quality - A method to evaluate the quality of the fit based on the fit result. + This method evaluates the quality of the fit based on the fit result. This returns "good" when reduced chi-squared is less than 3.0. Usually it returns string "good" or "bad" according to the evaluation. This criterion can be updated by subclass. .. rubric:: _run_data_processing - A method to perform data processing, i.e. create data arrays from - a list of experiment data payload. + This method performs data processing and returns the processed dataset. + By default, it internally calls :class:`DataProcessor` instance from the analysis options + and processes experiment data payload to create Y data with uncertainty. + X data and other metadata are generated within this method by inspecting the + circuit metadata. The series classification is also performed by based upon the + matching of circuit metadata and :attr:`SeriesDef.filter_kwargs`. .. rubric:: _run_curve_fit - A method to perform fitting with predefined fit models and formatted data. - This method internally calls :meth:`_generate_fit_guesses`. + This method performs the fitting with predefined fit models and the formatted dataset. + This method internally calls :meth:`_generate_fit_guesses` method. Note that this is a core functionality of the :meth:`_run_analysis` method, - that creates fit result object from the processed curve data. + that creates fit result object from the formatted dataset. .. rubric:: _create_analysis_results - A method to create analysis results for important fit parameters + This method to creates analysis results for important fit parameters that might be defined by analysis options ``result_parameters``. In addition, another entry for all fit parameters is created when the analysis option ``return_fit_parameters`` is ``True``. .. rubric:: _create_curve_data - A method to create analysis results for data points used for the fitting. + This method to creates analysis results for the formatted dataset, i.e. data used for the fitting. Entries are created when the analysis option ``return_data_points`` is ``True``. If analysis consists of multiple series, analysis result is created for - each curve data in the series. + each curve data in the series definitions. - .. rubric:: _preparation + .. rubric:: _initialize - A method that should be called before other methods are called. This method initializes analysis options against input experiment data. + Usually this method is called before other methods are called. """ @@ -102,9 +107,6 @@ def __init__(self): """Initialize data fields that are privately accessed by methods.""" super().__init__() - #: List[int]: Index of physical qubits - self._physical_qubits = None - @property @abstractmethod def parameters(self) -> List[str]: @@ -522,11 +524,13 @@ def _create_curve_data( return samples - def _preparation( + def _initialize( self, experiment_data: ExperimentData, ): - """Prepare for curve analysis. This method is called ahead of other processing. + """Initialize curve analysis with experiment data. + + This method is called ahead of other processing. Args: experiment_data: Experiment data to analyze. @@ -542,9 +546,3 @@ def _preparation( if not data_processor.is_trained: data_processor.train(data=experiment_data.data()) self.set_options(data_processor=data_processor) - - # get experiment metadata - try: - self._physical_qubits = experiment_data.metadata["physical_qubits"] - except KeyError: - pass diff --git a/qiskit_experiments/curve_analysis/curve_analysis.py b/qiskit_experiments/curve_analysis/curve_analysis.py index 3c7dc245d2..3141c01c92 100644 --- a/qiskit_experiments/curve_analysis/curve_analysis.py +++ b/qiskit_experiments/curve_analysis/curve_analysis.py @@ -90,11 +90,6 @@ def parameters(self) -> List[str]: """Return parameters of this curve analysis.""" return [s for s in self._fit_params() if s not in self.options.fixed_parameters] - @property - def _num_qubits(self) -> int: - """Getter for qubit number.""" - return len(self._physical_qubits) - # pylint: disable=bad-docstring-quotes @deprecated_function( last_version="0.4", @@ -156,7 +151,7 @@ def _run_analysis( self.__series__ = assigned_series # Prepare for fitting - self._preparation(experiment_data) + self._initialize(experiment_data) analysis_results = [] # Run data processing diff --git a/qiskit_experiments/curve_analysis/curve_data.py b/qiskit_experiments/curve_analysis/curve_data.py index 5c59a59504..d568c22cce 100644 --- a/qiskit_experiments/curve_analysis/curve_data.py +++ b/qiskit_experiments/curve_analysis/curve_data.py @@ -25,31 +25,39 @@ @dataclasses.dataclass(frozen=True) class SeriesDef: - """Description of curve.""" + """A dataclass to describe the definition of the curve. + + Args: + fit_func: A callable that defines the fit model of this curve. The argument names + in the callable are parsed to create the fit parameter list, which will appear + in the analysis results. The first argument should be ``x`` that represents + X-values that the experiment sweeps. + filter_kwargs: Optional. Dictionary of properties that uniquely identifies this series. + This dictionary is used for data processing. + This must be provided when the curve analysis consists of multiple series. + name: Optional. Name of this series. + plot_color: Optional. String representation of the color that is used to draw fit data + and data points in the output figure. This depends on the drawer class + being set to the curve analysis options. Usually this conforms to the + Matplotlib color names. + plot_symbol: Optional. String representation of the marker shape that is used to draw + data points in the output figure. This depends on the drawer class + being set to the curve analysis options. Usually this conforms to the + Matplotlib symbol names. + canvas: Optional. Index of sub-axis in the output figure that draws this curve. + This option is valid only when the drawer instance provides multi-axis drawing. + model_description: Optional. Arbitrary string representation of this fit model. + This string will appear in the analysis results as a part of metadata. + """ - # Arbitrary callback to define the fit function. First argument should be x. fit_func: Callable - - # Keyword dictionary to define the series with circuit metadata filter_kwargs: Dict[str, Any] = dataclasses.field(default_factory=dict) - - # Name of this series. This name will appear in the figure and raw x-y value report. name: str = "Series-0" - - # Color of this line. plot_color: str = "black" - - # Symbol to represent data points of this line. plot_symbol: str = "o" - - # Latex description of this fit model - model_description: Optional[str] = None - - # Index of canvas if the result figure is multi-panel canvas: Optional[int] = None - - # Automatically extracted signature of the fit function - signature: Tuple[str] = dataclasses.field(init=False) + model_description: Optional[str] = None + signature: Tuple[str, ...] = dataclasses.field(init=False) def __post_init__(self): """Parse the fit function signature to extract the names of the variables. @@ -65,24 +73,27 @@ def __post_init__(self): @dataclasses.dataclass(frozen=True) class CurveData: - """Set of extracted experiment data.""" + """A dataclass that manages the multiple arrays comprising the dataset for fitting. + + Args: + x: X-values that experiment sweeps. + y: Y-values that observed and processed by the data processor. + y_err: Uncertainty of the Y-values which is created by the data processor. + Usually this assumes standard error. + shots: Number of shots used in the experiment to obtain the Y-values. + data_allocation: List with identical size with other arrays. + The value indicates the series index of the corresponding element. + This is classified based upon the matching of :attr:`SeriesDef.filter_kwargs` + with the circuit metadata of the corresponding data index. + If metadata doesn't match with any series definition, element is filled with ``-1``. + labels: List of curve labels. The list index corresponds to the series index. + """ - # X data x: np.ndarray - - # Y data y: np.ndarray - - # Error bar y_err: np.ndarray - - # Shots number shots: np.ndarray - - # Maping of data index to series index data_allocation: np.ndarray - - # List of curve names labels: List[str] def get_subset_of(self, index: Union[str, int]) -> "CurveData": @@ -114,37 +125,34 @@ def get_subset_of(self, index: Union[str, int]) -> "CurveData": @dataclasses.dataclass(frozen=True) class FitData: - """Set of data generated by the fit function.""" + """A dataclass to store the outcome of the fitting. + + Args: + popt: List of optimal parameter values with uncertainties if available. + popt_keys: List of parameter names being fit. + pcov: Covariance matrix from the least square fitting. + reduced_chisq: Reduced Chi-squared value for the fit curve. + dof: Degree of freedom in this fit model. + x_data: X-values provided to the fitter. + y_data: Y-values provided to the fitter. + """ - # Order sensitive fit parameter values popt: List[uncertainties.UFloat] - - # Order sensitive parameter name list popt_keys: List[str] - - # Covariance matrix pcov: np.ndarray - - # Reduced Chi-squared value of fit curve reduced_chisq: float - - # Degree of freedom dof: int - - # X data x_data: np.ndarray - - # Y data y_data: np.ndarray @property def x_range(self) -> Tuple[float, float]: - """Return range of x values.""" + """Range of x values.""" return np.min(self.x_data), np.max(self.x_data) @property def y_range(self) -> Tuple[float, float]: - """Return range of y values.""" + """Range of y values.""" return np.min(self.y_data), np.max(self.y_data) def fitval(self, key: str) -> uncertainties.UFloat: @@ -169,7 +177,13 @@ def fitval(self, key: str) -> uncertainties.UFloat: @dataclasses.dataclass class ParameterRepr: - """Detailed description of fitting parameter.""" + """Detailed description of fitting parameter. + + Args: + name: Original name of the fit parameter being defined in the fit model. + repr: Optional. Human-readable parameter name shown in the analysis result and in the figure. + unit: Optional. Physical unit of this parameter if applicable. + """ # Fitter argument name name: str diff --git a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py index b11a254547..47bd33d5cf 100644 --- a/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py +++ b/qiskit_experiments/library/randomized_benchmarking/rb_analysis.py @@ -81,6 +81,7 @@ class RBAnalysis(curve.CurveAnalysis): def __init__(self): super().__init__() self._gate_counts_per_clifford = None + self._num_qubits = None @classmethod def _default_options(cls): @@ -238,11 +239,11 @@ def _create_analysis_results( return outcomes - def _preparation( + def _initialize( self, experiment_data: ExperimentData, ): - super()._preparation(experiment_data) + super()._initialize(experiment_data) if self.options.gate_error_ratio is not None: # If gate error ratio is not False, EPG analysis is enabled. @@ -277,6 +278,9 @@ def _preparation( gate_error_ratio[gate] = _lookup_epg_ratio(gate, len(qinds)) self.set_options(gate_error_ratio=gate_error_ratio) + # Get qubit number + self._num_qubits = len(experiment_data.metadata["physical_qubits"]) + def _lookup_epg_ratio(gate: str, n_qubits: int) -> Union[None, int]: """A helper method to look-up preset gate error ratio for given basis gate name.