-
Notifications
You must be signed in to change notification settings - Fork 127
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
### Summary This PR adds the artifacts interface following the design in https://github.com/Qiskit/rfcs/blob/master/0007-experiment-dataframe.md. ### Details and comments - Added the `ArtifactData` dataclass for representing artifacts. - Added `ExperimentData.artifacts()`, `.add_artifacts()`, and `delete_artifact()` for working with artifacts, which is stored in a thread safe list. Currently the `ScatterTable` and `CurveFitResult` objects are stored as artifacts, and experiment serialization data will be added in the future. - Artifacts are grouped by type and stored in a compressed format so that there aren't a huge number of individual files for composite experiments. As such, this PR depends on qiskit-community/qiskit-ibm-experiment#93 to allow `.zip` formats for uploading to the cloud service. Inside each zipped file is a list of JSON artifact files with the filename equal to their unique artifact ID. For composite experiments with `flatten_results=True`, all `ScatterTable` artifacts are stored in `curve_data.zip` in individual jsons and so forth. - Added a how-to for artifacts and updated documentation to demonstrate dataframe objects like AnalysisResults and the ScatterTable (`dataframe.css` is for styling these tables). - Deprecated accessing analysis results via numerical indices to anticipate removing the curve fit result from analysis results altogether in the next release. - Fixed bug where `figure_names` were being duplicated in a copied `ExperimentData` object. Example experiment with artifacts ([link](https://quantum.ibm.com/experiments/eaad518d-232f-4cab-b137-e480ff7f1cbb)): ![image](https://github.com/Qiskit-Extensions/qiskit-experiments/assets/3870315/a2929782-dfef-4535-b246-1167666ebfc9) --------- Co-authored-by: Naoki Kanazawa <[email protected]> Co-authored-by: Will Shanks <[email protected]>
- Loading branch information
1 parent
777e2d5
commit a7d260a
Showing
48 changed files
with
1,406 additions
and
379 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
/* Styling for pandas dataframes in documentation */ | ||
|
||
div.output table { | ||
border: none; | ||
border-collapse: collapse; | ||
border-spacing: 0; | ||
color: black; | ||
font-size: 12px; | ||
table-layout: fixed; | ||
width: 100%; | ||
} | ||
div.output thead { | ||
border-bottom: 1px solid black; | ||
vertical-align: bottom; | ||
} | ||
div.output tr, | ||
div.output th, | ||
div.output td { | ||
text-align: right; | ||
vertical-align: middle; | ||
padding: 0.5em 0.5em; | ||
line-height: normal; | ||
white-space: normal; | ||
max-width: none; | ||
border: none; | ||
} | ||
div.output th { | ||
font-weight: bold; | ||
} | ||
div.output tbody tr:nth-child(odd) { | ||
background: #f5f5f5; | ||
} | ||
div.output tbody tr:hover { | ||
background: rgba(66, 165, 245, 0.2); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
Work with experiment artifacts | ||
============================== | ||
|
||
Problem | ||
------- | ||
|
||
You want to view, add, remove, and save artifacts associated with your :class:`.ExperimentData` instance. | ||
|
||
Solution | ||
-------- | ||
|
||
Artifacts are used to store auxiliary data for an experiment that don't fit neatly in the | ||
:class:`.AnalysisResult` model. Any data that can be serialized, such as fit data, can be added as | ||
:class:`.ArtifactData` artifacts to :class:`.ExperimentData`. | ||
|
||
For example, after an experiment that uses :class:`.CurveAnalysis` is run, its :class:`.ExperimentData` | ||
object is automatically populated with ``fit_summary`` and ``curve_data`` artifacts. The ``fit_summary`` | ||
artifact has one or more :class:`.CurveFitResult` objects that contain parameters from the fit. The | ||
``curve_data`` artifact has a :class:`.ScatterTable` object that contains raw and fitted data in a pandas | ||
:class:`~pandas:pandas.DataFrame`. | ||
|
||
Viewing artifacts | ||
~~~~~~~~~~~~~~~~~ | ||
|
||
Here we run a parallel experiment consisting of two :class:`.T1` experiments in parallel and then view the output | ||
artifacts as a list of :class:`.ArtifactData` objects accessed by :meth:`.ExperimentData.artifacts`: | ||
|
||
.. jupyter-execute:: | ||
|
||
from qiskit_ibm_runtime.fake_provider import FakePerth | ||
from qiskit_aer import AerSimulator | ||
from qiskit_experiments.library import T1 | ||
from qiskit_experiments.framework import ParallelExperiment | ||
import numpy as np | ||
|
||
backend = AerSimulator.from_backend(FakePerth()) | ||
exp1 = T1(physical_qubits=[0], delays=np.arange(1e-6, 6e-4, 5e-5)) | ||
exp2 = T1(physical_qubits=[1], delays=np.arange(1e-6, 6e-4, 5e-5)) | ||
data = ParallelExperiment([exp1, exp2], flatten_results=True).run(backend).block_for_results() | ||
data.artifacts() | ||
|
||
Artifacts can be accessed using either the artifact ID, which has to be unique in each | ||
:class:`.ExperimentData` object, or the artifact name, which does not have to be unique and will return | ||
all artifacts with the same name: | ||
|
||
.. jupyter-execute:: | ||
|
||
print("Number of curve_data artifacts:", len(data.artifacts("curve_data"))) | ||
# retrieve by name and index | ||
curve_data_id = data.artifacts("curve_data")[0].artifact_id | ||
# retrieve by ID | ||
scatter_table = data.artifacts(curve_data_id).data | ||
print("The first curve_data artifact:\n") | ||
scatter_table.dataframe | ||
|
||
In composite experiments, artifacts behave like analysis results and figures in that if | ||
``flatten_results`` isn't ``True``, they are accessible in the :meth:`.artifacts` method of each | ||
:meth:`.child_data`. The artifacts in a large composite experiment with ``flatten_results=True`` can be | ||
distinguished from each other using the :attr:`~.ArtifactData.experiment` and | ||
:attr:`~.ArtifactData.device_components` | ||
attributes. | ||
|
||
One useful pattern is to load raw or fitted data from ``curve_data`` for further data manipulation. You | ||
can work with the dataframe using standard pandas dataframe methods or the built-in | ||
:class:`.ScatterTable` methods: | ||
|
||
.. jupyter-execute:: | ||
|
||
import matplotlib.pyplot as plt | ||
|
||
exp_type = data.artifacts(curve_data_id).experiment | ||
component = data.artifacts(curve_data_id).device_components[0] | ||
|
||
raw_data = scatter_table.filter(category="raw") | ||
fitted_data = scatter_table.filter(category="fitted") | ||
|
||
# visualize the data | ||
plt.figure() | ||
plt.errorbar(raw_data.x, raw_data.y, yerr=raw_data.y_err, capsize=5, label="raw data") | ||
plt.errorbar(fitted_data.x, fitted_data.y, yerr=fitted_data.y_err, capsize=5, label="fitted data") | ||
plt.title(f"{exp_type} experiment on {component}") | ||
plt.xlabel('x') | ||
plt.ylabel('y') | ||
plt.legend() | ||
plt.show() | ||
|
||
Adding artifacts | ||
~~~~~~~~~~~~~~~~ | ||
|
||
You can add arbitrary data as an artifact as long as it's serializable with :class:`.ExperimentEncoder`, | ||
which extends Python's default JSON serialization with support for other data types commonly used with | ||
Qiskit Experiments. | ||
|
||
.. jupyter-execute:: | ||
|
||
from qiskit_experiments.framework import ArtifactData | ||
|
||
new_artifact = ArtifactData(name="experiment_notes", data={"content": "Testing some new ideas."}) | ||
data.add_artifacts(new_artifact) | ||
data.artifacts("experiment_notes") | ||
|
||
.. jupyter-execute:: | ||
|
||
print(data.artifacts("experiment_notes").data) | ||
|
||
Saving and loading artifacts | ||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||
|
||
.. note:: | ||
This feature is only for those who have access to the cloud service. You can | ||
check whether you do by logging into the IBM Quantum interface | ||
and seeing if you can see the `database <https://quantum.ibm.com/experiments>`__. | ||
|
||
Artifacts are saved and loaded to and from the cloud service along with the rest of the | ||
:class:`ExperimentData` object. Artifacts are stored as ``.zip`` files in the cloud service grouped by | ||
the artifact name. For example, the composite experiment above will generate two artifact files, ``fit_summary.zip`` and | ||
``curve_data.zip``. Each of these zipfiles will contain serialized artifact data in JSON format named | ||
by their unique artifact ID: | ||
|
||
.. jupyter-execute:: | ||
:hide-code: | ||
|
||
print("fit_summary.zip") | ||
print(f"|- {data.artifacts('fit_summary')[0].artifact_id}.json") | ||
print(f"|- {data.artifacts('fit_summary')[1].artifact_id}.json") | ||
print("curve_data.zip") | ||
print(f"|- {data.artifacts('curve_data')[0].artifact_id}.json") | ||
print(f"|- {data.artifacts('curve_data')[1].artifact_id}.json") | ||
print("experiment_notes.zip") | ||
print(f"|- {data.artifacts('experiment_notes').artifact_id}.json") | ||
Note that for performance reasons, the auto save feature does not apply to artifacts. You must still | ||
call :meth:`.ExperimentData.save` once the experiment analysis has completed to upload artifacts to the | ||
cloud service. | ||
|
||
Note also though individual artifacts can be deleted, currently artifact files cannot be removed from the | ||
cloud service. Instead, you can delete all artifacts of that name | ||
using :meth:`~.delete_artifact` and then call :meth:`.ExperimentData.save`. | ||
This will save an empty file to the service, and the loaded experiment data will not contain | ||
these artifacts. | ||
|
||
See Also | ||
-------- | ||
|
||
* :ref:`Curve Analysis: Data management with scatter table <data_management_with_scatter_table>` tutorial | ||
* :class:`.ArtifactData` API documentation | ||
* :class:`.ScatterTable` API documentation | ||
* :class:`.CurveFitResult` API documentation |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.