def _run_analysis( self, experiment_data: ExperimentData ) -> Tuple[List[AnalysisResultData], List["pyplot.Figure"]]: # Update all fit functions in the series definitions if fixed parameter is defined. # These lines will be removed once proper fit model class is implemented. assigned_params = self.options.fixed_parameters if assigned_params: # Check if all parameters are assigned. if any(v is None for v in assigned_params.values()): raise AnalysisError( f"Unassigned fixed-value parameters for the fit " f"function {self.__class__.__name__}." f"All values of fixed-parameters, i.e. {assigned_params}, " "must be provided by the analysis options to run this analysis." ) # Override series definition with assigned fit functions. assigned_series = [] for series_def in self.__series__: dict_def = dataclasses.asdict(series_def) dict_def["fit_func"] = functools.partial( series_def.fit_func, **assigned_params) del dict_def["signature"] assigned_series.append(SeriesDef(**dict_def)) self.__series__ = assigned_series # Prepare for fitting self._initialize(experiment_data) analysis_results = [] # Run data processing processed_data = self._run_data_processing(experiment_data.data(), self.__series__) if self.options.plot and self.options.plot_raw_data: for s in self.__series__: sub_data = processed_data.get_subset_of(s.name) self.drawer.draw_raw_data( x_data=sub_data.x, y_data=sub_data.y, ax_index=s.canvas, ) # for backward compatibility, will be removed in 0.4. self.__processed_data_set["raw_data"] = processed_data # Format data formatted_data = self._format_data(processed_data) if self.options.plot: for s in self.__series__: sub_data = formatted_data.get_subset_of(s.name) self.drawer.draw_formatted_data( x_data=sub_data.x, y_data=sub_data.y, y_err_data=sub_data.y_err, name=s.name, ax_index=s.canvas, color=s.plot_color, marker=s.plot_symbol, ) # for backward compatibility, will be removed in 0.4. self.__processed_data_set["fit_ready"] = formatted_data # Run fitting fit_data = self._run_curve_fit(formatted_data, self.__series__) # Create figure and result data if fit_data: metadata = self.options.extra.copy() metadata["fit_models"] = { s.name: s.model_description or "no description" for s in self.__series__ } quality = self._evaluate_quality(fit_data) # Create analysis results analysis_results.extend( self._create_analysis_results(fit_data, quality, **metadata)) # calling old extra entry method for backward compatibility if hasattr(self, "_extra_database_entry"): warnings.warn( "Method '_extra_database_entry' has been deprecated and will be " "removed after 0.4. Please override new method " "'_create_analysis_results' with updated method signature.", DeprecationWarning, ) deprecated_method = getattr(self, "_extra_database_entry") analysis_results.extend(deprecated_method(fit_data)) # Draw fit curves and report if self.options.plot: for s in self.__series__: interp_x = np.linspace(*fit_data.x_range, 100) params = {} for fitpar in s.signature: if fitpar in self.options.fixed_parameters: params[fitpar] = self.options.fixed_parameters[ fitpar] else: params[fitpar] = fit_data.fitval(fitpar) y_data_with_uncertainty = s.fit_func(interp_x, **params) y_mean = unp.nominal_values(y_data_with_uncertainty) y_std = unp.std_devs(y_data_with_uncertainty) # Draw fit line self.drawer.draw_fit_line( x_data=interp_x, y_data=y_mean, ax_index=s.canvas, color=s.plot_color, ) # Draw confidence intervals with different n_sigma sigmas = unp.std_devs(y_data_with_uncertainty) if np.isfinite(sigmas).all(): for n_sigma, alpha in self.drawer.options.plot_sigma: self.drawer.draw_confidence_interval( x_data=interp_x, y_ub=y_mean + n_sigma * y_std, y_lb=y_mean - n_sigma * y_std, ax_index=s.canvas, alpha=alpha, color=s.plot_color, ) # Write fitting report report_description = "" for res in analysis_results: if isinstance(res.value, (float, UFloat)): report_description += f"{analysis_result_to_repr(res)}\n" report_description += r"Fit $\chi^2$ = " + f"{fit_data.reduced_chisq: .4g}" self.drawer.draw_fit_report(description=report_description) # Add raw data points analysis_results.extend( self._create_curve_data(formatted_data, self.__series__)) # Finalize plot if self.options.plot: self.drawer.format_canvas() return analysis_results, [self.drawer.figure] return analysis_results, []
def _extract_curves(self, experiment_data: ExperimentData, data_processor: Union[Callable, DataProcessor]): """Extract curve data from experiment data. This method internally populates two types of curve data. - raw_data: This is the data directly obtained from the experiment data. You can access this data with ``self._data(label="raw_data")``. - fit_ready: This is the formatted data created by pre-processing defined by `self._format_data()` method. This method is implemented by subclasses. You can access to this data with ``self._data(label="fit_ready")``. If multiple series exist, you can optionally specify ``series_name`` in ``self._data`` method to filter data in the target series. .. notes:: The target metadata properties to define each curve entry is described by the class attribute __series__ (see `filter_kwargs`). Args: experiment_data: ExperimentData object to fit parameters. data_processor: A callable or DataProcessor instance to format data into numpy array. This should take a list of dictionaries and return two tuple of float values, that represent a y value and an error of it. Raises: DataProcessorError: When `x_key` specified in the analysis option is not defined in the circuit metadata. AnalysisError: When formatted data has label other than fit_ready. """ self.__processed_data_set = list() def _is_target_series(datum, **filters): try: return all(datum["metadata"][key] == val for key, val in filters.items()) except KeyError: return False # Extract X, Y, Y_sigma data data = experiment_data.data() x_key = self.options.x_key try: x_values = np.asarray([datum["metadata"][x_key] for datum in data], dtype=float) except KeyError as ex: raise DataProcessorError( f"X value key {x_key} is not defined in circuit metadata." ) from ex if isinstance(data_processor, DataProcessor): y_data = data_processor(data) y_nominals = unp.nominal_values(y_data) y_stderrs = unp.std_devs(y_data) else: y_nominals, y_stderrs = zip(*map(data_processor, data)) y_nominals = np.asarray(y_nominals, dtype=float) y_stderrs = np.asarray(y_stderrs, dtype=float) # Store metadata metadata = np.asarray([datum["metadata"] for datum in data], dtype=object) # Store shots shots = np.asarray([datum.get("shots", np.nan) for datum in data]) # Find series (invalid data is labeled as -1) data_index = np.full(x_values.size, -1, dtype=int) for idx, series_def in enumerate(self.__series__): data_matched = np.asarray([ _is_target_series(datum, **series_def.filter_kwargs) for datum in data ], dtype=bool) data_index[data_matched] = idx # Store raw data raw_data = CurveData( label="raw_data", x=x_values, y=y_nominals, y_err=y_stderrs, shots=shots, data_index=data_index, metadata=metadata, ) self.__processed_data_set.append(raw_data) # Format raw data formatted_data = self._format_data(raw_data) if formatted_data.label != "fit_ready": raise AnalysisError( f"Not expected data label {formatted_data.label} != fit_ready." ) self.__processed_data_set.append(formatted_data)
def scipy_linear_lstsq( outcome_data: List[np.ndarray], shot_data: np.ndarray, measurement_data: np.ndarray, preparation_data: np.ndarray, measurement_basis: BaseFitterMeasurementBasis, preparation_basis: Optional[BaseFitterPreparationBasis] = None, weights: Optional[np.ndarray] = None, **kwargs, ) -> Tuple[np.ndarray, Dict]: r"""Weighted linear least-squares tomography fitter. Overview This fitter reconstructs the maximum-likelihood estimate by using :func:`scipy.linalg.lstsq` to minimize the least-squares negative log likelihood function .. math:: \hat{\rho} &= -\mbox{argmin }\log\mathcal{L}{\rho} \\ &= \mbox{argmin }\sum_i w_i^2(\mbox{Tr}[E_j\rho] - \hat{p}_i)^2 \\ &= \mbox{argmin }\|W(Ax - y) \|_2^2 where - :math:`A = \sum_j |j \rangle\!\langle\!\langle E_j|` is the matrix of measured basis elements. - :math:`W = \sum_j w_j|j\rangle\!\langle j|` is an optional diagonal weights matrix if an optional weights vector is supplied. - :math:`y = \sum_j \hat{p}_j |j\langle` is the vector of estimated measurement outcome probabilites for each basis element. - :math:`x = |\rho\rangle\!\rangle` is the vectorized density matrix. .. note:: Linear least-squares constructs the full basis matrix :math:`A` as a dense numpy array so should not be used for than 5 or 6 qubits. For larger number of qubits try the :func:`~qiskit_experiments.library.tomography.fitters.linear_inversion` fitter function. Args: outcome_data: list of outcome frequency data. shot_data: basis measurement total shot data. measurement_data: measurement basis indice data. preparation_data: preparation basis indice data. measurement_basis: measurement matrix basis. preparation_basis: Optional, preparation matrix basis. weights: Optional array of weights for least squares objective. kwargs: additional kwargs for :func:`scipy.linalg.lstsq`. Raises: AnalysisError: If the fitted vector is not a square matrix Returns: The fitted matrix rho that maximizes the least-squares likelihood function. """ basis_matrix, probability_data = fitter_utils.lstsq_data( outcome_data, shot_data, measurement_data, preparation_data, measurement_basis, preparation_basis=preparation_basis, ) if weights is not None: basis_matrix = weights[:, None] * basis_matrix probability_data = weights * probability_data # Perform least squares fit using Scipy.linalg lstsq function lstsq_options = {"check_finite": False, "lapack_driver": "gelsy"} for key, val in kwargs.items(): lstsq_options[key] = val sol, _, _, _ = la.lstsq(basis_matrix, probability_data, **lstsq_options) # Reshape fit to a density matrix size = len(sol) dim = int(np.sqrt(size)) if dim * dim != size: raise AnalysisError("Least-squares fitter: invalid result shape.") rho_fit = np.reshape(sol, (dim, dim), order="F") return rho_fit, {}