From 376547ac7363f3359857870524dbd746c31766ce Mon Sep 17 00:00:00 2001 From: Rathish Cholarajan Date: Wed, 2 Mar 2022 00:18:11 -0500 Subject: [PATCH] Remove random service --- MIGRATING.md | 1 - Makefile | 4 +- docs/apidocs/ibm-provider.rst | 1 - docs/apidocs/ibm_random.rst | 6 - qiskit_ibm/api/clients/random.py | 90 ------ qiskit_ibm/api/rest/random.py | 85 ------ qiskit_ibm/hub_group_project.py | 4 +- qiskit_ibm/ibm_provider.py | 31 +-- qiskit_ibm/random/__init__.py | 50 ---- qiskit_ibm/random/baserandomservice.py | 52 ---- qiskit_ibm/random/cqcextractor.py | 191 ------------- qiskit_ibm/random/cqcextractorjob.py | 196 ------------- qiskit_ibm/random/ibm_random_service.py | 119 -------- qiskit_ibm/random/utils.py | 59 ---- ...emove-random-service-9f799ce30099bbff.yaml | 5 + test/ibm/test_ibm_provider.py | 8 - test/ibm/test_random.py | 259 ------------------ 17 files changed, 11 insertions(+), 1150 deletions(-) delete mode 100644 docs/apidocs/ibm_random.rst delete mode 100644 qiskit_ibm/api/clients/random.py delete mode 100644 qiskit_ibm/api/rest/random.py delete mode 100644 qiskit_ibm/random/__init__.py delete mode 100644 qiskit_ibm/random/baserandomservice.py delete mode 100644 qiskit_ibm/random/cqcextractor.py delete mode 100644 qiskit_ibm/random/cqcextractorjob.py delete mode 100644 qiskit_ibm/random/ibm_random_service.py delete mode 100644 qiskit_ibm/random/utils.py create mode 100644 releasenotes/notes/remove-random-service-9f799ce30099bbff.yaml delete mode 100644 test/ibm/test_random.py diff --git a/MIGRATING.md b/MIGRATING.md index 2c51c4fd0..04969ba0a 100644 --- a/MIGRATING.md +++ b/MIGRATING.md @@ -182,7 +182,6 @@ result = job.result() | IBMQBackend | IBMBackend | | IBMQBackendService | IBMBackendService | | IBMQJob | IBMJob | -| IBMQRandomService | IBMRandomService | | IBMQError | IBMError | | IBMQProviderError | IBMProviderError | | IBMQAccountError | None (Removed) | diff --git a/Makefile b/Makefile index 598c3a8f2..cf84c3171 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ # that they have been altered from the originals. -.PHONY: lint style test mypy test1 test2 test3 runtime_integration +.PHONY: lint style test mypy test1 test2 test3 runtime_integration lint: pylint -rn qiskit_ibm test @@ -30,7 +30,7 @@ test1: python -m unittest -v test/ibm/test_ibm_backend.py test/ibm/test_account_client.py test/ibm/test_ibm_job_states.py test/ibm/test_tutorials.py test/ibm/test_basic_server_paths.py test/ibm/test_proxies.py test/ibm/test_ibm_integration.py test/ibm/test_ibm_logger.py test/ibm/test_filter_backends.py test/ibm/test_registration.py test2: - python -m unittest -v test/ibm/test_ibm_qasm_simulator.py test/ibm/test_serialization.py test/ibm/test_jupyter.py test/ibm/test_composite_job.py test/ibm/test_random.py test/ibm/test_ibm_provider.py + python -m unittest -v test/ibm/test_ibm_qasm_simulator.py test/ibm/test_serialization.py test/ibm/test_jupyter.py test/ibm/test_composite_job.py test/ibm/test_ibm_provider.py test3: python -m unittest -v test/ibm/test_ibm_job_attributes.py test/ibm/test_ibm_job.py test/ibm/websocket/test_websocket.py test/ibm/websocket/test_websocket_integration.py diff --git a/docs/apidocs/ibm-provider.rst b/docs/apidocs/ibm-provider.rst index 51c647b18..a0e869164 100644 --- a/docs/apidocs/ibm-provider.rst +++ b/docs/apidocs/ibm-provider.rst @@ -11,4 +11,3 @@ Qiskit IBM Quantum Provider API Reference ibm_visualization ibm_jupyter ibm_utils - ibm_random diff --git a/docs/apidocs/ibm_random.rst b/docs/apidocs/ibm_random.rst deleted file mode 100644 index 390503564..000000000 --- a/docs/apidocs/ibm_random.rst +++ /dev/null @@ -1,6 +0,0 @@ -.. _qiskit_ibm-random: - -.. automodule:: qiskit_ibm.random - :no-members: - :no-inherited-members: - :no-special-members: diff --git a/qiskit_ibm/api/clients/random.py b/qiskit_ibm/api/clients/random.py deleted file mode 100644 index 7dc367df3..000000000 --- a/qiskit_ibm/api/clients/random.py +++ /dev/null @@ -1,90 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Client for accessing Random Number Generator (RNG) services.""" - -import logging -from typing import List, Dict, Any - -from qiskit_ibm.credentials import Credentials -from qiskit_ibm.api.session import RetrySession - -from ..rest.random import Random - -logger = logging.getLogger(__name__) - - -class RandomClient: - """Client for accessing RNG services.""" - - def __init__( - self, - credentials: Credentials, - ) -> None: - """RandomClient constructor. - - Args: - credentials: Account credentials. - """ - self._session = RetrySession(credentials.extractor_url, credentials.access_token, - **credentials.connection_parameters()) - self.random_api = Random(self._session) - - def list_services(self) -> List[Dict[str, Any]]: - """Return RNG services available for this provider. - - Returns: - RNG services available for this provider. - """ - return self.random_api.list_services() - - def extract( - self, - name: str, - method: str, - data: Dict, - files: Dict - ) -> Dict: - """Perform extraction asynchronously. - - Args: - name: Name of the extractor. - method: Extractor method. - data: Encoded extractor parameters. - files: Raw extractor parameters. - - Returns: - JSON response. - """ - return self.random_api.extract(name, method, data, files) - - def job_get(self, job_id: str) -> Dict: - """Retrieve a job. - - Args: - job_id: Job ID. - - Returns: - JSON response. - """ - return self.random_api.job_get(job_id) - - def get_object_storage(self, url: str) -> Any: - """Get data from object storage. - - Args: - url: Object storage URL. - - Returns: - Response data. - """ - return self.random_api.get_object_storage(url) diff --git a/qiskit_ibm/api/rest/random.py b/qiskit_ibm/api/rest/random.py deleted file mode 100644 index 1e1016a1d..000000000 --- a/qiskit_ibm/api/rest/random.py +++ /dev/null @@ -1,85 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Random REST adapter.""" - -import logging -from typing import Dict, List, Any - -from qiskit_ibm.api.rest.base import RestAdapterBase - -logger = logging.getLogger(__name__) - - -class Random(RestAdapterBase): - """Rest adapter for RNG related endpoints.""" - - URL_MAP = { - 'list_extractors': '/extractors', - 'extract': '/extractors/{}/{}/async', - 'job_get': '/tasks/{}' - } - - def list_services(self) -> List[Dict[str, Any]]: - """Return a list of RNG services. - - Returns: - JSON response. - """ - url = self.get_url('list_extractors') - return self.session.get(url).json() - - def extract( - self, - name: str, - method: str, - data: Dict, - files: Dict - ) -> Dict: - """Invoke the remote extractor asynchronously. - - Args: - name: Name of the extractor. - method: Extractor method. - data: Encoded extractor parameters. - files: Raw extractor parameters. - - Returns: - JSON response. - """ - url = self.get_url('extract').format(name, method) - return self.session.post(url, data=data, files=files, timeout=600).json() - - def job_get(self, job_id: str) -> Dict: - """Retrieve a job. - - Args: - job_id: Job ID. - - Returns: - JSON response. - """ - url = self.get_url('job_get').format(job_id) - return self.session.get(url).json() - - def get_object_storage(self, url: str) -> Any: - """Get data from object storage. - - Args: - url: Object storage URL. - - Returns: - Response data. - """ - logger.debug('Downloading from object storage.') - response = self.session.get(url, bare=True, timeout=600) - return response.content diff --git a/qiskit_ibm/hub_group_project.py b/qiskit_ibm/hub_group_project.py index 10486f879..4f238b5c3 100644 --- a/qiskit_ibm/hub_group_project.py +++ b/qiskit_ibm/hub_group_project.py @@ -54,9 +54,7 @@ def __init__( # Initialize the internal list of backends. self._backends: Dict[str, IBMBackend] = {} self._service_urls = { - 'backend': self.credentials.url, - 'experiment': self.credentials.experiment_url, - 'random': self.credentials.extractor_url + 'backend': self.credentials.url } @property diff --git a/qiskit_ibm/ibm_provider.py b/qiskit_ibm/ibm_provider.py index e2e6856cd..12b3b205b 100644 --- a/qiskit_ibm/ibm_provider.py +++ b/qiskit_ibm/ibm_provider.py @@ -35,7 +35,6 @@ from .hub_group_project import HubGroupProject # pylint: disable=cyclic-import from .ibm_backend import IBMBackend # pylint: disable=cyclic-import from .ibm_backend_service import IBMBackendService # pylint: disable=cyclic-import -from .random.ibm_random_service import IBMRandomService # pylint: disable=cyclic-import logger = logging.getLogger(__name__) @@ -139,9 +138,7 @@ def __init__( * verify (bool): verify the server's TLS certificate. Returns: - An instance of IBMProvider with services like :class:`~qiskit_ibm.IBMBackendService` and - :class:`~qiskit_ibm.random.IBMRandomService` - as available to the account. + An instance of IBMProvider Raises: IBMProviderCredentialsInvalidFormat: If the default hub/group/project saved on @@ -381,20 +378,14 @@ def _get_hgps( def _initialize_services(self) -> None: """Initialize all services.""" self._backend = None - self._random = None hgps = self._get_hgps() for hgp in hgps: # Initialize backend service if not self._backend: self._backend = IBMBackendService(self, hgp) - # Initialize other services. - if not self._random: - self._random = IBMRandomService(self, hgp) \ - if hgp.has_service('random') else None - if all([self._backend, self._random]): + if self._backend: break - self._services = {'backend': self._backend, - 'random': self._random} + self._services = {'backend': self._backend} @property def backend(self) -> IBMBackendService: @@ -405,22 +396,6 @@ def backend(self) -> IBMBackendService: """ return self._backend - @property - def random(self) -> IBMRandomService: - """Return the random number service. - - Returns: - The random number service instance. - - Raises: - IBMNotAuthorizedError: If the account is not authorized to use - the service. - """ - if self._random: - return self._random - else: - raise IBMNotAuthorizedError("You are not authorized to use the service.") - def active_account(self) -> Optional[Dict[str, str]]: """Return the IBM Quantum account currently in use for the session. diff --git a/qiskit_ibm/random/__init__.py b/qiskit_ibm/random/__init__.py deleted file mode 100644 index fbf6b0945..000000000 --- a/qiskit_ibm/random/__init__.py +++ /dev/null @@ -1,50 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -""" -============================================================ -Random Number Services (:mod:`qiskit_ibm.random`) -============================================================ - -.. currentmodule:: qiskit_ibm.random - -Modules related to IBM Quantum random number generator services. - -.. caution:: - - This package is currently provided in beta form and heavy modifications to - both functionality and API are likely to occur. - -The only service currently provided is the Cambridge Quantum Computing (CQC) -extractor. To use this service, you need to first generate raw random bits -and a set of parameters for the extractor. See -`qiskit_rng `_ for more -details. - -.. note:: - - The CQC extractor service is not available to all accounts. - -Classes -========================== -.. autosummary:: - :toctree: ../stubs/ - - IBMRandomService - CQCExtractor - CQCExtractorJob - -""" - -from .ibm_random_service import IBMRandomService -from .cqcextractor import CQCExtractor -from .cqcextractorjob import CQCExtractorJob diff --git a/qiskit_ibm/random/baserandomservice.py b/qiskit_ibm/random/baserandomservice.py deleted file mode 100644 index 202aa77f1..000000000 --- a/qiskit_ibm/random/baserandomservice.py +++ /dev/null @@ -1,52 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Module for interfacing with a remote extractor.""" - -import logging -from typing import List, Any -from abc import ABC, abstractmethod - - -from qiskit_ibm import ibm_provider # pylint: disable=unused-import -from ..api.clients.random import RandomClient - -logger = logging.getLogger(__name__) - - -class BaseRandomService(ABC): - """Base class for random number services.""" - - def __init__( - self, - name: str, - provider: 'ibm_provider.IBMProvider', - client: RandomClient, - methods: List - ): - """BaseRandomService constructor. - - Args: - name: Name of the extractor. - provider: IBM Quantum account provider. - client: Client used to communicate with the server. - methods: Service methods. - """ - self.name = name - self._provider = provider - self._client = client - self.methods = methods - - @abstractmethod - def run(self, *args: Any, **kwargs: Any) -> Any: - """Execute the service.""" - pass diff --git a/qiskit_ibm/random/cqcextractor.py b/qiskit_ibm/random/cqcextractor.py deleted file mode 100644 index a0b709a55..000000000 --- a/qiskit_ibm/random/cqcextractor.py +++ /dev/null @@ -1,191 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Module for interfacing with a remote extractor.""" - -import logging -from typing import Optional, Callable, List - -import numpy as np - -from .utils import generate_wsr, bitarray_to_bytes -from .baserandomservice import BaseRandomService -from .cqcextractorjob import CQCExtractorJob - -logger = logging.getLogger(__name__) - - -class CQCExtractor(BaseRandomService): - """Class for interfacing with a CQC remote extractor. - - There are two extractor methods - Dodis (extractor 1) and Hayashi (extractor 2). - These methods can be invoked synchronously or asynchronously. - To invoke them synchronously:: - - random_bits = extractor.run(*cqc_parameters) - - To invoke them asynchronously:: - - import numpy as np - extractor1_out = extractor.run_async_ext1(*ext1_parameters).block_until_ready() - extractor2_out = extractor.run_async_ext2( - ext2_seed=extractor1_out, *ext2_parameters).block_until_ready() - random_bits = np.append(extractor1_out, extractor2_out) - - Running them asynchronously takes more steps because extractor 2 uses the - output of extractor 1 as its seed, so it must wait for extractor 1 to finish first. - """ - - def run( # type: ignore[override] - self, - ext1_input_num_bits: int, - ext1_output_num_bits: int, - ext1_raw_bytes: bytes, - ext1_wsr_bytes: bytes, - ext2_seed_num_bits: int, - ext2_wsr_multiplier: int, - ext2_wsr_generator: Optional[Callable] = None - ) -> List[int]: - """Process input data synchronously. - - Args: - ext1_input_num_bits: Number of input bits, for extractor 1. - ext1_output_num_bits: Number of output bits, for extractor 1. - ext1_raw_bytes: Initial random numbers, in bytes, for extractor 1. - ext1_wsr_bytes: Initial WSRs, in bytes, for extractor 1. - ext2_seed_num_bits: Number of bits in the seed, for extractor 2. - ext2_wsr_multiplier: WSR multiplier, for extractor 2. The number of - bits used by extractor 2 is ext2_seed_num_bits*ext2_wsr_multiplier. - ext2_wsr_generator: WSR generator used for extractor 2. It must take the - number of bits as the input and a list of random bits (0s and 1s) - as the output. If ``None``, :func:``generate_wsr`` is used. - - Returns: - An instance of ``CQCExtractorJob`` which can be used to retrieve the - results later. - """ - # pylint: disable=arguments-differ - # Run ext1 - output = self.run_async_ext1(ext1_input_num_bits, ext1_output_num_bits, - ext1_raw_bytes, ext1_wsr_bytes).block_until_ready() - - # Run ext2 if requested. - if ext2_wsr_multiplier != 0: - ext2_out = self.run_async_ext2( - output, ext2_seed_num_bits, ext2_wsr_multiplier, - ext2_wsr_generator).block_until_ready() - - output = np.append(output, ext2_out).tolist() - return output - - def run_async_ext1( - self, - ext1_input_num_bits: int, - ext1_output_num_bits: int, - ext1_raw_bytes: bytes, - ext1_wsr_bytes: bytes - ) -> CQCExtractorJob: - """Run the first extractor asynchronously. - - Args: - ext1_input_num_bits: Number of input bits, for extractor 1. - ext1_output_num_bits: Number of output bits, for extractor 1. - ext1_raw_bytes: Initial random numbers, in bytes, for extractor 1. - ext1_wsr_bytes: Initial WSRs, in bytes, for extractor 1. - - Returns: - An instance of ``CQCExtractorJob`` which can be used to retrieve the - results later. - - Raises: - ValueError: If an invalid argument values are specified. - """ - if not ext1_input_num_bits or not ext1_output_num_bits: - raise ValueError("Invalid input arguments. ext1_input_num_bits and " - "ext1_output_num_bits must be non-zero.") - - logger.info("Starting first extraction.") - # Run ext1 - ext1_data = {"n": ext1_input_num_bits, - "m": ext1_output_num_bits} - ext1_files = {"x": ext1_raw_bytes, - "y": ext1_wsr_bytes} - response = self._client.extract( - name='cqc', method='ext1', data=ext1_data, files=ext1_files) - parameters = {'ext1_input_num_bits': ext1_input_num_bits, - 'ext1_output_num_bits': ext1_output_num_bits, - 'ext1_raw_bytes': ext1_raw_bytes, - 'ext1_wsr_bytes': ext1_wsr_bytes} - return CQCExtractorJob(job_id=response['id'], client=self._client, parameters=parameters) - - def run_async_ext2( - self, - ext2_seed: List[int], - ext2_seed_num_bits: int, - ext2_wsr_multiplier: int, - ext2_wsr_generator: Optional[Callable] = None - ) -> CQCExtractorJob: - """Run the second extractor asynchronously. - - Args: - ext2_seed: Seed used for extractor 2, such as the output of extractor 1. - ext2_seed_num_bits: Number of bits in the seed, for extractor 2. - ext2_wsr_multiplier: WSR multiplier, for extractor 2. The number of - bits used by extractor 2 is ext2_seed_num_bits*ext2_wsr_multiplier. - ext2_wsr_generator: WSR generator used for extractor 2. It must take the - number of bits as the input and a list of random bits (0s and 1s) - as the output. If ``None``, :func:``generate_wsr`` is used. - - Returns: - An instance of ``CQCExtractorJob`` which can be used to retrieve the - results later. - - Raises: - ValueError: If an invalid argument values are specified. - """ - if not ext2_seed_num_bits or not ext2_wsr_multiplier: - raise ValueError("Invalid input arguments. ext2_seed_num_bits and " - "ext2_wsr_multiplier must be non-zero.") - - logger.info("Starting second extraction.") - ext2_seed = bitarray_to_bytes(ext2_seed[:ext2_seed_num_bits]) # type: ignore[assignment] - if ext2_wsr_generator is None: - ext2_wsr_generator = generate_wsr - ext2_wsr = ext2_wsr_generator(ext2_seed_num_bits*ext2_wsr_multiplier) - ext2_wsr = bitarray_to_bytes(ext2_wsr) - ext2_data = {"a": ext2_seed_num_bits, - "b": ext2_wsr_multiplier} - ext2_files = {"r": ext2_seed, - "x": ext2_wsr} - response = self._client.extract(name='cqc', method='ext2', - data=ext2_data, files=ext2_files) - parameters = {'ext2_seed_num_bits': ext2_seed_num_bits, - 'ext2_wsr_multiplier': ext2_wsr_multiplier, - 'ext2_seed_bytes': ext2_seed, - 'ext2_wsr': ext2_wsr} - return CQCExtractorJob(job_id=response['id'], client=self._client, parameters=parameters) - - def retrieve_job(self, job_id: str) -> CQCExtractorJob: - """Retrieve a previously submitted job. - - Args: - job_id: Job ID. - - Returns: - A ``CQCExtractorJob`` instance. - """ - return CQCExtractorJob(job_id, self._client) - - def __repr__(self) -> str: - return "<{}('{}') from {}>".format(self.__class__.__name__, - self.name, - self._provider) diff --git a/qiskit_ibm/random/cqcextractorjob.py b/qiskit_ibm/random/cqcextractorjob.py deleted file mode 100644 index d8ec4fbe2..000000000 --- a/qiskit_ibm/random/cqcextractorjob.py +++ /dev/null @@ -1,196 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""IBM Quantum job.""" - -import logging -from typing import Optional, Dict, List -import time - -from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES -from qiskit.providers.exceptions import JobTimeoutError - -from ..api.clients.random import RandomClient -from ..utils.utils import api_status_to_job_status -from .utils import bytes_to_bitarray - -logger = logging.getLogger(__name__) - - -class CQCExtractorJob: - """Representation of an asynchronous call to the CQC extractor. - - An instance of this class is returned when you call - :meth:`~qiskit_ibm.random.CQCExtractor.run_async_ext1`, - :meth:`~qiskit_ibm.random.CQCExtractor.run_async_ext2`, or - :meth:`~qiskit_ibm.random.CQCExtractor.retrieve_job` method of the - :class:`~qiskit_ibm.random.CQCExtractor` class. - - If the job is successfully submitted, you can inspect the job's status by - calling :meth:`status()`. - - Some of the methods in this class are blocking, which means control may - not be returned immediately. :meth:`block_until_ready()` is an example - of a blocking method, which waits until the job completes:: - - job = extractor.run_async_ext1(...) - random_bits = job.block_until_ready() - - Note: - An error may occur when querying the remote server to get job information. - The most common errors are temporary network failures - and server errors, in which case an - :class:`~qiskit_ibm.api.exceptions.RequestsApiError` - is raised. These errors usually clear quickly, so retrying the operation is - likely to succeed. - """ - - def __init__( - self, - job_id: str, - client: RandomClient, - parameters: Optional[Dict] = None, - ) -> None: - """CQCExtractorJob constructor. - - Args: - job_id: Job ID. - client: Object for connecting to the server. - parameters: Parameters used for this job. - """ - self.job_id = job_id - self._client = client - self._parameters = parameters - self._result_url = None # type: Optional[str] - self._result = None # type: Optional[List[int]] - self._status = None - self._api_parameters = None - - def status(self) -> JobStatus: - """Query the server for the latest job status. - - Returns: - The status of the job. - """ - if self._status not in JOB_FINAL_STATES: - self._refresh() - return self._status - - def block_until_ready( - self, - timeout: Optional[float] = None, - wait: float = 10 - ) -> List[int]: - """Wait for the job to finish and return the result. - - Args: - timeout: Seconds to wait for the job. If ``None``, wait indefinitely. - wait: Seconds between queries. Use a larger number if the job is - expected to run for a long time. - - Returns: - Extractor output. - - Raises: - JobTimeoutError: If the job does not finish before the - specified timeout. - """ - if self._result: - return self._result - - self._wait_for_final_state(timeout, wait) - raw_bytes = self._client.get_object_storage(self._result_url) - if self.extractor_method == 'ext1': - self._result = bytes_to_bitarray(raw_bytes, self.parameters['ext1_output_num_bits']) - else: - self._result = bytes_to_bitarray( - raw_bytes, - (self.parameters['ext2_wsr_multiplier']-1)*self.parameters['ext2_seed_num_bits']) - - return self._result - - @property - def extractor_method(self) -> str: - """Return the extractor method used. - - Returns: - Extractor method used. - """ - if not self._api_parameters: - self._refresh() - if set(self._api_parameters.keys()) == {'n', 'm', 'x', 'y'}: - return 'ext1' - return 'ext2' - - @property - def parameters(self) -> Dict: - """Return the parameters passed to the extractor. - - Returns: - Parameters passed to the extractor. - """ - if self._parameters: - return self._parameters - - if not self._api_parameters: - self._refresh() - - if self.extractor_method == 'ext1': - self._parameters = { - 'ext1_input_num_bits': self._api_parameters['n'], - 'ext1_output_num_bits': self._api_parameters['m'], - 'ext1_raw_bytes': self._client.get_object_storage(self._api_parameters['x']), - 'ext1_wsr_bytes': self._client.get_object_storage(self._api_parameters['y']) - } - else: - self._parameters = { - 'ext2_seed_num_bits': self._api_parameters['a'], - 'ext2_wsr_multiplier': self._api_parameters['b'], - 'ext2_seed_bytes': self._client.get_object_storage(self._api_parameters['r']), - 'ext2_wsr': self._client.get_object_storage(self._api_parameters['x']) - } - - return self._parameters - - def _refresh(self) -> None: - """Retrieve job data from the remote server.""" - response = self._client.job_get(self.job_id) - self._status = api_status_to_job_status(response['status']) - if not self._api_parameters: - self._api_parameters = response['parameters'] - if self._status == JobStatus.DONE: - self._result_url = response['result'] - - def _wait_for_final_state( - self, - timeout: Optional[float] = None, - wait: float = 30 - ) -> None: - """Poll the job status until it progresses to a final state such as ``DONE`` or ``ERROR``. - - Args: - timeout: Seconds to wait for the job. If ``None``, wait indefinitely. - wait: Seconds between queries. - - Raises: - JobTimeoutError: If the job does not reach a final state before the - specified timeout. - """ - start_time = time.time() - status = self.status() - while status not in JOB_FINAL_STATES: - elapsed_time = time.time() - start_time - if timeout is not None and elapsed_time >= timeout: - raise JobTimeoutError( - 'Timeout while waiting for job {}.'.format(self.job_id)) - time.sleep(wait) - status = self.status() diff --git a/qiskit_ibm/random/ibm_random_service.py b/qiskit_ibm/random/ibm_random_service.py deleted file mode 100644 index 5a2469863..000000000 --- a/qiskit_ibm/random/ibm_random_service.py +++ /dev/null @@ -1,119 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""IBM Quantum random number service.""" - -import logging -from typing import Dict, List, Any - -from qiskit_ibm import ibm_provider # pylint: disable=unused-import -from .baserandomservice import BaseRandomService -from .cqcextractor import CQCExtractor -from ..api.clients.random import RandomClient -from ..api.exceptions import RequestsApiError -from ..exceptions import IBMError -from ..hub_group_project import HubGroupProject - -logger = logging.getLogger(__name__) - - -class IBMRandomService: - """Random number services for an IBM Quantum account provider. - - Represent a namespace for random number services available to this provider. - An instance of this class is used as an attribute to the - :class:`~qiskit_ibm.IBMProvider` class. - This allows a convenient way to query for - all services or to access a specific one:: - - random_services = provider.random.services() - extractor = provider.random.get_extractor('cqc_extractor') - extractor = provider.random.cqc_extractor # Short hand for above. - """ - - def __init__(self, provider: 'ibm_provider.IBMProvider', hgp: HubGroupProject) -> None: - """IBMRandomService constructor. - - Args: - provider: IBM Quantum account provider. - hgp: default hub/group/project to use for the service. - """ - self._provider = provider - self._default_hgp = hgp - if hgp.credentials.extractor_url: - self._random_client = RandomClient(hgp.credentials) - self._initialized = False - else: - self._random_client = None - self._initialized = True - self._services = {} # type: Dict[str, BaseRandomService] - - def _discover_services(self) -> None: - """Discovers the remote random services for this provider, if not already known.""" - if not self._initialized: - try: - services = self._random_client.list_services() - for service in services: - service_name = service['name'] - if service_name == 'cqc': - service_name = 'cqc_extractor' - extractor = CQCExtractor(name=service_name, - provider=self._provider, - client=self._random_client, - methods=service['extractors']) - self._services[service_name] = extractor - else: - logger.warning("Unknown service %s found. It will be ignored.", - service_name) - self.__dict__.update(self._services) - self._initialized = True - except RequestsApiError as err: - logger.warning("Unable to retrieve service information. " - "Please try again later. Error: %s", str(err)) - pass - - def services(self) -> List[BaseRandomService]: - """Return all random number services available to this account.""" - self._discover_services() - return list(self._services.values()) - - def get_service(self, name: str) -> BaseRandomService: - """Return the random number service with the given name. - - Args: - name: Name of the service. - - Returns: - Service with the given name. - - Raises: - IBMError: If the service cannot be found. - """ - self._discover_services() - service = self._services.get(name, None) - if service is None: - raise IBMError('No service with the name {} can be found.'.format(name)) - - return service - - def __dir__(self) -> Dict: - self._discover_services() - return self.__dict__ - - def __getattr__(self, item: str) -> Any: - if not self.__getattribute__('_initialized'): - self._discover_services() - try: - return self._services[item] - except KeyError: - raise AttributeError("'{}' object has no attribute '{}'".format( - self.__class__.__name__, item)) diff --git a/qiskit_ibm/random/utils.py b/qiskit_ibm/random/utils.py deleted file mode 100644 index eba0d2c2f..000000000 --- a/qiskit_ibm/random/utils.py +++ /dev/null @@ -1,59 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Module for utility functions.""" - -from typing import List - -import numpy as np - - -def bytes_to_bitarray(the_bytes: bytes, num_bits: int) -> List[int]: - """Convert input bytes into an array of bits. - - Args: - the_bytes: Bytes to be converted. - num_bits: Number of bits to return. - - Returns: - An array of bits. - """ - return [(the_bytes[i >> 3] >> (i & 7)) & 1 for i in range(num_bits)] - - -def bitarray_to_bytes(bitarray: List[int]) -> bytes: - """Convert an array of bits to bytes. - - Args: - bitarray: Bit array to be converted. - - Returns: - Input array in bytes. - """ - n_bits = len(bitarray) - n_bytes = (n_bits + 7) >> 3 - int_array = [0] * n_bytes - for i in range(n_bits): - int_array[i >> 3] |= bitarray[i] << (i & 7) - return bytes(int_array) - - -def generate_wsr(num_bits: int) -> List: - """Generate a list of WSR bits. - - Args: - num_bits: Number of bits needed. - - Returns: - A list of random binary numbers. - """ - return list(np.random.randint(2, size=num_bits)) diff --git a/releasenotes/notes/remove-random-service-9f799ce30099bbff.yaml b/releasenotes/notes/remove-random-service-9f799ce30099bbff.yaml new file mode 100644 index 000000000..ac641f51a --- /dev/null +++ b/releasenotes/notes/remove-random-service-9f799ce30099bbff.yaml @@ -0,0 +1,5 @@ +--- +upgrade: + - | + `qiskit.providers.ibmq.random`, the random number service which was used to access the CQC + randomness extractor is no longer supported and has been removed. \ No newline at end of file diff --git a/test/ibm/test_ibm_provider.py b/test/ibm/test_ibm_provider.py index eebd32717..ade4f0425 100644 --- a/test/ibm/test_ibm_provider.py +++ b/test/ibm/test_ibm_provider.py @@ -24,7 +24,6 @@ from qiskit_ibm.ibm_backend import IBMSimulator, IBMBackend from qiskit_ibm.ibm_backend_service import IBMBackendService -from qiskit_ibm.random import IBMRandomService from qiskit_ibm.runtime import IBMRuntimeService from qiskit_ibm.ibm_provider import IBMProvider from qiskit_ibm import hub_group_project @@ -467,10 +466,3 @@ def test_provider_services(self): self.assertIsInstance(services['backend'], IBMBackendService) self.assertIsInstance(self.provider.service('backend'), IBMBackendService) self.assertIsInstance(self.provider.backend, IBMBackendService) - - if 'random' in services: - self.assertIsInstance(self.provider.service('random'), IBMRandomService) - self.assertIsInstance(self.provider.random, IBMRandomService) - if 'runtime' in services: - self.assertIsInstance(self.provider.service('runtime'), IBMRuntimeService) - self.assertIsInstance(self.provider.runtime, IBMRuntimeService) diff --git a/test/ibm/test_random.py b/test/ibm/test_random.py deleted file mode 100644 index 8d914f9e0..000000000 --- a/test/ibm/test_random.py +++ /dev/null @@ -1,259 +0,0 @@ -# This code is part of Qiskit. -# -# (C) Copyright IBM 2021. -# -# This code is licensed under the Apache License, Version 2.0. You may -# obtain a copy of this license in the LICENSE.txt file in the root directory -# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -# -# Any modifications or derivative works of this code must retain this -# copyright notice, and modified files need to carry a notice indicating -# that they have been altered from the originals. - -"""Tests for random number services.""" - -import time -import uuid -from unittest import skipIf -from concurrent.futures import ThreadPoolExecutor -import pkg_resources - -import numpy as np -from qiskit.providers.jobstatus import JobStatus -from qiskit_ibm.random.cqcextractor import CQCExtractor -from qiskit_ibm.random.utils import bitarray_to_bytes -from qiskit_ibm.random.ibm_random_service import IBMRandomService -from qiskit_ibm.exceptions import IBMError - -from ..ibm_test_case import IBMTestCase -from ..decorators import requires_provider - -HAS_QISKIT_RNG = True -try: - from qiskit_rng import Generator - QISKIT_RNG_VERSION = pkg_resources.get_distribution("qiskit_rng").version -except ImportError: - HAS_QISKIT_RNG = False - - -@skipIf(not HAS_QISKIT_RNG, 'qiskit_rng is needed for this test.') -class TestRandomIntegration(IBMTestCase): - """Integration tests for random number services.""" - - @classmethod - @requires_provider - def setUpClass(cls, provider, hub, group, project): - """Initial class level setup.""" - # pylint: disable=arguments-differ - super().setUpClass() - cls.provider = provider - cls.hub = hub - cls.group = group - cls.project = project - - def can_access_extractor(self): - """Return whether there is access to the CQC extractors.""" - try: - self.provider.random.get_service('cqc_extractor') - return True - except IBMError: - return False - - @skipIf(QISKIT_RNG_VERSION <= '0.2.2', "Need qiskit_rng > 0.2.2") - def test_cqc_extractor(self): - """Test invoking the CQC extractors.""" - generator = Generator(self.provider.get_backend('ibmq_qasm_simulator', hub=self.hub, - group=self.group, project=self.project)) - output = generator.sample(1024).block_until_ready() - params = output.get_cqc_extractor_params() - - if not self.can_access_extractor(): - self.skipTest("No access to CQC extractor") - - extractor = self.provider.random.cqc_extractor - job1 = extractor.run_async_ext1(params.ext1_input_num_bits, params.ext1_output_num_bits, - params.ext1_raw_bytes, params.ext1_wsr_bytes) - self.assertEqual(job1.extractor_method, 'ext1') - self.assertEqual(job1.parameters['ext1_input_num_bits'], params.ext1_input_num_bits) - self.assertEqual(job1.parameters['ext1_output_num_bits'], params.ext1_output_num_bits) - self.assertEqual(job1.parameters['ext1_wsr_bytes'], params.ext1_wsr_bytes) - self.assertEqual(job1.parameters['ext1_raw_bytes'], params.ext1_raw_bytes) - self.log.debug("Waiting for extractor 1 to finish.") - ext1_out = job1.block_until_ready(timeout=300) - self.assertIsInstance(ext1_out, list) - self.assertEqual(len(ext1_out), params.ext1_output_num_bits) - - job2 = extractor.run_async_ext2(ext1_out, params.ext2_seed_num_bits, - params.ext2_wsr_multiplier) - self.assertEqual(job2.extractor_method, 'ext2') - self.assertEqual(job2.parameters['ext2_wsr_multiplier'], params.ext2_wsr_multiplier) - self.assertEqual(job2.parameters['ext2_seed_num_bits'], params.ext2_seed_num_bits) - self.assertEqual(job2.parameters['ext2_seed_bytes'], - bitarray_to_bytes(ext1_out[:params.ext2_seed_num_bits])) - self.log.debug("Waiting for extractor 2 to finish.") - ext2_out = job2.block_until_ready(timeout=300) - self.assertIsInstance(ext2_out, list) - - final_out = np.append(ext1_out, ext2_out).tolist() - c_out = extractor.run(*params) - self.assertIsInstance(c_out, list) - self.assertEqual(len(final_out), len(c_out)) - - -class TestRandom(IBMTestCase): - """Tests for random number services.""" - - @classmethod - @requires_provider - def setUpClass(cls, provider, hub, group, project): - """Initial class level setup.""" - # pylint: disable=arguments-differ - super().setUpClass() - cls.provider = provider - cls.hub = hub - cls.group = group - cls.project = project - cls.hub = provider._get_hgp(hub, group, project) - # pylint: disable=no-value-for-parameter - random_service = IBMRandomService(provider, cls.hub) - random_service._random_client = FakeRandomClient() - random_service._initialized = False - cls.provider._random = random_service - - @classmethod - def tearDownClass(cls) -> None: - """Class level teardown.""" - super().tearDownClass() - - def test_list_random_services(self): - """Test listing random number services.""" - random_services = self.provider.random.services() - cqc_found = False - for service in random_services: - if service.name == 'cqc_extractor': - self.assertIsInstance(service, CQCExtractor) - cqc_found = True - self.assertTrue(cqc_found, "CQC extractor not found.") - - def test_get_random_service(self): - """Test retrieving a specific service.""" - self.assertIsInstance(self.provider.random.get_service('cqc_extractor'), CQCExtractor) - self.assertIsInstance(self.provider.random.cqc_extractor, CQCExtractor) - - def test_extractor_sync_run_ext1(self): - """Test invoking extractor 1 synchronously.""" - extractor = self.provider.random.get_service('cqc_extractor') - some_int = 42 - some_byte = some_int.to_bytes(1, 'big') - output = extractor.run(5, 8, some_byte, some_byte, 0, 0) - self.assertIsInstance(output, list) - self.assertEqual(output, FakeRandomClient.RESULT) - - def test_extractor_sync_run_both(self): - """Test invoking both extractors synchronously.""" - extractor = self.provider.random.get_service('cqc_extractor') - some_int = 42 - some_byte = some_int.to_bytes(1, 'big') - output = extractor.run(5, 8, some_byte, some_byte, 8, 2) - self.assertIsInstance(output, list) - self.assertEqual(output, FakeRandomClient.RESULT*2) - - def test_extractor_async_ext1(self): - """Test invoking extractor 1 asynchronously.""" - extractor = self.provider.random.get_service('cqc_extractor') - some_int1 = 42 - some_int2 = 24 - some_byte1 = some_int1.to_bytes(1, 'big') - some_byte2 = some_int2.to_bytes(1, 'big') - job = extractor.run_async_ext1(5, 8, some_byte1, some_byte2) - self.assertEqual(job.parameters['ext1_input_num_bits'], 5) - self.assertEqual(job.parameters['ext1_output_num_bits'], 8) - self.assertEqual(job.parameters['ext1_raw_bytes'], some_byte1) - self.assertEqual(job.parameters['ext1_wsr_bytes'], some_byte2) - self.assertEqual(job.extractor_method, 'ext1') - result = job.block_until_ready() - self.assertEqual(job.status(), JobStatus.DONE) - self.assertEqual(result, FakeRandomClient.RESULT) - - def test_extractor_async_ext2(self): - """Test invoking extractor 2 asynchronously.""" - extractor = self.provider.random.get_service('cqc_extractor') - seed = list(np.random.randint(2, size=8)) - - job = extractor.run_async_ext2(seed, len(seed), 2) - self.assertEqual(job.parameters['ext2_seed_num_bits'], len(seed)) - self.assertEqual(job.parameters['ext2_wsr_multiplier'], 2) - self.assertEqual(job.parameters['ext2_seed_bytes'], bitarray_to_bytes(seed)) - self.assertEqual(job.extractor_method, 'ext2') - result = job.block_until_ready() - self.assertEqual(job.status(), JobStatus.DONE) - self.assertEqual(result, FakeRandomClient.RESULT) - - -class FakeRandomClient: - """Client to return fake extractor data.""" - - RESULT = [0, 1, 0, 1, 0, 1, 0, 1] - - def __init__(self): - self._executor = ThreadPoolExecutor() - self._jobs = {} - - def list_services(self): - """Return fake random services.""" - return [{'name': 'cqc', 'extractors': ['ext1', 'ext2']}] - - def extract(self, name, method, data, files): - """Return fake random output.""" - # pylint: disable=unused-argument - job_id = uuid.uuid4().hex - new_job = BaseFakeJob(self._executor, job_id, method, data, files) - self._jobs[job_id] = new_job - return new_job.job_data(include_result=False) - - def job_get(self, job_id): - """Return fake job data.""" - return self._jobs.get(job_id).job_data() - - def get_object_storage(self, url): - """Return fake data.""" - if url == 'fake_result_url': - return bitarray_to_bytes(self.RESULT) - return bitarray_to_bytes([1, 1, 1]) - - -class BaseFakeJob: - """Base class for faking a remote job.""" - - _job_progress = [ - 'RUNNING', - 'COMPLETED' - ] - - def __init__(self, executor, job_id, method, data, files): - """Initialize a fake job.""" - self._job_id = job_id - self._status = 'RUNNING' - self._method = method - if method == 'ext1': - self._params = {'x': 'fake_x_url', 'y': 'fake_y_url'} - else: - self._params = {'r': 'fake_r_url', 'x': 'fake_x_url'} - self._params.update(data) - self._data = data - self._files = files - self._future = executor.submit(self._auto_progress) - - def _auto_progress(self): - """Automatically update job status.""" - for status in self._job_progress: - time.sleep(1) - self._status = status - - def job_data(self, include_result=True): - """Return current job data.""" - status = self._status - data = {'id': self._job_id, 'status': status, 'parameters': self._params} - if status == 'COMPLETED' and include_result: - data['result'] = 'fake_result_url' - return data