Skip to content

Commit

Permalink
Add AnalysisResult class
Browse files Browse the repository at this point in the history
- AnalysisResultData class has been deprecated
- ExperimentData.load is updated to directly instantiate own class
- DbExperimentData._analysis_res_cls attribute is added to instantiate proper analysis container

AnalysisResult class implements _from_service_data method that implements
formatter for FitVal. UFloat value is converted into FitVal just before data saving
and converted back into UFloat in the loader. Covariance info will be lost in round trip.
FitVal is still supported as value container for UFloat in database service.
FakeService unittest class is also udpated to support storing the analysis results
and several unittests for AnalysisResult class are also added.
  • Loading branch information
nkanazawa1989 committed Jan 29, 2022
1 parent 266ef45 commit 0bf1507
Show file tree
Hide file tree
Showing 22 changed files with 600 additions and 121 deletions.
14 changes: 7 additions & 7 deletions qiskit_experiments/curve_analysis/curve_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@
from qiskit_experiments.framework import (
BaseAnalysis,
ExperimentData,
AnalysisResultData,
AnalysisResult,
Options,
)

Expand Down Expand Up @@ -203,7 +203,7 @@ class AnalysisExample(CurveAnalysis):
- Create extra data from fit result:
Override :meth:`~self._extra_database_entry`. You need to return a list of
:class:`~qiskit_experiments.framework.analysis_result_data.AnalysisResultData`
:class:`~qiskit_experiments.framework.analysis_result_data.AnalysisResult`
object. This returns an empty list by default.
- Customize fit quality evaluation:
Expand Down Expand Up @@ -496,7 +496,7 @@ def _format_data(self, data: CurveData) -> CurveData:
)

# pylint: disable=unused-argument
def _extra_database_entry(self, fit_data: FitData) -> List[AnalysisResultData]:
def _extra_database_entry(self, fit_data: FitData) -> List[AnalysisResult]:
"""Calculate new quantity from the fit result.
Subclasses can override this method to do post analysis.
Expand Down Expand Up @@ -746,7 +746,7 @@ def _data(

def _run_analysis(
self, experiment_data: ExperimentData
) -> Tuple[List[AnalysisResultData], List["pyplot.Figure"]]:
) -> Tuple[List[AnalysisResult], List["pyplot.Figure"]]:
#
# 1. Parse arguments
#
Expand Down Expand Up @@ -868,7 +868,7 @@ def _run_analysis(

# overview entry
analysis_results.append(
AnalysisResultData(
AnalysisResult(
name=PARAMS_ENTRY_PREFIX + self.__class__.__name__,
value=[p.nominal_value for p in fit_result.popt],
chisq=fit_result.reduced_chisq,
Expand All @@ -895,7 +895,7 @@ def _run_analysis(
p_name = param_repr
p_repr = param_repr
unit = None
result_entry = AnalysisResultData(
result_entry = AnalysisResult(
name=p_repr,
value=fit_result.fitval(p_name),
unit=unit,
Expand All @@ -918,7 +918,7 @@ def _run_analysis(
"ydata": series_data.y,
"sigma": series_data.y_err,
}
raw_data_entry = AnalysisResultData(
raw_data_entry = AnalysisResult(
name=DATA_ENTRY_PREFIX + self.__class__.__name__,
value=raw_data_dict,
extra={
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
from qiskit.utils import detach_prefix

from qiskit_experiments.curve_analysis.curve_data import SeriesDef, FitData, CurveData
from qiskit_experiments.framework import AnalysisResultData
from qiskit_experiments.framework import AnalysisResult
from qiskit_experiments.framework.matplotlib import get_non_gui_ax
from .curves import plot_scatter, plot_errorbar, plot_curve_fit
from .style import PlotterStyle
Expand All @@ -47,7 +47,7 @@ def draw(
fit_samples: List[CurveData],
tick_labels: Dict[str, str],
fit_data: FitData,
result_entries: List[AnalysisResultData],
result_entries: List[AnalysisResult],
style: Optional[PlotterStyle] = None,
axis: Optional["matplotlib.axes.Axes"] = None,
) -> "pyplot.Figure":
Expand Down Expand Up @@ -146,7 +146,7 @@ def draw(
fit_samples: List[CurveData],
tick_labels: Dict[str, str],
fit_data: FitData,
result_entries: List[AnalysisResultData],
result_entries: List[AnalysisResult],
style: Optional[PlotterStyle] = None,
axis: Optional["matplotlib.axes.Axes"] = None,
) -> "pyplot.Figure":
Expand Down Expand Up @@ -336,7 +336,7 @@ def draw_single_curve_mpl(
)


def write_fit_report(result_entries: List[AnalysisResultData]) -> str:
def write_fit_report(result_entries: List[AnalysisResult]) -> str:
"""A function that generates fit reports documentation from list of data.
Args:
Expand Down
2 changes: 1 addition & 1 deletion qiskit_experiments/database_service/db_analysis_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ def save(self) -> None:

