diff --git a/qiskit/providers/ibmq/api/clients/experiment.py b/qiskit/providers/ibmq/api/clients/experiment.py index b0019c98e..45746aaf7 100644 --- a/qiskit/providers/ibmq/api/clients/experiment.py +++ b/qiskit/providers/ibmq/api/clients/experiment.py @@ -57,7 +57,7 @@ def experiments( exclude_mine: Optional[bool] = False, mine_only: Optional[bool] = False, sort_by: Optional[str] = None - ) -> Dict: + ) -> str: """Retrieve experiments, with optional filtering. Args: @@ -94,7 +94,7 @@ def experiments( sort_by=sort_by) return resp - def experiment_get(self, experiment_id: str) -> Dict: + def experiment_get(self, experiment_id: str) -> str: """Get a specific experiment. Args: @@ -228,7 +228,7 @@ def analysis_results( verified: Optional[bool] = None, tags: Optional[List[str]] = None, sort_by: Optional[str] = None - ) -> Dict: + ) -> str: """Return a list of analysis results. Args: @@ -294,7 +294,7 @@ def analysis_result_delete(self, result_id: str) -> Dict: """ return self.base_api.analysis_result(result_id).delete() - def analysis_result_get(self, result_id: str) -> Dict: + def analysis_result_get(self, result_id: str) -> str: """Retrieve an analysis result. Args: diff --git a/qiskit/providers/ibmq/api/rest/analysis_result.py b/qiskit/providers/ibmq/api/rest/analysis_result.py index dbac59cb3..b64ab9412 100644 --- a/qiskit/providers/ibmq/api/rest/analysis_result.py +++ b/qiskit/providers/ibmq/api/rest/analysis_result.py @@ -60,11 +60,11 @@ def delete(self) -> Dict: url = self.get_url('self') return self.session.delete(url).json() - def get(self) -> Dict: + def get(self) -> str: """Retrieve the analysis result. Returns: - JSON response. + Server response. """ url = self.get_url('self') - return self.session.get(url).json() + return self.session.get(url).text diff --git a/qiskit/providers/ibmq/api/rest/experiment.py b/qiskit/providers/ibmq/api/rest/experiment.py index d26f60a39..33d38d19e 100644 --- a/qiskit/providers/ibmq/api/rest/experiment.py +++ b/qiskit/providers/ibmq/api/rest/experiment.py @@ -39,14 +39,14 @@ def __init__(self, session: RetrySession, experiment_uuid: str, url_prefix: str """ super().__init__(session, '{}/experiments/{}'.format(url_prefix, experiment_uuid)) - def retrieve(self) -> Dict: + def retrieve(self) -> str: """Retrieve the specific experiment. Returns: - JSON response. + Experiment data. """ url = self.get_url('self') - return self.session.get(url).json() + return self.session.get(url).text def update(self, experiment: Dict) -> Dict: """Update the experiment. diff --git a/qiskit/providers/ibmq/api/rest/root.py b/qiskit/providers/ibmq/api/rest/root.py index 8e1acba4d..5ca444806 100644 --- a/qiskit/providers/ibmq/api/rest/root.py +++ b/qiskit/providers/ibmq/api/rest/root.py @@ -163,7 +163,7 @@ def experiments( exclude_mine: Optional[bool] = False, mine_only: Optional[bool] = False, sort_by: Optional[str] = None - ) -> Dict: + ) -> str: """Return experiment data. Args: @@ -184,7 +184,7 @@ def experiments( sort_by: Sorting order. Returns: - JSON response. + Response text. """ url = self.get_url('experiments') params = {} # type: Dict[str, Any] @@ -218,8 +218,8 @@ def experiments( params['owner'] = 'me' if sort_by: params['sort'] = sort_by - params['include_plot_names'] = "true" - return self.session.get(url, params=params).json() + + return self.session.get(url, params=params).text def experiment_devices(self) -> Dict: """Return experiment devices. @@ -256,7 +256,7 @@ def analysis_results( verified: Optional[bool] = None, tags: Optional[List[str]] = None, sort_by: Optional[str] = None - ) -> Dict: + ) -> str: """Return all analysis results. Args: @@ -272,7 +272,7 @@ def analysis_results( sort_by: Indicates how the output should be sorted. Returns: - JSON response. + Server response. """ url = self.get_url('analysis_results') params = {} # type: Dict[str, Any] @@ -296,7 +296,7 @@ def analysis_results( params['tags'] = tags if sort_by: params['sort'] = sort_by - return self.session.get(url, params=params).json() + return self.session.get(url, params=params).text def analysis_result_upload(self, result: Dict) -> Dict: """Upload an analysis result. diff --git a/qiskit/providers/ibmq/experiment/__init__.py b/qiskit/providers/ibmq/experiment/__init__.py index e0eeaef5d..b657ee479 100644 --- a/qiskit/providers/ibmq/experiment/__init__.py +++ b/qiskit/providers/ibmq/experiment/__init__.py @@ -19,6 +19,10 @@ Modules related to IBM Quantum experiment service. +.. note:: + + This service is not available to all accounts. + You can use the experiment service to query, upload, and retrieve experiments, experiment figures, and analysis results. For example:: @@ -31,7 +35,7 @@ This service is intended to be used in conjunction with the ``qiskit-experiments`` package, which allows you to create different types of experiments (for example, -:class:`~qiskit_experiments.characterization.T1Experiment`). +:class:`qiskit_experiments.library.characterization.T1`). Classes diff --git a/qiskit/providers/ibmq/experiment/ibm_experiment_service.py b/qiskit/providers/ibmq/experiment/ibm_experiment_service.py index 4246d8325..63862c795 100644 --- a/qiskit/providers/ibmq/experiment/ibm_experiment_service.py +++ b/qiskit/providers/ibmq/experiment/ibm_experiment_service.py @@ -10,10 +10,11 @@ # copyright notice, and modified files need to carry a notice indicating # that they have been altered from the originals. -"""IBM Quantum Experience experiment service.""" +"""IBM Quantum experiment service.""" import logging -from typing import Optional, List, Dict, Union, Tuple, Any +import json +from typing import Optional, List, Dict, Union, Tuple, Any, Type from datetime import datetime from collections import defaultdict @@ -23,7 +24,7 @@ from .constants import (ExperimentShareLevel, ResultQuality, RESULT_QUALITY_FROM_API, RESULT_QUALITY_TO_API) from .utils import map_api_error -from .device_component import DeviceComponent, to_component +from .device_component import DeviceComponent from ..utils.converters import local_to_utc_str, utc_to_local from ..api.clients.experiment import ExperimentClient from ..api.exceptions import RequestsApiError @@ -41,7 +42,7 @@ class IBMExperimentService: retrieve experiments, experiment figures, and analysis results. The ``experiment`` attribute of :class:`~qiskit.providers.ibmq.accountprovider.AccountProvider` is an - instance of this class, and the main syntax for using the services is + instance of this class, and the main syntax for using the service is ``provider.experiment.``. For example:: from qiskit import IBMQ @@ -79,7 +80,7 @@ def __init__( self, provider: 'accountprovider.AccountProvider' ) -> None: - """IBMQBackendService constructor. + """IBMExperimentService constructor. Args: provider: IBM Quantum Experience account provider. @@ -99,7 +100,7 @@ def _default_options(cls) -> Dict: return {"auto_save": False} def backends(self) -> List[Dict]: - """Return a list of backends. + """Return a list of backends that can be used for experiments. Returns: A list of backends. @@ -151,7 +152,8 @@ def create_experiment( """ # pylint: disable=arguments-differ if kwargs: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", kwargs.keys()) data = { @@ -211,7 +213,8 @@ def update_experiment( """ # pylint: disable=arguments-differ if kwargs: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", kwargs.keys()) data = self._experiment_data_to_api(metadata=metadata, @@ -276,12 +279,14 @@ def _experiment_data_to_api( def experiment( self, - experiment_id: str + experiment_id: str, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder ) -> Dict: """Retrieve a previously stored experiment. Args: experiment_id: Experiment ID. + json_decoder: Custom JSON decoder to use to decode the retrieved experiment. Returns: Retrieved experiment data. @@ -293,11 +298,12 @@ def experiment( with map_api_error(f"Experiment {experiment_id} not found."): raw_data = self._api_client.experiment_get(experiment_id) - return self._api_to_experiment_data(raw_data) + return self._api_to_experiment_data(json.loads(raw_data, cls=json_decoder)) def experiments( self, limit: Optional[int] = 10, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder, device_components: Optional[List[Union[str, DeviceComponent]]] = None, device_components_operator: Optional[str] = None, experiment_type: Optional[str] = None, @@ -327,6 +333,7 @@ def experiments( Args: limit: Number of experiments to retrieve. ``None`` indicates no limit. + json_decoder: Custom JSON decoder to use to decode the retrieved experiments. device_components: Filter by device components. device_components_operator: Operator used when filtering by device components. Valid values are ``None`` and "contains": @@ -379,9 +386,10 @@ def experiments( Cannot be ``True`` if `exclude_mine` is ``True``. sort_by: Specifies how the output should be sorted. This can be a single sorting option or a list of options. Each option should contain a sort key - and a direction. Valid sort keys are "start_datetime" and "experiment_type". + and a direction, separated by a semicolon. Valid sort keys are + "start_datetime" and "experiment_type". Valid directions are "asc" for ascending or "desc" for descending. - For example, ``sort_by=["experiment_type: asc", "start_datetime:desc"]`` will + For example, ``sort_by=["experiment_type:asc", "start_datetime:desc"]`` will return an output list that is first sorted by experiment type in ascending order, then by start datetime by descending order. By default, experiments are sorted by ``start_datetime`` @@ -398,7 +406,8 @@ def experiments( """ # pylint: disable=arguments-differ if filters: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", filters.keys()) if limit is not None and (not isinstance(limit, int) or limit <= 0): # type: ignore @@ -441,7 +450,7 @@ def experiments( marker = None while limit is None or limit > 0: with map_api_error(f"Request failed."): - raw_data = self._api_client.experiments( + response = self._api_client.experiments( limit=limit, marker=marker, backend_name=backend_name, @@ -455,6 +464,7 @@ def experiments( exclude_mine=exclude_mine, mine_only=mine_only, sort_by=converted["sort_by"]) + raw_data = json.loads(response, cls=json_decoder) marker = raw_data.get('marker') for exp in raw_data['experiments']: experiments.append(self._api_to_experiment_data(exp)) @@ -553,7 +563,7 @@ def delete_experiment(self, experiment_id: str) -> None: def create_analysis_result( self, experiment_id: str, - data: Dict, + result_data: Dict, result_type: str, device_components: Optional[Union[List[Union[str, DeviceComponent]], str, DeviceComponent]] = None, @@ -568,7 +578,7 @@ def create_analysis_result( Args: experiment_id: ID of the experiment this result is for. - data: Result data to be stored. + result_data: Result data to be stored. result_type: Analysis result type. device_components: Target device components, such as qubits. tags: Tags to be associated with the analysis result. @@ -589,7 +599,8 @@ def create_analysis_result( """ # pylint: disable=arguments-differ if kwargs: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", kwargs.keys()) components = [] @@ -597,9 +608,7 @@ def create_analysis_result( if not isinstance(device_components, list): device_components = [device_components] for comp in device_components: - if isinstance(comp, str): - comp = to_component(comp) - components.append(comp) + components.append(str(comp)) if isinstance(quality, str): quality = ResultQuality(quality.upper()) @@ -607,7 +616,7 @@ def create_analysis_result( request = self._analysis_result_to_api( experiment_id=experiment_id, device_components=components, - data=data, + data=result_data, result_type=result_type, tags=tags, quality=quality, @@ -622,7 +631,7 @@ def create_analysis_result( def update_analysis_result( self, result_id: str, - data: Optional[Dict] = None, + result_data: Optional[Dict] = None, tags: Optional[List[str]] = None, quality: Union[ResultQuality, str] = None, verified: bool = None, @@ -633,7 +642,7 @@ def update_analysis_result( Args: result_id: Analysis result ID. - data: Result data to be stored. + result_data: Result data to be stored. quality: Quality of this analysis. verified: Whether the result quality has been verified. tags: Tags to be associated with the analysis result. @@ -647,13 +656,14 @@ def update_analysis_result( """ # pylint: disable=arguments-differ if kwargs: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", kwargs.keys()) if isinstance(quality, str): quality = ResultQuality(quality.upper()) - request = self._analysis_result_to_api(data=data, + request = self._analysis_result_to_api(data=result_data, tags=tags, quality=quality, verified=verified, @@ -664,7 +674,7 @@ def update_analysis_result( def _analysis_result_to_api( self, experiment_id: Optional[str] = None, - device_components: Optional[List[DeviceComponent]] = None, + device_components: Optional[List[str]] = None, data: Optional[Dict] = None, result_type: Optional[str] = None, tags: Optional[List[str]] = None, @@ -694,7 +704,7 @@ def _analysis_result_to_api( if experiment_id: out["experiment_uuid"] = experiment_id if device_components: - out["device_components"] = [str(comp) for comp in device_components] + out["device_components"] = device_components if data: out["fit"] = data if result_type: @@ -714,11 +724,13 @@ def _analysis_result_to_api( def analysis_result( self, result_id: str, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder ) -> Dict: """Retrieve a previously stored experiment. Args: result_id: Analysis result ID. + json_decoder: Custom JSON decoder to use to decode the retrieved analysis result. Returns: Retrieved analysis result. @@ -730,11 +742,12 @@ def analysis_result( with map_api_error(f"Analysis result {result_id} not found."): raw_data = self._api_client.analysis_result_get(result_id) - return self._api_to_analysis_result(raw_data) + return self._api_to_analysis_result(json.loads(raw_data, cls=json_decoder)) def analysis_results( self, limit: Optional[int] = 10, + json_decoder: Type[json.JSONDecoder] = json.JSONDecoder, device_components: Optional[List[Union[str, DeviceComponent]]] = None, device_components_operator: Optional[str] = None, experiment_id: Optional[str] = None, @@ -752,6 +765,7 @@ def analysis_results( Args: limit: Number of analysis results to retrieve. + json_decoder: Custom JSON decoder to use to decode the retrieved analysis results. device_components: Filter by device components. device_components_operator: Operator used when filtering by device components. Valid values are ``None`` and "contains": @@ -811,7 +825,8 @@ def analysis_results( """ # pylint: disable=arguments-differ if filters: - logger.info("Keywords %s are not supported by IBM Quantum experiment service.", + logger.info("Keywords %s are not supported by IBM Quantum experiment service " + "and will be ignored.", filters.keys()) if limit is not None and (not isinstance(limit, int) or limit <= 0): # type: ignore @@ -836,7 +851,7 @@ def analysis_results( marker = None while limit is None or limit > 0: with map_api_error("Request failed."): - raw_data = self._api_client.analysis_results( + response = self._api_client.analysis_results( limit=limit, marker=marker, backend_name=backend_name, @@ -848,6 +863,7 @@ def analysis_results( tags=converted["tags"], sort_by=converted["sort_by"] ) + raw_data = json.loads(response, cls=json_decoder) marker = raw_data.get('marker') for result in raw_data['analysis_results']: results.append(self._api_to_analysis_result(result)) diff --git a/test/ibmq/experiment/test_experiment_server_integration.py b/test/ibmq/experiment/test_experiment_server_integration.py index 186874d3e..d168fc0ec 100644 --- a/test/ibmq/experiment/test_experiment_server_integration.py +++ b/test/ibmq/experiment/test_experiment_server_integration.py @@ -543,7 +543,7 @@ def test_upload_analysis_result(self): aresult_id = self.provider.experiment.create_analysis_result( experiment_id=exp_id, result_type="qiskit_test", - data=fit, + result_data=fit, device_components=self.device_components, tags=["qiskit_test"], quality=ResultQuality.GOOD, @@ -573,7 +573,7 @@ def test_update_analysis_result(self): self.provider.experiment.update_analysis_result( result_id=result_id, - data=fit, + result_data=fit, tags=["qiskit_test"], quality=ResultQuality.GOOD, verified=True, @@ -991,7 +991,7 @@ def _create_analysis_result( data = data or {} aresult_id = self.provider.experiment.create_analysis_result( experiment_id=experiment_id, - data=data, + result_data=data, result_type=result_type, **kwargs )