Skip to content
This repository has been archived by the owner on Jul 28, 2023. It is now read-only.

Add paging support for experiments and analysis results #807

Merged
merged 7 commits into from
Nov 12, 2020
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ lint:
pylint -rn qiskit/providers/ibmq test

mypy:
mypy --module qiskit.providers.ibmq --show-error-codes --no-site-packages
mypy --module qiskit.providers.ibmq --show-error-codes --no-site-packages --python-version 3.6

style:
pycodestyle qiskit test
Expand Down
25 changes: 17 additions & 8 deletions qiskit/providers/ibmq/api/clients/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,27 +46,31 @@ def __init__(

def experiments(
self,
limit: Optional[int],
marker: Optional[str],
backend_name: Optional[str],
experiment_type: Optional[str] = None,
start_time: Optional[List] = None,
device_components: Optional[List[str]] = None,
tags: Optional[List[str]] = None
) -> List[Dict]:
) -> Dict:
"""Retrieve experiments, with optional filtering.
Args:
limit: Number of experiments to retrieve.
marker: Marker used to indicate where to start the next query.
backend_name: Name of the backend.
experiment_type: Experiment type.
start_time: A list of timestamps used to filter by experiment start time.
device_components: A list of device components used for filtering.
tags: Tags used for filtering.
Returns:
A list of experiments.
A list of experiments and the marker, if applicable.
"""
resp = self.base_api.experiments(
backend_name, experiment_type, start_time, device_components, tags)
return resp['experiments']
limit, marker, backend_name, experiment_type, start_time, device_components, tags)
return resp

def experiment_get(self, experiment_id: str) -> Dict:
"""Get a specific experiment.
Expand Down Expand Up @@ -180,27 +184,32 @@ def experiment_devices(self) -> List:

def analysis_results(
self,
limit: Optional[int],
marker: Optional[str],
backend_name: Optional[str] = None,
device_components: Optional[List[str]] = None,
experiment_uuid: Optional[str] = None,
result_type: Optional[str] = None,
quality: Optional[List[str]] = None
) -> List[Dict]:
) -> Dict:
"""Return a list of analysis results.
Args:
limit: Number of analysis results to retrieve.
marker: Marker used to indicate where to start the next query.
backend_name: Name of the backend.
device_components: A list of device components used for filtering.
experiment_uuid: Experiment UUID used for filtering.
result_type: Analysis result type used for filtering.
quality: Quality value used for filtering.
Returns:
A list of analysis results.
A list of analysis results and the marker, if applicable.
"""
resp = self.base_api.analysis_results(
backend_name, device_components, experiment_uuid, result_type, quality)
return resp['analysis_results']
limit, marker, backend_name, device_components, experiment_uuid,
result_type, quality)
return resp

def analysis_result_upload(self, result: Dict) -> Dict:
"""Upload an analysis result.
Expand Down
16 changes: 16 additions & 0 deletions qiskit/providers/ibmq/api/rest/root.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,8 @@ def reservations(self) -> List:

