Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions docs/tutorials/quantum_volume.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,7 @@
"qv_exp.set_experiment_options(trials=60)\n",
"expdata2 = qv_exp.run(backend, analysis=False).block_for_results()\n",
"expdata2.add_data(expdata.data())\n",
"qv_exp.run_analysis(expdata2)\n",
"qv_exp.run_analysis(expdata2).block_for_results()\n",
"\n",
"# View result data\n",
"display(expdata2.figure(0))\n",
Expand Down Expand Up @@ -281,7 +281,7 @@
],
"source": [
"qv_values = [\n",
" batch_expdata.component_experiment_data(i).analysis_results(\"quantum_volume\").value\n",
" batch_expdata.child_data(i).analysis_results(\"quantum_volume\").value\n",
" for i in range(batch_exp.num_experiments)\n",
"]\n",
"\n",
Expand Down Expand Up @@ -399,7 +399,7 @@
"source": [
"for i in range(batch_exp.num_experiments):\n",
" print(f\"\\nComponent experiment {i}\")\n",
" sub_data = batch_expdata.component_experiment_data(i)\n",
" sub_data = batch_expdata.child_data(i)\n",
" display(sub_data.figure(0))\n",
" for result in sub_data.analysis_results():\n",
" print(result)"
Expand Down
11 changes: 4 additions & 7 deletions docs/tutorials/randomized_benchmarking.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -580,15 +580,13 @@
"source": [
"### Viewing sub experiment data\n",
"\n",
"The experiment data returned from a batched experiment also contains individual experiment data for each sub experiment which can be accessed using `component_experiment_data(index)`"
"The experiment data returned from a batched experiment also contains individual experiment data for each sub experiment which can be accessed using `child_data`"
]
},
{
"cell_type": "code",
"execution_count": 14,
"metadata": {
"scrolled": false
},
"metadata": {},
"outputs": [
{
"name": "stdout",
Expand Down Expand Up @@ -761,9 +759,8 @@
],
"source": [
"# Print sub-experiment data\n",
"for i in range(par_exp.num_experiments):\n",
"for i, sub_data in enumerate(par_expdata.child_data):\n",
" print(f\"Component experiment {i}\")\n",
" sub_data = par_expdata.component_experiment_data(i)\n",
" display(sub_data.figure(0))\n",
" for result in sub_data.analysis_results():\n",
" print(result)"
Expand Down Expand Up @@ -832,7 +829,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.11"
"version": "3.7.5"
}
},
"nbformat": 4,
Expand Down
3 changes: 1 addition & 2 deletions docs/tutorials/state_tomography.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,7 @@
}
],
"source": [
"for i in range(parexp.num_experiments):\n",
" expdata = pardata.component_experiment_data(i)\n",
"for i, expdata in enumerate(pardata.child_data()):\n",
" state_result_i = expdata.analysis_results(\"state\")\n",
" fid_result_i = expdata.analysis_results(\"state_fidelity\")\n",
" \n",
Expand Down
5 changes: 2 additions & 3 deletions docs/tutorials/t1.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@
"source": [
"### Viewing sub experiment data\n",
"\n",
"The experiment data returned from a batched experiment also contains individual experiment data for each sub experiment which can be accessed using `component_experiment_data(index)`"
"The experiment data returned from a batched experiment also contains individual experiment data for each sub experiment which can be accessed using `child_data`"
]
},
{
Expand Down Expand Up @@ -260,9 +260,8 @@
],
"source": [
"# Print sub-experiment data\n",
"for i in range(parallel_exp.num_experiments):\n",
"for i, sub_data in enumerate(parallel_data.child_data()):\n",
" print(f\"Component experiment {i}\")\n",
" sub_data = parallel_data.component_experiment_data(i)\n",
" display(sub_data.figure(0))\n",
" for result in sub_data.analysis_results():\n",
" print(result)"
Expand Down
16 changes: 16 additions & 0 deletions qiskit_experiments/database_service/db_analysis_result.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,22 @@ def save(self) -> None:
json_encoder=self._json_encoder,
)

def copy(self) -> "DbAnalysisResultV1":
"""Return a copy of the result with a new result ID"""
return DbAnalysisResultV1(
name=self.name,
value=self.value,
device_components=self.device_components,
experiment_id=self.experiment_id,
chisq=self.chisq,
quality=self.quality,
extra=self.extra,
verified=self.verified,
tags=self.tags,
service=self.service,
source=self._source,
)