def copy(self) -> "DbAnalysisResultV1":
"""Return a copy of the result with a new result ID"""
return DbAnalysisResultV1(
return self.__class__(
name=self.name,
value=self.value,
device_components=self.device_components,
Expand Down
10 changes: 4 additions & 6 deletions qiskit_experiments/database_service/db_experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ class DbExperimentDataV1(DbExperimentData):
verbose = True # Whether to print messages to the standard output.
_metadata_version = 1
_job_executor = futures.ThreadPoolExecutor()
_analysis_res_cls = DbAnalysisResult

_json_encoder = ExperimentEncoder
_json_decoder = ExperimentDecoder
Expand Down Expand Up @@ -812,7 +813,7 @@ def _retrieve_analysis_results(self, refresh: bool = False):
)
for result in retrieved_results:
result_id = result["result_id"]
self._analysis_results[result_id] = DbAnalysisResult._from_service_data(result)
self._analysis_results[result_id] = self._analysis_res_cls._from_service_data(result)

def analysis_results(
self,
Expand Down Expand Up @@ -1008,18 +1009,15 @@ def load(cls, experiment_id: str, service: DatabaseServiceV1) -> "DbExperimentDa
service_data = service.experiment(experiment_id, json_decoder=cls._json_decoder)

# Parse serialized metadata
metadata = service_data.pop("metadata")

# Initialize container
expdata = DbExperimentDataV1(
expdata = cls(
experiment_type=service_data.pop("experiment_type"),
backend=service_data.pop("backend"),
experiment_id=service_data.pop("experiment_id"),
parent_id=service_data.pop("parent_id", None),
tags=service_data.pop("tags"),
job_ids=service_data.pop("job_ids"),
share_level=service_data.pop("share_level"),
metadata=metadata,
metadata=service_data.pop("metadata"),
figure_names=service_data.pop("figure_names"),
notes=service_data.pop("notes"),
**service_data,
Expand Down
9 changes: 0 additions & 9 deletions qiskit_experiments/database_service/db_fitval.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"""DB class for fit value with std error and unit."""

import dataclasses
import warnings
from typing import Optional


Expand All @@ -34,11 +33,3 @@ def __str__(self):
if self.unit is not None:
out += f" {str(self.unit)}"
return out


def __new__(cls, *args, **kwargs):
warnings.warn(
"FitVal object has been deprecated in Qiskit Experiments version 0.3 and "
"will be removed in version 0.5. Use version <= 0.3 to load this object.",
DeprecationWarning,
)
28 changes: 28 additions & 0 deletions qiskit_experiments/database_service/device_component.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

"""Device component classes."""

from typing import Dict, Any
from abc import ABC, abstractmethod


Expand All @@ -35,6 +36,15 @@ def __init__(self, index: int):
def __str__(self):
return f"Q{self._index}"

def __json_encode__(self):
"""Convert to format that can be JSON serialized."""
return {"index": self._index}

@classmethod
def __json_decode__(cls, value: Dict[str, Any]) -> "Qubit":
"""Load from JSON compatible format."""
return cls(**value)


class Resonator(DeviceComponent):
"""Class representing a resonator device component."""
Expand All @@ -45,6 +55,15 @@ def __init__(self, index: int):
def __str__(self):
return f"R{self._index}"

def __json_encode__(self):
"""Convert to format that can be JSON serialized."""
return {"index": self._index}

@classmethod
def __json_decode__(cls, value: Dict[str, Any]) -> "Resonator":
"""Load from JSON compatible format."""
return cls(**value)


class UnknownComponent(DeviceComponent):
"""Class representing unknown device component."""
Expand All @@ -55,6 +74,15 @@ def __init__(self, component: str):
def __str__(self):
return self._component

def __json_encode__(self):
"""Convert to format that can be JSON serialized."""
return {"component": self._component}

@classmethod
def __json_decode__(cls, value: Dict[str, Any]) -> "UnknownComponent":
"""Load from JSON compatible format."""
return cls(**value)


def to_component(string: str) -> DeviceComponent:
"""Convert the input string to a ``DeviceComponent`` instance.
Expand Down
5 changes: 3 additions & 2 deletions qiskit_experiments/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@
The :meth:`BaseAnalysis._run_analysis` method should return a pair
``(results, figures)`` where ``results`` is a list of
:class:`AnalysisResultData` and ``figures`` is a list of
:class:`AnalysisResult` and ``figures`` is a list of
:class:`matplotlib.figure.Figure`.
The :mod:`qiskit_experiments.data_processing` module contains classes for
Expand All @@ -207,6 +207,7 @@
JobStatus
AnalysisStatus
FitVal
AnalysisResult
AnalysisResultData
ExperimentConfig
AnalysisConfig
Expand Down Expand Up @@ -246,7 +247,7 @@
from .base_analysis import BaseAnalysis
from .base_experiment import BaseExperiment
from .configs import ExperimentConfig, AnalysisConfig
from .analysis_result_data import AnalysisResultData
from .analysis_result_data import AnalysisResultData, AnalysisResult
from .experiment_data import ExperimentData
from .composite import (
ParallelExperiment,
Expand Down
Loading

0 comments on commit 0bf1507

Please sign in to comment.