def experiments(
self,
limit: Optional[int],
marker: Optional[str],
backend_name: Optional[str] = None,
experiment_type: Optional[str] = None,
start_time: Optional[List] = None,
Expand All @@ -159,6 +161,8 @@ def experiments(
"""Return experiment data.

Args:
limit: Number of experiments to retrieve.
marker: Marker used to indicate where to start the next query.
backend_name: Name of the backend.
experiment_type: Experiment type.
start_time: A list of timestamps used to filter by experiment start time.
Expand All @@ -180,6 +184,10 @@ def experiments(
params['device_components'] = device_components
if tags:
params['tags'] = tags
if limit is not None:
params['limit'] = limit
if marker:
params['marker'] = marker
return self.session.get(url, params=params).json()

def experiment_devices(self) -> Dict:
Expand Down Expand Up @@ -207,6 +215,8 @@ def experiment_upload(self, experiment: Dict) -> Dict:

def analysis_results(
self,
limit: Optional[int],
marker: Optional[str],
backend_name: Optional[str] = None,
device_components: Optional[List[str]] = None,
experiment_uuid: Optional[str] = None,
Expand All @@ -216,6 +226,8 @@ def analysis_results(
"""Return all analysis results.

Args:
limit: Number of analysis results to retrieve.
marker: Marker used to indicate where to start the next query.
backend_name: Name of the backend.
device_components: A list of device components used for filtering.
experiment_uuid: Experiment UUID used for filtering.
Expand All @@ -237,6 +249,10 @@ def analysis_results(
params['quality'] = quality
if result_type:
params['type'] = result_type
if limit:
params['limit'] = limit
if marker:
params['marker'] = marker
return self.session.get(url, params=params).json()

def analysis_result_upload(self, result: Dict) -> Dict:
Expand Down
45 changes: 35 additions & 10 deletions qiskit/providers/ibmq/experiment/experimentservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ def backends(self) -> List[Dict]:

def experiments(
self,
limit: Optional[int] = 10,
backend_name: Optional[str] = None,
type: Optional[str] = None, # pylint: disable=redefined-builtin
start_datetime: Optional[datetime] = None,
Expand All @@ -110,6 +111,7 @@ def experiments(
"""Retrieve all experiments, with optional filtering.

Args:
limit: Number of experiments to retrieve. ``None`` indicates no limit.
jyu00 marked this conversation as resolved.
Show resolved Hide resolved
backend_name: Backend name used for filtering.
type: Experiment type used for filtering.
start_datetime: Filter by the given start timestamp, in local time. This is used to
Expand All @@ -133,8 +135,11 @@ def experiments(
A list of experiments.

Raises:
ValueError: If an invalid `tags_operator` value is specified.
ValueError: If an invalid parameter value is specified.
"""
if limit is not None and (not isinstance(limit, int) or limit <= 0): # type: ignore
raise ValueError(f"{limit} is not a valid `limit`, which has to be a positive integer.")

start_time_filters = []
if start_datetime:
st_filter = 'ge:{}'.format(local_to_utc_str(start_datetime))
Expand All @@ -153,11 +158,17 @@ def experiments(
raise ValueError('{} is not a valid `tags_operator`. Valid values are '
'"AND" and "OR".'.format(tags_operator))

raw_data = self._api_client.experiments(
backend_name, type, start_time_filters, device_components, tags_filter)
experiments = []
for exp in raw_data:
experiments.append(Experiment.from_remote_data(self._provider, exp))
marker = None
while limit and limit > 0:
jyu00 marked this conversation as resolved.
Show resolved Hide resolved
raw_data = self._api_client.experiments(
limit, marker, backend_name, type, start_time_filters,
device_components, tags_filter)
for exp in raw_data['experiments']:
experiments.append(Experiment.from_remote_data(self._provider, exp))
marker = raw_data.get('marker')
if limit:
limit -= len(raw_data['experiments'])
return experiments

def upload_experiment(self, experiment: Experiment) -> None:
Expand Down Expand Up @@ -250,6 +261,7 @@ def delete_experiment(self, experiment: Union[Experiment, str]) -> Optional[Expe

def analysis_results(
self,
limit: Optional[int] = 10,
backend_name: Optional[str] = None,
device_components: Optional[List[str]] = None,
experiment_id: Optional[str] = None,
Expand All @@ -259,6 +271,7 @@ def analysis_results(
"""Retrieve all analysis results, with optional filtering.

Args:
limit: Number of analysis results to retrieve.
backend_name: Backend name used for filtering.
device_components: Filter by device components. An analysis result's
device components must match this list exactly for it to be included.
Expand All @@ -274,20 +287,32 @@ def analysis_results(

Returns:
A list of analysis results.

Raises:
ValueError: If an invalid parameter value is specified.
"""
if limit is not None and (not isinstance(limit, int) or limit <= 0): # type: ignore
raise ValueError(f"{limit} is not a valid `limit`, which has to be a positive integer.")

qualit_list = []
if quality:
for op, qual in quality:
if isinstance(qual, ResultQuality):
qual = qual.value
qual_str = qual if op == 'eq' else "{}:{}".format(op, qual)
qualit_list.append(qual_str)
response = self._api_client.analysis_results(
backend_name=backend_name, device_components=device_components,
experiment_uuid=experiment_id, result_type=result_type, quality=qualit_list)
results = []
for result in response:
results.append(AnalysisResult.from_remote_data(result))
marker = None
while limit and limit > 0:
jyu00 marked this conversation as resolved.
Show resolved Hide resolved
raw_data = self._api_client.analysis_results(
limit=limit, marker=marker,
backend_name=backend_name, device_components=device_components,
experiment_uuid=experiment_id, result_type=result_type, quality=qualit_list)
for result in raw_data['analysis_results']:
results.append(AnalysisResult.from_remote_data(result))
marker = raw_data.get('marker')
if limit:
limit -= len(raw_data['analysis_results'])
return results

def upload_analysis_result(self, result: AnalysisResult) -> None:
Expand Down
15 changes: 15 additions & 0 deletions releasenotes/notes/experiments-limit-c7805ef972b7b4c1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
---
features:
- |
Methods
:meth:`qiskit.providers.ibmq.experiment.ExperimentService.experiments` and
:meth"`qiskit.providers.ibmq.experiment.ExperimentService.analysis_results`
now support a ``limit`` parameter that allows you to limit the number of
experiments and analysis results returned.
upgrade:
- |
A new parameter, ``limit`` is now the first parameter for both
:meth:`qiskit.providers.ibmq.experiment.ExperimentService.experiments` and
:meth"`qiskit.providers.ibmq.experiment.ExperimentService.analysis_results`
methods. This ``limit`` has a default value of 10, meaning by deafult only
10 experiments and analysis results will be returned.
27 changes: 27 additions & 0 deletions test/ibmq/test_experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,33 @@ def test_upload_update_plot_data(self):
rplot = self.provider.experiment.retrieve_plot(new_exp.uuid, plot_name)
self.assertEqual(rplot, friend_bytes, "Retrieved plot not equal updated plot.")

def test_experiments_limit(self):
"""Test getting experiments with a limit."""
limits = [10, 1000, 1001]

for limit in limits:
with self.subTest(limit=limit):
experiments = self.provider.experiment.experiments(limit=limit)
self.assertEqual(len(experiments), limit)
self.assertEqual(len({expr.uuid for expr in experiments}), len(experiments))

with self.assertRaises(ValueError) as context_manager:
self.provider.experiment.experiments(limit=-1)
self.assertIn("limit", str(context_manager.exception))

def test_analysis_results_limit(self):
"""Test getting analysis results with a limit."""
limits = [10, 1000, 1001]
jyu00 marked this conversation as resolved.
Show resolved Hide resolved
for limit in limits:
with self.subTest(limit=limit):
results = self.provider.experiment.analysis_results(limit=limit)
self.assertEqual(len(results), limit)
self.assertEqual(len({res.uuid for res in results}), len(results))

with self.assertRaises(ValueError) as context_manager:
self.provider.experiment.analysis_results(limit=-1)
self.assertIn("limit", str(context_manager.exception))

def _create_experiment(
self,
backend_name: Optional[str] = None,
Expand Down