@classmethod
def _from_service_data(cls, service_data: Dict) -> "DbAnalysisResultV1":
"""Construct an analysis result from saved database service data.
Expand Down
68 changes: 32 additions & 36 deletions qiskit_experiments/database_service/db_experiment_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ def _clear_results(self):
self._deleted_analysis_results.append(key)
self._analysis_results = ThreadSafeOrderedDict()
# Schedule existing figures for deletion next save call
for key in self._analysis_results.keys():
for key in self._figures.keys():
self._deleted_figures.append(key)
self._figures = ThreadSafeOrderedDict()

Expand Down Expand Up @@ -532,7 +532,7 @@ def add_figures(
)
added_figs.append(fig_name)

return added_figs if len(added_figs) > 1 else added_figs[0]
return added_figs if len(added_figs) != 1 else added_figs[0]

@do_auto_save
def delete_figure(
Expand Down Expand Up @@ -1130,24 +1130,26 @@ def errors(self) -> str:

return "\n".join(errors)

def _copy_metadata(
self, new_instance: Optional["DbExperimentDataV1"] = None
) -> "DbExperimentDataV1":
"""Make a copy of the experiment metadata.
def copy(self, copy_results: bool = True) -> "DbExperimentDataV1":
"""Make a copy of the experiment data with a new experiment ID.

Note:
This method only copies experiment data and metadata, not its
figures nor analysis results. The copy also contains a different
experiment ID.
Args:
copy_results: If True copy the analysis results and figures
into the returned container, along with the
experiment data and metadata. If False only copy
the experiment data and metadata.

Returns:
A copy of the ``DbExperimentDataV1`` object with the same data
and metadata but different ID.
A copy of the experiment data object with the same data
but different IDs.

.. note:
If analysis results and figures are copied they will also have
new result IDs and figure names generated for the copies.
"""
if new_instance is None:
# pylint: disable=no-value-for-parameter
new_instance = self.__class__()
new_instance = self.__class__()

# Copy basic properties and metadata
new_instance._type = self.experiment_type
new_instance._backend = self._backend
new_instance._tags = self._tags
Expand All @@ -1159,6 +1161,7 @@ def _copy_metadata(
new_instance._service = self._service
new_instance._extra_data = self._extra_data

# Copy circuit result data and jobs
with self._data.lock: # Hold the lock so no new data can be added.
new_instance._data = self._data.copy_object()
for orig_kwargs, fut in self._job_futures.copy():
Expand All @@ -1177,6 +1180,20 @@ def _copy_metadata(
**extra_kwargs,
)

# If not copying results return the object
if not copy_results:
return new_instance

# Copy results and figures.
# This requires analysis callbacks to finish
self._wait_for_callbacks()
with self._analysis_results.lock:
new_instance._analysis_results = ThreadSafeOrderedDict()
new_instance.add_analysis_results([result.copy() for result in self.analysis_results()])
with self._figures.lock:
new_instance._figures = ThreadSafeOrderedDict()
new_instance.add_figures(self._figures.values())

return new_instance

@property
Expand Down Expand Up @@ -1403,27 +1420,6 @@ def __repr__(self):
out += ")"
return out

def __str__(self):
Comment thread
yaelbh marked this conversation as resolved.
line = 51 * "-"
n_res = len(self._analysis_results)
status = self.status()
ret = line
ret += f"\nExperiment: {self.experiment_type}"
ret += f"\nExperiment ID: {self.experiment_id}"
ret += f"\nStatus: {status}"
if self.backend:
ret += f"\nBackend: {self.backend}"
if self.tags:
ret += f"\nTags: {self.tags}"
ret += f"\nData: {len(self._data)}"
ret += f"\nAnalysis Results: {n_res}"
ret += f"\nFigures: {len(self._figures)}"
ret += "\n" + line
if n_res:
ret += "\nLast Analysis Result:"
ret += f"\n{str(self._analysis_results.values()[-1])}"
return ret

def __getattr__(self, name: str) -> Any:
try:
return self._extra_data[name]
Expand Down
5 changes: 5 additions & 0 deletions qiskit_experiments/database_service/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,11 @@ def copy_object(self):
obj._container = self.copy()
return obj

def clear(self):
"""Remove all elements from this container."""
with self.lock:
self._container.clear()


class ThreadSafeOrderedDict(ThreadSafeContainer):
"""Thread safe OrderedDict."""
Expand Down
2 changes: 0 additions & 2 deletions qiskit_experiments/framework/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,6 @@
ParallelExperiment
BatchExperiment
CompositeAnalysis
CompositeExperimentData

Base Classes
************
Expand All @@ -244,5 +243,4 @@
ParallelExperiment,
BatchExperiment,
CompositeAnalysis,
CompositeExperimentData,
)
17 changes: 3 additions & 14 deletions qiskit_experiments/framework/base_analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
from abc import ABC, abstractmethod
from typing import List, Tuple

