예제 #1
0
    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, []
예제 #2
0
    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)
예제 #3
0
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, {}