from qiskit.exceptions import QiskitError

from qiskit_experiments.database_service.device_component import Qubit
from qiskit_experiments.framework import Options
from qiskit_experiments.framework.experiment_data import ExperimentData
Expand All @@ -41,9 +39,6 @@ class BaseAnalysis(ABC):
run method and passed to the `_run_analysis` function.
"""

# Expected experiment data container for analysis
__experiment_data__ = ExperimentData

@classmethod
def _default_options(cls) -> Options:
return Options()
Expand Down Expand Up @@ -84,19 +79,14 @@ def run(
will be returned containing only the new analysis results and figures.
This data can then be saved as its own experiment to a database service.
"""
if not isinstance(experiment_data, self.__experiment_data__):
raise QiskitError(
f"Invalid experiment data type, expected {self.__experiment_data__.__name__}"
f" but received {type(experiment_data).__name__}"
)

# Make a new copy of experiment data if not updating results
if not replace_results and (
experiment_data._created_in_db
or experiment_data._analysis_results
or experiment_data._figures
or getattr(experiment_data, "_child_data", None)
):
experiment_data = experiment_data._copy_metadata()
experiment_data = experiment_data.copy()

# Get experiment device components
if "physical_qubits" in experiment_data.metadata:
Expand All @@ -119,8 +109,7 @@ def run_analysis(expdata):
for result in results
]
# Update experiment data with analysis results
if replace_results:
experiment_data._clear_results()
experiment_data._clear_results()
if analysis_results:
expdata.add_analysis_results(analysis_results)
if figures:
Expand Down
7 changes: 1 addition & 6 deletions qiskit_experiments/framework/base_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,16 +95,11 @@ class BaseExperiment(ABC):
__analysis_class__: Optional, the default Analysis class to use for
data analysis. If None no data analysis will be
done on experiment data (Default: None).
__experiment_data__: ExperimentData class that is produced by the
experiment (Default: ExperimentData).
"""

# Analysis class for experiment
__analysis_class__ = None

# ExperimentData class for experiment
__experiment_data__ = ExperimentData

def __init__(
self,
qubits: Sequence[int],
Expand Down Expand Up @@ -325,7 +320,7 @@ def run(

def _initialize_experiment_data(self) -> ExperimentData:
"""Initialize the return data container for the experiment run"""
return self.__experiment_data__(experiment=self)
return ExperimentData(experiment=self)

def run_analysis(
self, experiment_data: ExperimentData, replace_results: bool = False, **options
Expand Down
1 change: 0 additions & 1 deletion qiskit_experiments/framework/composite/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
"""Composite Experiments"""

# Base classes
from .composite_experiment_data import CompositeExperimentData
from .composite_analysis import CompositeAnalysis

# Composite experiment classes
Expand Down
18 changes: 17 additions & 1 deletion qiskit_experiments/framework/composite/batch_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,23 @@

@fix_class_docs
class BatchExperiment(CompositeExperiment):
"""Batch experiment class"""
"""Combine multiple experiments into a batch experiment.

Batch experiments combine individual experiments on any subset of qubits
into a single composite experiment which appends all the circuits from
each component experiment into a single batch of circuits to be executed
as one experiment job.

Analysis of batch experiments is performed using the
:class:`~qiskit_experiments.framework.CompositeAnalysis` class which handles
sorting the composite experiment circuit data into individual child
:class:`ExperimentData` containers for each component experiment which are
then analyzed using the corresponding analysis class for that component
experiment.

See :class:`~qiskit_experiments.framework.CompositeAnalysis`
documentation for additional information.
"""

def __init__(self, experiments: List[BaseExperiment], backend: Optional[Backend] = None):
"""Initialize a batch experiment.
Expand Down
Loading