diff --git a/Makefile b/Makefile index 3cd601802f..b1a2db46a9 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ mypy: mypy --module qiskit_ibm_runtime --package test style: - black --check qiskit_ibm_runtime setup.py test docs/tutorials program_source + black --check qiskit_ibm_runtime setup.py test docs/tutorials unit-test: python -m unittest discover --verbose --top-level-directory . --start-directory test/unit @@ -40,4 +40,4 @@ unit-test-coverage: coverage lcov black: - black qiskit_ibm_runtime setup.py test docs/tutorials program_source \ No newline at end of file + black qiskit_ibm_runtime setup.py test docs/tutorials \ No newline at end of file diff --git a/docs/migrate/migrate-setup.rst b/docs/migrate/migrate-setup.rst index 53fbe39a53..e71ba96061 100644 --- a/docs/migrate/migrate-setup.rst +++ b/docs/migrate/migrate-setup.rst @@ -26,18 +26,9 @@ The module from which the classes are imported has changed. The following table * - ``qiskit.providers.ibmq.runtime.RuntimeJob`` - ``qiskit_ibm_runtime.RuntimeJob`` - - * - ``qiskit.providers.ibmq.runtime.RuntimeProgram`` - - ``qiskit_ibm_runtime.RuntimeProgram`` - - - * - ``qiskit.providers.ibmq.runtime.UserMessenger`` - - ``qiskit_ibm_runtime.program.UserMessenger`` - - Notice the new location, in ``qiskit_ibm_runtime.program`` - * - ``qiskit.providers.ibmq.runtime.ProgramBackend`` - - ``qiskit_ibm_runtime.program.ProgramBackend`` - - Notice the new location, in ``qiskit_ibm_runtime.program`` * - ``qiskit.providers.ibmq.runtime.ResultDecoder`` - - ``qiskit_ibm_runtime.program.ResultDecoder`` - - Notice the new location, in ``qiskit_ibm_runtime.program`` + - ``qiskit_ibm_runtime.utils.ResultDecoder`` + - Notice the new location, in ``qiskit_ibm_runtime.utils`` * - ``qiskit.providers.ibmq.runtime.RuntimeEncoder`` - ``qiskit_ibm_runtime.RuntimeEncoder`` - diff --git a/program_source/__init__.py b/program_source/__init__.py deleted file mode 100644 index 2ba089a0d2..0000000000 --- a/program_source/__init__.py +++ /dev/null @@ -1,19 +0,0 @@ -# This code is part of qiskit-runtime. -# -# (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. - -"""Main entry point for the qiskit_runtime""" - - -try: - from .version import version as __version__ -except ImportError: - __version__ = "0.0.0" diff --git a/program_source/circuit_runner/__init__.py b/program_source/circuit_runner/__init__.py deleted file mode 100644 index 24326fd26b..0000000000 --- a/program_source/circuit_runner/__init__.py +++ /dev/null @@ -1,15 +0,0 @@ -# This code is part of qiskit-runtime. -# -# (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. - -""" -Qiskit circuit runner module -""" diff --git a/program_source/circuit_runner/circuit_runner.json b/program_source/circuit_runner/circuit_runner.json deleted file mode 100644 index b32e386920..0000000000 --- a/program_source/circuit_runner/circuit_runner.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "circuit-runner", - "description": "A runtime program that takes one or more circuits, compiles them, executes them, and optionally applies measurement error mitigation.", - "max_execution_time": 14400, - "version": "1.0", - "parameters": [ - {"name": "circuits", "description": "A circuit or a list of circuits.", "type": "A QuantumCircuit or a list of QuantumCircuits.", "required": true}, - {"name": "shots", "description": "Number of repetitions of each circuit, for sampling. Default: 1024.", "type": "int", "required": false}, - {"name": "initial_layout", "description": "Initial position of virtual qubits on physical qubits.", "type": "dict or list", "required": false}, - {"name": "layout_method", "description": "Name of layout selection pass ('trivial', 'dense', 'noise_adaptive', 'sabre')", "type": "string", "required": false}, - {"name": "routing_method", "description": "Name of routing pass ('basic', 'lookahead', 'stochastic', 'sabre').", "type": "string", "required": false}, - {"name": "translation_method", "description": "Name of translation pass ('unroller', 'translator', 'synthesis').", "type": "string", "required": false}, - {"name": "seed_transpiler", "description": "Sets random seed for the stochastic parts of the transpiler.", "type": "int", "required": false}, - {"name": "optimization_level", "description": "How much optimization to perform on the circuits (0-3). Higher levels generate more optimized circuits. Default is 1.", "type": "int", "required": false}, - {"name": "init_qubits", "description": "Whether to reset the qubits to the ground state for each shot.", "type": "bool", "required": false}, - {"name": "rep_delay", "description": "Delay between programs in seconds.", "type": "float", "required": false}, - {"name": "transpiler_options", "description": "Additional compilation options.", "type": "dict", "required": false}, - {"name": "measurement_error_mitigation", "description": "Whether to apply measurement error mitigation. Default is False.", "type": "bool", "required": false} - ], - "return_values": [ - {"name": "-", "description": "Circuit execution results.", "type": "RunnerResult object"} - ] -} diff --git a/program_source/circuit_runner/circuit_runner.py b/program_source/circuit_runner/circuit_runner.py deleted file mode 100644 index 82a9aaf7dc..0000000000 --- a/program_source/circuit_runner/circuit_runner.py +++ /dev/null @@ -1,68 +0,0 @@ -# This code is part of qiskit-runtime. -# -# (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. - -"""Circuit-runner runtime program. - -This is a simplified version of the circuit-runner program. -""" - -from qiskit.compiler import transpile, schedule - - -def main( - backend, - user_messenger, # pylint: disable=unused-argument - circuits, - initial_layout=None, - seed_transpiler=None, - optimization_level=None, - transpiler_options=None, - scheduling_method=None, - schedule_circuit=False, - inst_map=None, - meas_map=None, - measurement_error_mitigation=False, - **kwargs, -): - """Run the circuits on the backend.""" - - # transpiling the circuits using given transpile options - transpiler_options = transpiler_options or {} - circuits = transpile( - circuits, - initial_layout=initial_layout, - seed_transpiler=seed_transpiler, - optimization_level=optimization_level, - backend=backend, - **transpiler_options, - ) - - if schedule_circuit: - circuits = schedule( - circuits=circuits, - backend=backend, - inst_map=inst_map, - meas_map=meas_map, - method=scheduling_method, - ) - - if not isinstance(circuits, list): - circuits = [circuits] - - # Compute raw results - result = backend.run(circuits, **kwargs).result() - - if measurement_error_mitigation: - # Performs measurement error mitigation. - pass - - return result.to_dict() diff --git a/program_source/version.py b/program_source/version.py deleted file mode 100644 index af762813ae..0000000000 --- a/program_source/version.py +++ /dev/null @@ -1,5 +0,0 @@ -# THIS FILE IS GENERATED FROM QISKIT_RUNTIME SETUP.PY -# pylint: disable=missing-module-docstring,invalid-name -short_version = "0.1.0" -version = "0.1.0.dev0+378fb03" -release = False diff --git a/qiskit_ibm_runtime/__init__.py b/qiskit_ibm_runtime/__init__.py index bb3963f490..b6a8a941ee 100644 --- a/qiskit_ibm_runtime/__init__.py +++ b/qiskit_ibm_runtime/__init__.py @@ -143,16 +143,6 @@ def result_callback(job_id, result): job = Sampler(backend).run(ReferenceCircuits.bell(), callback=result_callback) print(job.result()) -.. dropdown:: Uploading a program - :animate: fade-in-slide-down - - Authorized users can upload their custom Qiskit Runtime programs. - A Qiskit Runtime program is a piece of Python - code and its metadata that takes certain inputs, performs - quantum and maybe classical processing, and returns the results. - - Files related to writing a runtime program are in the - ``qiskit_ibm_runtime/program`` directory. Classes ========================== @@ -165,8 +155,6 @@ def result_callback(job_id, result): Session IBMBackend RuntimeJob - RuntimeProgram - ParameterNamespace RuntimeOptions RuntimeEncoder RuntimeDecoder @@ -177,7 +165,6 @@ def result_callback(job_id, result): from .qiskit_runtime_service import QiskitRuntimeService from .ibm_backend import IBMBackend from .runtime_job import RuntimeJob -from .runtime_program import RuntimeProgram, ParameterNamespace from .runtime_options import RuntimeOptions from .utils.json import RuntimeEncoder, RuntimeDecoder from .session import Session # pylint: disable=cyclic-import diff --git a/qiskit_ibm_runtime/api/clients/runtime.py b/qiskit_ibm_runtime/api/clients/runtime.py index f59708cff3..c45e38c771 100644 --- a/qiskit_ibm_runtime/api/clients/runtime.py +++ b/qiskit_ibm_runtime/api/clients/runtime.py @@ -47,74 +47,6 @@ def __init__( ) self._api = Runtime(self._session) - def list_programs(self, limit: int = None, skip: int = None) -> Dict[str, Any]: - """Return a list of runtime programs. - - Args: - limit: The number of programs to return. - skip: The number of programs to skip. - - Returns: - A list of runtime programs. - """ - return self._api.list_programs(limit, skip) - - def program_create( - self, - program_data: str, - name: str, - description: str, - max_execution_time: int, - is_public: Optional[bool] = False, - spec: Optional[Dict] = None, - ) -> Dict: - """Create a new program. - - Args: - name: Name of the program. - program_data: Program data (base64 encoded). - description: Program description. - max_execution_time: Maximum execution time. - is_public: Whether the program should be public. - spec: Backend requirements, parameters, interim results, return values, etc. - - Returns: - Server response. - """ - return self._api.create_program( - program_data=program_data, - name=name, - description=description, - max_execution_time=max_execution_time, - is_public=is_public, - spec=spec, - ) - - def program_get(self, program_id: str) -> Dict: - """Return a specific program. - - Args: - program_id: Program ID. - - Returns: - Program information. - """ - return self._api.program(program_id).get() - - def set_program_visibility(self, program_id: str, public: bool) -> None: - """Sets a program's visibility. - - Args: - program_id: Program ID. - public: If ``True``, make the program visible to all. - If ``False``, make the program visible to just your account. - - """ - if public: - self._api.program(program_id).make_public() - else: - self._api.program(program_id).make_private() - def program_run( self, program_id: str, @@ -168,44 +100,6 @@ def program_run( **hgp_dict, ) - def program_delete(self, program_id: str) -> None: - """Delete the specified program. - - Args: - program_id: Program ID. - """ - self._api.program(program_id).delete() - - def program_update( - self, - program_id: str, - program_data: str = None, - name: str = None, - description: str = None, - max_execution_time: int = None, - spec: Optional[Dict] = None, - ) -> None: - """Update a program. - - Args: - program_id: Program ID. - program_data: Program data (base64 encoded). - name: Name of the program. - description: Program description. - max_execution_time: Maximum execution time. - spec: Backend requirements, parameters, interim results, return values, etc. - """ - if program_data: - self._api.program(program_id).update_data(program_data) - - if any([name, description, max_execution_time, spec]): - self._api.program(program_id).update_metadata( - name=name, - description=description, - max_execution_time=max_execution_time, - spec=spec, - ) - def job_get(self, job_id: str, exclude_params: bool = None) -> Dict: """Get job data. diff --git a/qiskit_ibm_runtime/api/rest/program.py b/qiskit_ibm_runtime/api/rest/program.py deleted file mode 100644 index ce76bdeac8..0000000000 --- a/qiskit_ibm_runtime/api/rest/program.py +++ /dev/null @@ -1,106 +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. - -"""Program REST adapter.""" - -from typing import Dict, Any, Optional -from concurrent import futures - -from qiskit_ibm_provider.api.rest.base import RestAdapterBase -from ..session import RetrySession - - -class Program(RestAdapterBase): - """Rest adapter for program related endpoints.""" - - URL_MAP = { - "self": "", - "data": "/data", - "run": "/jobs", - "private": "/private", - "public": "/public", - } - - _executor = futures.ThreadPoolExecutor() - - def __init__(self, session: RetrySession, program_id: str, url_prefix: str = "") -> None: - """Job constructor. - - Args: - session: Session to be used in the adapter. - program_id: ID of the runtime program. - url_prefix: Prefix to use in the URL. - """ - super().__init__(session, "{}/programs/{}".format(url_prefix, program_id)) - - def get(self) -> Dict[str, Any]: - """Return program information. - - Returns: - JSON response. - """ - url = self.get_url("self") - return self.session.get(url).json() - - def make_public(self) -> None: - """Sets a runtime program's visibility to public.""" - url = self.get_url("public") - self.session.put(url) - - def make_private(self) -> None: - """Sets a runtime program's visibility to private.""" - url = self.get_url("private") - self.session.put(url) - - def delete(self) -> None: - """Delete this program.""" - url = self.get_url("self") - self.session.delete(url) - - def update_data(self, program_data: str) -> None: - """Update program data. - - Args: - program_data: Program data (base64 encoded). - """ - url = self.get_url("data") - self.session.put( - url, data=program_data, headers={"Content-Type": "application/octet-stream"} - ) - - def update_metadata( - self, - name: str = None, - description: str = None, - max_execution_time: int = None, - spec: Optional[Dict] = None, - ) -> None: - """Update program metadata. - - Args: - name: Name of the program. - description: Program description. - max_execution_time: Maximum execution time. - spec: Backend requirements, parameters, interim results, return values, etc. - """ - url = self.get_url("self") - payload: Dict = {} - if name: - payload["name"] = name - if description: - payload["description"] = description - if max_execution_time: - payload["cost"] = max_execution_time - if spec: - payload["spec"] = spec - - self.session.patch(url, json=payload) diff --git a/qiskit_ibm_runtime/api/rest/runtime.py b/qiskit_ibm_runtime/api/rest/runtime.py index 856a677f99..e34708f3a0 100644 --- a/qiskit_ibm_runtime/api/rest/runtime.py +++ b/qiskit_ibm_runtime/api/rest/runtime.py @@ -22,7 +22,6 @@ from qiskit_ibm_provider.utils import local_to_utc from .runtime_session import RuntimeSession -from .program import Program from ...utils import RuntimeEncoder from .cloud_backend import CloudBackend @@ -39,17 +38,6 @@ class Runtime(RestAdapterBase): "cloud_instance": "/instance", } - def program(self, program_id: str) -> "Program": - """Return an adapter for the program. - - Args: - program_id: ID of the program. - - Returns: - The program adapter. - """ - return Program(self.session, program_id) - def program_job(self, job_id: str) -> "ProgramJob": """Return an adapter for the job. @@ -72,59 +60,6 @@ def runtime_session(self, session_id: str) -> "RuntimeSession": """ return RuntimeSession(self.session, session_id) - def list_programs(self, limit: int = None, skip: int = None) -> Dict[str, Any]: - """Return a list of runtime programs. - - Args: - limit: The number of programs to return. - skip: The number of programs to skip. - - Returns: - A list of runtime programs. - """ - url = self.get_url("programs") - payload: Dict[str, int] = {} - if limit: - payload["limit"] = limit - if skip: - payload["offset"] = skip - return self.session.get(url, params=payload).json() - - def create_program( - self, - program_data: str, - name: str, - description: str, - max_execution_time: int, - is_public: Optional[bool] = False, - spec: Optional[Dict] = None, - ) -> Dict: - """Upload a new program. - - Args: - program_data: Program data (base64 encoded). - name: Name of the program. - description: Program description. - max_execution_time: Maximum execution time. - is_public: Whether the program should be public. - spec: Backend requirements, parameters, interim results, return values, etc. - - Returns: - JSON response. - """ - url = self.get_url("programs") - payload = { - "name": name, - "data": program_data, - "cost": max_execution_time, - "description": description, - "is_public": is_public, - } - if spec is not None: - payload["spec"] = spec - data = json.dumps(payload) - return self.session.post(url, data=data).json() - def program_run( self, program_id: str, diff --git a/qiskit_ibm_runtime/constants.py b/qiskit_ibm_runtime/constants.py index 3a5568cabe..6dc82872aa 100644 --- a/qiskit_ibm_runtime/constants.py +++ b/qiskit_ibm_runtime/constants.py @@ -14,7 +14,7 @@ from qiskit.providers.jobstatus import JobStatus -from .program.result_decoder import ResultDecoder +from .utils.result_decoder import ResultDecoder from .utils.estimator_result_decoder import EstimatorResultDecoder from .utils.sampler_result_decoder import SamplerResultDecoder from .utils.runner_result import RunnerResult diff --git a/qiskit_ibm_runtime/program/__init__.py b/qiskit_ibm_runtime/program/__init__.py deleted file mode 100644 index 8740e8fac0..0000000000 --- a/qiskit_ibm_runtime/program/__init__.py +++ /dev/null @@ -1,78 +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. - -""" -==================================================== -Runtime Programs (:mod:`qiskit_ibm_runtime.program`) -==================================================== - -.. currentmodule:: qiskit_ibm_runtime.program - -This package contains files to help you write your custom Qiskit Runtime programs. - -Only authorized users can upload their custom Qiskit Runtime programs. -A Qiskit Runtime program is a piece of Python -code and its metadata that takes certain inputs, performs -quantum and maybe classical processing, and returns the results. - -Each runtime program has both ``data`` and ``metadata``. Program data is -the Python code to be executed. Program metadata provides usage information, -such as program description, its inputs and outputs, and backend requirements. - -Each program data needs to have a ``main(backend, user_messenger, **kwargs)`` -method, which serves as the entry point to the program. The ``backend`` parameter -is a :class:`ProgramBackend` instance whose :meth:`ProgramBackend.run` method -can be used to submit circuits. The ``user_messenger`` is a :class:`UserMessenger` -instance whose :meth:`UserMessenger.publish` method can be used to publish interim and -final results. -See `qiskit_ibm_runtime/program/program_template.py` for a program data -template file. - -Each program metadata must include at least the program name, description, and -maximum execution time. You can find description of each metadata field in -the :meth:`QiskitRuntimeService.upload_program` method. Instead of passing in -the metadata fields individually, you can pass in a JSON file or a dictionary -to :meth:`QiskitRuntimeService.upload_program` via the ``metadata`` parameter. -`qiskit_ibm_runtime/program/program_metadata_sample.json` -is a sample file of program metadata. - -You can use the :meth:`QiskitRuntimeService.upload_program` to upload a program. -For example:: - - from qiskit_ibm_runtime import QiskitRuntimeService - - service = QiskitRuntimeService() - program_id = service.upload_program( - data="my_vqe.py", - metadata="my_vqe_metadata.json" - ) - -In the example above, the file ``my_vqe.py`` contains the program data, and -``my_vqe_metadata.json`` contains the program metadata. - -Method :meth:`QiskitRuntimeService.delete_program` allows you to delete a -program. - - -Classes -========================== -.. autosummary:: - :toctree: ../stubs/ - - ProgramBackend - UserMessenger - ResultDecoder -""" - -from .program_backend import ProgramBackend -from .user_messenger import UserMessenger -from .result_decoder import ResultDecoder diff --git a/qiskit_ibm_runtime/program/program_backend.py b/qiskit_ibm_runtime/program/program_backend.py deleted file mode 100644 index 6225dfdce9..0000000000 --- a/qiskit_ibm_runtime/program/program_backend.py +++ /dev/null @@ -1,57 +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. - -"""Base class for program backend.""" - -import logging -from typing import Union, List, Dict -from abc import abstractmethod, ABC - -from qiskit.providers.backend import BackendV1 as Backend -from qiskit.providers.job import JobV1 as Job -from qiskit.circuit import QuantumCircuit - -logger = logging.getLogger(__name__) - - -class ProgramBackend(Backend, ABC): - """Base class for a program backend. - - The ``main()`` function of your runtime program will receive an instance - of this class as the first parameter. You can then use the instance - to submit circuits to the target backend. - """ - - @abstractmethod - def run( - self, - circuits: Union[QuantumCircuit, List[QuantumCircuit]], - **run_config: Dict, - ) -> Job: - """Run on the backend. - - Runtime circuit execution is synchronous, and control will not go - back until the execution finishes. You can use the `timeout` parameter - to set a timeout value to wait for the execution to finish. Note that if - the execution times out, circuit execution results will not be available. - - Args: - circuits: An individual or a - list of :class:`~qiskit.circuits.QuantumCircuit` - to run on the backend. - **run_config: Extra arguments used to configure the run. - - Returns: - The job to be executed. - """ - # pylint: disable=arguments-differ - pass diff --git a/qiskit_ibm_runtime/program/program_metadata_sample.json b/qiskit_ibm_runtime/program/program_metadata_sample.json deleted file mode 100644 index 238fb96a3e..0000000000 --- a/qiskit_ibm_runtime/program/program_metadata_sample.json +++ /dev/null @@ -1,37 +0,0 @@ -{ - "name": "runtime-simple", - "description": "Simple runtime program used for testing.", - "max_execution_time": 300, - "spec": { - "backend_requirements": { - "min_num_qubits": 5 - }, - "parameters": { - "type": "object", - "properties": { - "iterations": { - "description": "Number of iterations to run. Each iteration generates and runs a random circuit.", - "type": "integer" - } - }, - "required": ["iterations"] - }, - "return_values": { - "type": "string", - "description": "A string that says 'Hello, World!'." - }, - "interim_results": { - "type": "object", - "properties": { - "iteration": { - "description": "Iteration number.", - "type": "integer" - }, - "counts": { - "description": "Histogram data of the circuit result.", - "type": "object" - } - } - } - } -} diff --git a/qiskit_ibm_runtime/program/program_template.py b/qiskit_ibm_runtime/program/program_template.py deleted file mode 100644 index a876c85f1f..0000000000 --- a/qiskit_ibm_runtime/program/program_template.py +++ /dev/null @@ -1,51 +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. -# pylint: disable=unused-argument -# pylint: disable=invalid-name - -"""Runtime program template. - -The ``main()`` method is the entry point of a runtime program. It takes a -:class:`ProgramBackend` and a :class:`UserMessenger` that can be used to -send circuits to the backend and messages to the user, respectively. -""" - -from typing import Any - -from qiskit_ibm_runtime.program import UserMessenger, ProgramBackend - - -def program(backend: ProgramBackend, user_messenger: UserMessenger, **kwargs): - """Function that does classical-quantum calculation.""" - # UserMessenger can be used to publish interim results. - user_messenger.publish("This is an interim result.") - return "final result" - - -def main(backend: ProgramBackend, user_messenger: UserMessenger, **kwargs) -> Any: - """This is the main entry point of a runtime program. - - The name of this method must not change. It also must have ``backend`` - and ``user_messenger`` as the first two positional arguments. - - Args: - backend: Backend for the circuits to run on. - user_messenger: Used to communicate with the program user. - **kwargs: User inputs. - - Returns: - The final result of the runtime program. - """ - # Massage the input if necessary. - result = program(backend, user_messenger, **kwargs) - # Final results can be directly returned - return result diff --git a/qiskit_ibm_runtime/program/user_messenger.py b/qiskit_ibm_runtime/program/user_messenger.py deleted file mode 100644 index e1aa712574..0000000000 --- a/qiskit_ibm_runtime/program/user_messenger.py +++ /dev/null @@ -1,46 +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. - -"""Base class for handling communication with program users.""" - -import json -from typing import Any, Type - -from ..utils.json import RuntimeEncoder - - -class UserMessenger: - """Base class for handling communication with program users. - - The ``main()`` function of your runtime program will receive an instance - of this class as the second parameter. You can then use the instance - to send results back to the program user. - """ - - def publish( - self, - message: Any, - encoder: Type[json.JSONEncoder] = RuntimeEncoder, - ) -> None: - """Publish message. - - You can use this method to publish messages, such as interim and final results, - to the program user. The messages will be made immediately available to the user, - but they may choose not to receive the messages. - - Args: - message: Message to be published. Can be any type. - encoder: An optional JSON encoder for serializing - """ - # pylint: disable=unused-argument - # Default implementation for testing. - print(json.dumps(message, cls=encoder)) diff --git a/qiskit_ibm_runtime/qiskit_runtime_service.py b/qiskit_ibm_runtime/qiskit_runtime_service.py index 6ef274fad7..8129821302 100644 --- a/qiskit_ibm_runtime/qiskit_runtime_service.py +++ b/qiskit_ibm_runtime/qiskit_runtime_service.py @@ -43,15 +43,13 @@ from .exceptions import IBMNotAuthorizedError, IBMInputValueError, IBMAccountError from .exceptions import ( IBMRuntimeError, - RuntimeDuplicateProgramError, RuntimeProgramNotFound, RuntimeJobNotFound, ) from .hub_group_project import HubGroupProject # pylint: disable=cyclic-import -from .program.result_decoder import ResultDecoder +from .utils.result_decoder import ResultDecoder from .runtime_job import RuntimeJob -from .runtime_program import RuntimeProgram, ParameterNamespace -from .utils import RuntimeDecoder, to_base64_string, to_python_identifier +from .utils import RuntimeDecoder, to_python_identifier from .api.client_parameters import ClientParameters from .runtime_options import RuntimeOptions from .ibm_backend import IBMBackend @@ -196,7 +194,6 @@ def __init__( self._channel_strategy = channel_strategy or self._account.channel_strategy self._channel = self._account.channel - self._programs: Dict[str, RuntimeProgram] = {} self._backends: Dict[str, "ibm_backend.IBMBackend"] = {} self._backend_configs: Dict[str, Any] = {} @@ -814,182 +811,10 @@ def backend( def get_backend(self, name: str = None, **kwargs: Any) -> Backend: return self.backend(name, **kwargs) - def pprint_programs( - self, - refresh: bool = False, - detailed: bool = False, - limit: int = 20, - skip: int = 0, - ) -> None: - """Pretty print information about available runtime programs. - - Args: - refresh: If ``True``, re-query the server for the programs. Otherwise - return the cached value. - detailed: If ``True`` print all details about available runtime programs. - limit: The number of programs returned at a time. Default and maximum - value of 20. - skip: The number of programs to skip. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - programs = self.programs(refresh, limit, skip) - for prog in programs: - print("=" * 50) - if detailed: - print(str(prog)) - else: - print( - f"{prog.program_id}:", - ) - print(f" Name: {prog.name}") - print(f" Description: {prog.description}") - - def programs( - self, refresh: bool = False, limit: int = 20, skip: int = 0 - ) -> List[RuntimeProgram]: - """Return available runtime programs. - - Currently only program metadata is returned. - - Args: - refresh: If ``True``, re-query the server for the programs. Otherwise - return the cached value. - limit: The number of programs returned at a time. ``None`` means no limit. - skip: The number of programs to skip. - - Returns: - A list of runtime programs. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - if skip is None: - skip = 0 - if not self._programs or refresh: - self._programs = {} - current_page_limit = 20 - offset = 0 - while True: - response = self._api_client.list_programs(limit=current_page_limit, skip=offset) - program_page = response.get("programs", []) - # count is the total number of programs that would be returned if - # there was no limit or skip - count = response.get("count", 0) - if limit is None: - limit = count - for prog_dict in program_page: - program = self._to_program(prog_dict) - self._programs[program.program_id] = program - num_cached_programs = len(self._programs) - if num_cached_programs == count or num_cached_programs >= (limit + skip): - # Stop if there are no more programs returned by the server or - # if the number of cached programs is greater than the sum of limit and skip - break - offset += len(program_page) - if limit is None: - limit = len(self._programs) - return list(self._programs.values())[skip : limit + skip] - - def program(self, program_id: str, refresh: bool = False) -> RuntimeProgram: - """Retrieve a runtime program. - - Currently only program metadata is returned. - - Args: - program_id: Program ID. - refresh: If ``True``, re-query the server for the program. Otherwise - return the cached value. - - Returns: - Runtime program. - - Raises: - RuntimeProgramNotFound: If the program does not exist. - IBMRuntimeError: If the request failed. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - if program_id not in self._programs or refresh: - try: - response = self._api_client.program_get(program_id) - except RequestsApiError as ex: - if ex.status_code == 404: - raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None - raise IBMRuntimeError(f"Failed to get program: {ex}") from None - - self._programs[program_id] = self._to_program(response) - - return self._programs[program_id] - - def _to_program(self, response: Dict) -> RuntimeProgram: - """Convert server response to ``RuntimeProgram`` instances. - - Args: - response: Server response. - - Returns: - A ``RuntimeProgram`` instance. - """ - backend_requirements = {} - parameters = {} - return_values = {} - interim_results = {} - if "spec" in response: - backend_requirements = response["spec"].get("backend_requirements", {}) - parameters = response["spec"].get("parameters", {}) - return_values = response["spec"].get("return_values", {}) - interim_results = response["spec"].get("interim_results", {}) - - return RuntimeProgram( - program_name=response["name"], - program_id=response["id"], - description=response.get("description", ""), - parameters=parameters, - return_values=return_values, - interim_results=interim_results, - max_execution_time=response.get("cost", 0), - creation_date=response.get("creation_date", ""), - update_date=response.get("update_date", ""), - backend_requirements=backend_requirements, - is_public=response.get("is_public", False), - data=response.get("data", ""), - api_client=self._api_client, - ) - def run( self, program_id: str, - inputs: Union[Dict, ParameterNamespace], + inputs: Dict, options: Optional[Union[RuntimeOptions, Dict]] = None, callback: Optional[Callable] = None, result_decoder: Optional[Union[Type[ResultDecoder], Sequence[Type[ResultDecoder]]]] = None, @@ -1026,30 +851,12 @@ def run( RuntimeProgramNotFound: If the program cannot be found. IBMRuntimeError: An error occurred running the program. """ - if program_id not in ["sampler", "estimator", "circuit-runner", "qasm3-runner"]: - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) qrt_options: RuntimeOptions = options if options is None: qrt_options = RuntimeOptions() elif isinstance(options, Dict): qrt_options = RuntimeOptions(**options) - # If using params object, extract as dictionary. - if isinstance(inputs, ParameterNamespace): - inputs.validate() - inputs = vars(inputs) - qrt_options.validate(channel=self.channel) hgp_name = None @@ -1066,7 +873,6 @@ def run( warnings.warn( f"The backend {backend.name} currently has a status of {status.status_msg}." ) - try: response = self._api_client.program_run( program_id=program_id, @@ -1113,282 +919,6 @@ def run( ) return job - def upload_program(self, data: str, metadata: Optional[Union[Dict, str]] = None) -> str: - """Upload a runtime program. - - In addition to program data, the following program metadata is also - required: - - - name - - max_execution_time - - Program metadata can be specified using the `metadata` parameter or - individual parameter (for example, `name` and `description`). If the - same metadata field is specified in both places, the individual parameter - takes precedence. For example, if you specify:: - - upload_program(metadata={"name": "name1"}, name="name2") - - ``name2`` will be used as the program name. - - Args: - data: Program data or path of the file containing program data to upload. - metadata: Name of the program metadata file or metadata dictionary. - A metadata file needs to be in the JSON format. The ``parameters``, - ``return_values``, and ``interim_results`` should be defined as JSON Schema. - See :file:`program/program_metadata_sample.json` for an example. The - fields in metadata are explained below. - - * name: Name of the program. Required. - * max_execution_time: Maximum execution time in seconds. Required. - * description: Program description. - * is_public: Whether the runtime program should be visible to the public. - The default is ``False``. - * spec: Specifications for backend characteristics and input parameters - required to run the program, interim results and final result. - - * backend_requirements: Backend requirements. - * parameters: Program input parameters in JSON schema format. - * return_values: Program return values in JSON schema format. - * interim_results: Program interim results in JSON schema format. - - Returns: - Program ID. - - Raises: - IBMInputValueError: If required metadata is missing. - RuntimeDuplicateProgramError: If a program with the same name already exists. - IBMNotAuthorizedError: If you are not authorized to upload programs. - IBMRuntimeError: If the upload failed. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - program_metadata = self._read_metadata(metadata=metadata) - - for req in ["name", "max_execution_time"]: - if req not in program_metadata or not program_metadata[req]: - raise IBMInputValueError(f"{req} is a required metadata field.") - - if "def main(" not in data: - # This is the program file - with open(data, "r", encoding="utf-8") as file: - data = file.read() - - try: - program_data = to_base64_string(data) - response = self._api_client.program_create( - program_data=program_data, **program_metadata - ) - except RequestsApiError as ex: - if ex.status_code == 409: - raise RuntimeDuplicateProgramError( - "Program with the same name already exists." - ) from None - if ex.status_code == 403: - raise IBMNotAuthorizedError("You are not authorized to upload programs.") from None - raise IBMRuntimeError(f"Failed to create program: {ex}") from None - return response["id"] - - def _read_metadata(self, metadata: Optional[Union[Dict, str]] = None) -> Dict: - """Read metadata. - - Args: - metadata: Name of the program metadata file or metadata dictionary. - - Returns: - Return metadata. - """ - upd_metadata: dict = {} - if metadata is not None: - if isinstance(metadata, str): - with open(metadata, "r", encoding="utf-8") as file: - upd_metadata = json.load(file) - else: - upd_metadata = metadata - # TODO validate metadata format - metadata_keys = [ - "name", - "max_execution_time", - "description", - "spec", - "is_public", - ] - return {key: val for key, val in upd_metadata.items() if key in metadata_keys} - - def update_program( - self, - program_id: str, - data: str = None, - metadata: Optional[Union[Dict, str]] = None, - name: str = None, - description: str = None, - max_execution_time: int = None, - spec: Optional[Dict] = None, - ) -> None: - """Update a runtime program. - - Program metadata can be specified using the `metadata` parameter or - individual parameters, such as `name` and `description`. If the - same metadata field is specified in both places, the individual parameter - takes precedence. - - Args: - program_id: Program ID. - data: Program data or path of the file containing program data to upload. - metadata: Name of the program metadata file or metadata dictionary. - name: New program name. - description: New program description. - max_execution_time: New maximum execution time. - spec: New specifications for backend characteristics, input parameters, - interim results and final result. - - Raises: - RuntimeProgramNotFound: If the program doesn't exist. - IBMRuntimeError: If the request failed. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - if not any([data, metadata, name, description, max_execution_time, spec]): - warnings.warn( - "None of the 'data', 'metadata', 'name', 'description', " - "'max_execution_time', or 'spec' parameters is specified. " - "No update is made." - ) - return - - if data: - if "def main(" not in data: - # This is the program file - with open(data, "r", encoding="utf-8") as file: - data = file.read() - data = to_base64_string(data) - - if metadata: - metadata = self._read_metadata(metadata=metadata) - combined_metadata = self._merge_metadata( - metadata=metadata, - name=name, - description=description, - max_execution_time=max_execution_time, - spec=spec, - ) - - try: - self._api_client.program_update(program_id, program_data=data, **combined_metadata) - except RequestsApiError as ex: - if ex.status_code == 404: - raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None - raise IBMRuntimeError(f"Failed to update program: {ex}") from None - - if program_id in self._programs: - program = self._programs[program_id] - program._refresh() - - def _merge_metadata(self, metadata: Optional[Dict] = None, **kwargs: Any) -> Dict: - """Merge multiple copies of metadata. - Args: - metadata: Program metadata. - **kwargs: Additional metadata fields to overwrite. - Returns: - Merged metadata. - """ - merged = {} - metadata = metadata or {} - metadata_keys = ["name", "max_execution_time", "description", "spec"] - for key in metadata_keys: - if kwargs.get(key, None) is not None: - merged[key] = kwargs[key] - elif key in metadata.keys(): - merged[key] = metadata[key] - return merged - - def delete_program(self, program_id: str) -> None: - """Delete a runtime program. - - Args: - program_id: Program ID. - - Raises: - RuntimeProgramNotFound: If the program doesn't exist. - IBMRuntimeError: If the request failed. - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - try: - self._api_client.program_delete(program_id=program_id) - except RequestsApiError as ex: - if ex.status_code == 404: - raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None - raise IBMRuntimeError(f"Failed to delete program: {ex}") from None - - if program_id in self._programs: - del self._programs[program_id] - - def set_program_visibility(self, program_id: str, public: bool) -> None: - """Sets a program's visibility. - - Args: - program_id: Program ID. - public: If ``True``, make the program visible to all. - If ``False``, make the program visible to just your account. - - Raises: - RuntimeProgramNotFound: if program not found (404) - IBMRuntimeError: if update failed (401, 403) - """ - warnings.warn( - ( - "Custom programs are being deprecated as of qiskit-ibm-runtime 0.14.0 and will " - "be removed on November 27, 2023. You can instead convert your custom programs " - "to use Qiskit Runtime primitives with Quantum Serverless. Refer to the migration " - "guide for instructions: " - "https://qiskit-extensions.github.io/quantum-serverless/migration" - "/migration_from_qiskit_runtime_programs.html" - ), - DeprecationWarning, - stacklevel=2, - ) - try: - self._api_client.set_program_visibility(program_id, public) - except RequestsApiError as ex: - if ex.status_code == 404: - raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None - raise IBMRuntimeError(f"Failed to set program visibility: {ex}") from None - - if program_id in self._programs: - program = self._programs[program_id] - program._is_public = public - def job(self, job_id: str) -> RuntimeJob: """Retrieve a runtime job. diff --git a/qiskit_ibm_runtime/runtime_job.py b/qiskit_ibm_runtime/runtime_job.py index 297933ef6c..7606865d06 100644 --- a/qiskit_ibm_runtime/runtime_job.py +++ b/qiskit_ibm_runtime/runtime_job.py @@ -40,7 +40,7 @@ RuntimeJobTimeoutError, RuntimeJobMaxTimeoutError, ) -from .program.result_decoder import ResultDecoder +from .utils.result_decoder import ResultDecoder from .api.clients import RuntimeClient, RuntimeWebsocketClient, WebsocketClientCloseCode from .exceptions import IBMError from .api.exceptions import RequestsApiError diff --git a/qiskit_ibm_runtime/runtime_program.py b/qiskit_ibm_runtime/runtime_program.py deleted file mode 100644 index a21063a904..0000000000 --- a/qiskit_ibm_runtime/runtime_program.py +++ /dev/null @@ -1,428 +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. - -"""Qiskit runtime program.""" - -import logging -import re -from typing import Optional, Dict -from types import SimpleNamespace -from qiskit_ibm_runtime.exceptions import IBMInputValueError, IBMNotAuthorizedError -from .exceptions import IBMRuntimeError, RuntimeProgramNotFound -from .api.clients.runtime import RuntimeClient -from .api.exceptions import RequestsApiError - -logger = logging.getLogger(__name__) - - -class RuntimeProgram: - """Class representing program metadata. - - This class contains the metadata describing a program, such as its - name, ID, description, etc. - - You can use the :class:`~qiskit_ibm_runtime.QiskitRuntimeService` - to retrieve the metadata of a specific program or all programs. For example:: - - from qiskit_ibm_runtime import QiskitRuntimeService - - service = QiskitRuntimeService() - - # To retrieve metadata of all programs. - programs = service.programs() - - # To retrieve metadata of a single program. - program = service.program(program_id='sampler') - print(f"Program {program.name} takes parameters {program.parameters().metadata}") - """ - - def __init__( - self, - program_name: str, - program_id: str, - description: str, - parameters: Optional[Dict] = None, - return_values: Optional[Dict] = None, - interim_results: Optional[Dict] = None, - max_execution_time: int = 0, - backend_requirements: Optional[Dict] = None, - creation_date: str = "", - update_date: str = "", - is_public: Optional[bool] = False, - data: str = "", - api_client: Optional[RuntimeClient] = None, - ) -> None: - """RuntimeProgram constructor. - - Args: - program_name: Program name. - program_id: Program ID. - description: Program description. - parameters: Documentation on program parameters. - return_values: Documentation on program return values. - interim_results: Documentation on program interim results. - max_execution_time: Maximum execution time. - backend_requirements: Backend requirements. - creation_date: Program creation date. - update_date: Program last updated date. - is_public: ``True`` if program is visible to all. ``False`` if it's only visible to you. - data: Program data. - api_client: Runtime api client. - """ - self._name = program_name - self._id = program_id - self._description = description - self._max_execution_time = max_execution_time - self._backend_requirements = backend_requirements or {} - self._parameters = parameters or {} - self._return_values = return_values or {} - self._interim_results = interim_results or {} - self._creation_date = creation_date - self._update_date = update_date - self._is_public = is_public - self._data = data - self._api_client = api_client - - def __str__(self) -> str: - def _format_common(schema: Dict) -> None: - """Add title, description and property details to `formatted`.""" - if "description" in schema: - formatted.append(" " * 4 + "Description: {}".format(schema["description"])) - if "type" in schema: - formatted.append(" " * 4 + "Type: {}".format(str(schema["type"]))) - if "properties" in schema: - formatted.append(" " * 4 + "Properties:") - for property_name, property_value in schema["properties"].items(): - formatted.append(" " * 8 + "- " + property_name + ":") - for key, value in property_value.items(): - formatted.append( - " " * 12 + "{}: {}".format(camel_to_sentence_case(key), str(value)) - ) - formatted.append( - " " * 12 + "Required: " + str(property_name in schema.get("required", [])) - ) - - def _format_backend_requirements(schema: Dict) -> None: - """Add backend requirements details to `formatted`.""" - if "min_num_qubits" in schema: - formatted.append( - " " * 4 + "Minimum number of qubits: {}".format(str(schema["min_num_qubits"])) - ) - for key, value in schema.items(): - if key not in ["min_num_qubits"]: - formatted.append( - " " * 4 + "{}: {}".format(snake_to_sentence_case(key), str(value)) - ) - - def snake_to_sentence_case(snake_case_text: str) -> str: - """Converts snake_case to Sentence case""" - snake_case_words = snake_case_text.split("_") - return camel_to_sentence_case( - snake_case_words[0] + "".join(x.title() for x in snake_case_words[1:]) - ) - - def camel_to_sentence_case(camel_case_text: str) -> str: - """Converts camelCase to Sentence case""" - if camel_case_text == "": - return camel_case_text - sentence_case_text = re.sub("([A-Z])", r" \1", camel_case_text) - return sentence_case_text[:1].upper() + sentence_case_text[1:].lower() - - formatted = [ - f"{self.program_id}:", - f" Name: {self.name}", - f" Description: {self.description}", - f" Creation date: {self.creation_date}", - f" Update date: {self.update_date}", - f" Max execution time: {self.max_execution_time}", - ] - - formatted.append(" Backend requirements:") - if self._backend_requirements: - _format_backend_requirements(self._backend_requirements) - else: - formatted.append(" " * 4 + "none") - - formatted.append(" Input parameters:") - if self._parameters: - _format_common(self._parameters) - else: - formatted.append(" " * 4 + "none") - - formatted.append(" Interim results:") - if self._interim_results: - _format_common(self._interim_results) - else: - formatted.append(" " * 4 + "none") - - formatted.append(" Returns:") - if self._return_values: - _format_common(self._return_values) - else: - formatted.append(" " * 4 + "none") - return "\n".join(formatted) - - def to_dict(self) -> Dict: - """Convert program metadata to dictionary format. - - Returns: - Program metadata in dictionary format. - """ - return { - "program_id": self.program_id, - "name": self.name, - "description": self.description, - "max_execution_time": self.max_execution_time, - "backend_requirements": self.backend_requirements, - "parameters": self.parameters(), - "return_values": self.return_values, - "interim_results": self.interim_results, - "is_public": self._is_public, - } - - def parameters(self) -> "ParameterNamespace": - """Program parameter namespace. - - You can use the returned namespace to assign parameter values and pass - the namespace to :meth:`qiskit_ibm_runtime.QiskitRuntimeService.run`. - The namespace allows you to use auto-completion to find program parameters. - - Note that each call to this method returns a new namespace instance and - does not include any modification to the previous instance. - - Returns: - Program parameter namespace. - """ - return ParameterNamespace(self._parameters) - - @property - def program_id(self) -> str: - """Program ID. - - Returns: - Program ID. - """ - return self._id - - @property - def name(self) -> str: - """Program name. - - Returns: - Program name. - """ - return self._name - - @property - def description(self) -> str: - """Program description. - - Returns: - Program description. - """ - return self._description - - @property - def return_values(self) -> Dict: - """Program return value definitions. - - Returns: - Return value definitions for this program. - """ - return self._return_values - - @property - def interim_results(self) -> Dict: - """Program interim result definitions. - - Returns: - Interim result definitions for this program. - """ - return self._interim_results - - @property - def max_execution_time(self) -> int: - """Maximum execution time in seconds. - - A program execution exceeding this time will be forcibly terminated. - - Returns: - Maximum execution time. - """ - return self._max_execution_time - - @property - def backend_requirements(self) -> Dict: - """Backend requirements. - - Returns: - Backend requirements for this program. - """ - return self._backend_requirements - - @property - def creation_date(self) -> str: - """Program creation date. - - Returns: - Program creation date. - """ - return self._creation_date - - @property - def update_date(self) -> str: - """Program last updated date. - - Returns: - Program last updated date. - """ - return self._update_date - - @property - def is_public(self) -> bool: - """Whether the program is visible to all. - - Returns: - Whether the program is public. - """ - return self._is_public - - @property - def data(self) -> str: - """Program data. - - Returns: - Program data. - - Raises: - IBMNotAuthorizedError: if user is not the program author. - """ - if not self._data: - self._refresh() - if not self._data: - raise IBMNotAuthorizedError( - "Only program authors are authorized to retrieve program data" - ) - return self._data - - def _refresh(self) -> None: - """Refresh program data and metadata - - Raises: - RuntimeProgramNotFound: If the program does not exist. - IBMRuntimeError: If the request failed. - """ - try: - response = self._api_client.program_get(self._id) - except RequestsApiError as ex: - if ex.status_code == 404: - raise RuntimeProgramNotFound(f"Program not found: {ex.message}") from None - raise IBMRuntimeError(f"Failed to get program: {ex}") from None - self._backend_requirements = {} - self._parameters = {} - self._return_values = {} - self._interim_results = {} - if "spec" in response: - self._backend_requirements = response["spec"].get("backend_requirements", {}) - self._parameters = response["spec"].get("parameters", {}) - self._return_values = response["spec"].get("return_values", {}) - self._interim_results = response["spec"].get("interim_results", {}) - self._name = response["name"] - self._id = response["id"] - self._description = response.get("description", "") - self._max_execution_time = response.get("cost", 0) - self._creation_date = response.get("creation_date", "") - self._update_date = response.get("update_date", "") - self._is_public = response.get("is_public", False) - self._data = response.get("data", "") - - def __repr__(self) -> str: - return f"<{self.__class__.__name__}('{self._id}')>" - - -class ParameterNamespace(SimpleNamespace): - """A namespace for program parameters with validation. - - This class provides a namespace for program parameters with auto-completion - and validation support. - """ - - def __init__(self, parameters: Dict): - """ParameterNamespace constructor. - - Args: - parameters: The program's input parameters. - """ - super().__init__() - # Allow access to the raw program parameters dict - self.__metadata = parameters - # For localized logic, create store of parameters in dictionary - self.__program_params: dict = {} - - for parameter_name, parameter_value in parameters.get("properties", {}).items(): - # (1) Add parameters to a dict by name - setattr(self, parameter_name, None) - # (2) Store the program params for validation - self.__program_params[parameter_name] = parameter_value - - @property - def metadata(self) -> Dict: - """Returns the parameter metadata""" - return self.__metadata - - def validate(self) -> None: - """Validate program input values. - - Note: - This method only verifies that required parameters have values. It - does not fail the validation if the namespace has extraneous parameters. - - Raises: - IBMInputValueError: if validation fails - """ - - # Iterate through the user's stored inputs - for parameter_name, _ in self.__program_params.items(): - # Set invariants: User-specified parameter value (value) and if it's required (req) - value = getattr(self, parameter_name, None) - # Check there exists a program parameter of that name. - if value is None and parameter_name in self.metadata.get("required", []): - raise IBMInputValueError("Param (%s) missing required value!" % parameter_name) - - def __str__(self) -> str: - """Creates string representation of object""" - # Header - header = "| {:10.10} | {:12.12} | {:12.12} " "| {:8.8} | {:>15} |".format( - "Name", "Value", "Type", "Required", "Description" - ) - params_str = "\n".join( - [ - "| {:10.10} | {:12.12} | {:12.12}| {:8.8} | {:>15} |".format( - parameter_name, - str(getattr(self, parameter_name, "None")), - str(parameter_value.get("type", "None")), - str(parameter_name in self.metadata.get("required", [])), - str(parameter_value.get("description", "None")), - ) - for parameter_name, parameter_value in self.__program_params.items() - ] - ) - - return "ParameterNamespace (Values):\n%s\n%s\n%s" % ( - header, - "-" * len(header), - params_str, - ) - - def to_dict(self) -> Dict: - """Convert to dictionary.""" - return self.__program_params diff --git a/qiskit_ibm_runtime/session.py b/qiskit_ibm_runtime/session.py index a1a6b739ca..c9bdc75f19 100644 --- a/qiskit_ibm_runtime/session.py +++ b/qiskit_ibm_runtime/session.py @@ -20,8 +20,7 @@ from qiskit_ibm_runtime import QiskitRuntimeService from .runtime_job import RuntimeJob -from .runtime_program import ParameterNamespace -from .program.result_decoder import ResultDecoder +from .utils.result_decoder import ResultDecoder from .ibm_backend import IBMBackend from .utils.default_session import set_cm_session from .utils.deprecation import deprecate_arguments @@ -128,7 +127,7 @@ def __init__( def run( self, program_id: str, - inputs: Union[Dict, ParameterNamespace], + inputs: Dict, options: Optional[Dict] = None, callback: Optional[Callable] = None, result_decoder: Optional[Type[ResultDecoder]] = None, diff --git a/qiskit_ibm_runtime/utils/estimator_result_decoder.py b/qiskit_ibm_runtime/utils/estimator_result_decoder.py index b4423d14c8..9046b49500 100644 --- a/qiskit_ibm_runtime/utils/estimator_result_decoder.py +++ b/qiskit_ibm_runtime/utils/estimator_result_decoder.py @@ -17,7 +17,7 @@ from qiskit.primitives import EstimatorResult -from ..program.result_decoder import ResultDecoder +from .result_decoder import ResultDecoder class EstimatorResultDecoder(ResultDecoder): diff --git a/qiskit_ibm_runtime/program/result_decoder.py b/qiskit_ibm_runtime/utils/result_decoder.py similarity index 100% rename from qiskit_ibm_runtime/program/result_decoder.py rename to qiskit_ibm_runtime/utils/result_decoder.py diff --git a/qiskit_ibm_runtime/utils/runner_result.py b/qiskit_ibm_runtime/utils/runner_result.py index a1d8455c5b..02af5b3ba4 100644 --- a/qiskit_ibm_runtime/utils/runner_result.py +++ b/qiskit_ibm_runtime/utils/runner_result.py @@ -19,7 +19,7 @@ from qiskit.result.postprocess import _hex_to_bin from qiskit.exceptions import QiskitError -from ..program import ResultDecoder +from .result_decoder import ResultDecoder from .json import RuntimeDecoder diff --git a/qiskit_ibm_runtime/utils/sampler_result_decoder.py b/qiskit_ibm_runtime/utils/sampler_result_decoder.py index d84a020eb3..626f45ee4b 100644 --- a/qiskit_ibm_runtime/utils/sampler_result_decoder.py +++ b/qiskit_ibm_runtime/utils/sampler_result_decoder.py @@ -18,7 +18,7 @@ from qiskit.result import QuasiDistribution from qiskit.primitives import SamplerResult -from ..program.result_decoder import ResultDecoder +from .result_decoder import ResultDecoder class SamplerResultDecoder(ResultDecoder): diff --git a/releasenotes/notes/remove-custom-programs-aea76f4dd19cd481.yaml b/releasenotes/notes/remove-custom-programs-aea76f4dd19cd481.yaml new file mode 100644 index 0000000000..8823cfb37d --- /dev/null +++ b/releasenotes/notes/remove-custom-programs-aea76f4dd19cd481.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Methods related to using custom programs are removed. diff --git a/test/ibm_test_case.py b/test/ibm_test_case.py index f58158a221..c724ae1138 100644 --- a/test/ibm_test_case.py +++ b/test/ibm_test_case.py @@ -13,7 +13,6 @@ """Custom TestCase for IBM Provider.""" import os -import copy import logging import inspect import warnings @@ -29,7 +28,6 @@ from .utils import setup_test_logging from .decorators import IntegrationTestDependencies, integration_test_setup -from .templates import RUNTIME_PROGRAM, RUNTIME_PROGRAM_METADATA, PROGRAM_PREFIX class IBMTestCase(BaseQiskitTestCase): @@ -107,25 +105,6 @@ def tearDown(self) -> None: with suppress(Exception): service.delete_job(job.job_id()) - def _upload_program( - self, - service: QiskitRuntimeService, - name: str = None, - max_execution_time: int = 300, - data: str = None, - is_public: bool = False, - ) -> str: - """Upload a new program.""" - name = name or PROGRAM_PREFIX - data = data or RUNTIME_PROGRAM - metadata = copy.deepcopy(RUNTIME_PROGRAM_METADATA) - metadata["name"] = name - metadata["max_execution_time"] = max_execution_time - metadata["is_public"] = is_public - program_id = service.upload_program(data=data, metadata=metadata) - self.to_delete[service.channel].append(program_id) - return program_id - class IBMIntegrationJobTestCase(IBMIntegrationTestCase): """Custom integration test case for job-related tests.""" diff --git a/test/integration/test_job.py b/test/integration/test_job.py index 444d420f23..697d8aeffd 100644 --- a/test/integration/test_job.py +++ b/test/integration/test_job.py @@ -25,7 +25,6 @@ RuntimeJobFailureError, RuntimeInvalidStateError, RuntimeJobNotFound, - RuntimeJobMaxTimeoutError, ) from ..ibm_test_case import IBMIntegrationJobTestCase from ..decorators import run_integration_test, production_only, quantum_only @@ -96,40 +95,6 @@ def test_run_program_failed(self, service): job.result() self.assertIn("KeyError", str(err_cm.exception)) - @unittest.skip("Custom programs not currently supported.") - @run_integration_test - def test_run_program_failed_ran_too_long(self, service): - """Test a program that failed since it ran longer than maximum execution time.""" - max_execution_time = 60 - inputs = {"iterations": 1, "sleep_per_iteration": 61} - program_id = self._upload_program(service, max_execution_time=max_execution_time) - job = self._run_program(service, program_id=program_id, inputs=inputs) - - job.wait_for_final_state() - job_result_raw = service._api_client.job_results(job.job_id()) - self.assertEqual(JobStatus.ERROR, job.status()) - self.assertIn( - API_TO_JOB_ERROR_MESSAGE["CANCELLED - RAN TOO LONG"].format( - job.job_id(), job_result_raw - ), - job.error_message(), - ) - with self.assertRaises(RuntimeJobMaxTimeoutError): - job.result() - - @unittest.skip("Custom programs not currently supported.") - @run_integration_test - def test_run_program_override_max_execution_time(self, service): - """Test that the program max execution time is overridden.""" - program_max_execution_time = 400 - job_max_execution_time = 350 - program_id = self._upload_program(service, max_execution_time=program_max_execution_time) - job = self._run_program( - service, program_id=program_id, max_execution_time=job_max_execution_time - ) - job.wait_for_final_state() - self.assertEqual(job._api_client.job_get(job.job_id())["cost"], job_max_execution_time) - @run_integration_test @production_only def test_cancel_job_queued(self, service): diff --git a/test/integration/test_program.py b/test/integration/test_program.py deleted file mode 100644 index 380cd7c6b4..0000000000 --- a/test/integration/test_program.py +++ /dev/null @@ -1,220 +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 runtime service.""" - -import os -import tempfile -import unittest - -from qiskit_ibm_runtime.exceptions import IBMNotAuthorizedError -from qiskit_ibm_runtime.exceptions import ( - RuntimeProgramNotFound, -) -from qiskit_ibm_runtime.runtime_program import RuntimeProgram -from ..ibm_test_case import IBMIntegrationTestCase -from ..decorators import run_integration_test, quantum_only -from ..templates import RUNTIME_PROGRAM, PROGRAM_PREFIX - - -class TestIntegrationProgram(IBMIntegrationTestCase): - """Integration tests for runtime modules.""" - - @run_integration_test - @quantum_only - def test_list_programs(self, service): - """Test listing programs.""" - program_id = self._upload_program(service) - programs = service.programs() - self.assertTrue(programs) - found = False - for prog in programs: - self._validate_program(prog) - if prog.program_id == program_id: - found = True - self.assertTrue(found, f"Program {program_id} not found!") - - @run_integration_test - @quantum_only - def test_list_programs_with_limit_skip(self, service): - """Test listing programs with limit and skip.""" - for _ in range(4): - self._upload_program(service) - programs = service.programs(limit=3, refresh=True) - all_ids = [prog.program_id for prog in programs] - self.assertEqual(len(all_ids), 3, f"Retrieved programs: {all_ids}") - programs = service.programs(limit=2, skip=1) - some_ids = [prog.program_id for prog in programs] - self.assertEqual(len(some_ids), 2, f"Retrieved programs: {some_ids}") - self.assertNotIn(all_ids[0], some_ids) - self.assertIn(all_ids[1], some_ids) - self.assertIn(all_ids[2], some_ids) - - @run_integration_test - @quantum_only - def test_list_program(self, service): - """Test listing a single program.""" - program_id = self._upload_program(service) - program = service.program(program_id) - self.assertEqual(program_id, program.program_id) - self._validate_program(program) - - @run_integration_test - @quantum_only - def test_retrieve_program_data(self, service): - """Test retrieving program data""" - program_id = self._upload_program(service) - program = service.program(program_id) - self.assertEqual(RUNTIME_PROGRAM, program.data) - self._validate_program(program) - - @run_integration_test - def test_retrieve_unauthorized_program_data(self, service): - """Test retrieving program data when user is not the program author""" - programs = service.programs() - not_mine = None - for prog in programs: - if prog.is_public: - not_mine = prog - break - if not_mine is None: - self.skipTest("Cannot find a program that's not mine!") - with self.assertRaises(IBMNotAuthorizedError): - return not_mine.data - - @run_integration_test - @quantum_only - def test_upload_program(self, service): - """Test uploading a program.""" - max_execution_time = 3000 - program_id = self._upload_program(service, max_execution_time=max_execution_time) - self.assertTrue(program_id) - program = service.program(program_id) - self.assertTrue(program) - self.assertEqual(max_execution_time, program.max_execution_time) - - @run_integration_test - @quantum_only - def test_upload_program_file(self, service): - """Test uploading a program using a file.""" - temp_fp = tempfile.NamedTemporaryFile(mode="w", delete=False) - self.addCleanup(os.remove, temp_fp.name) - temp_fp.write(RUNTIME_PROGRAM) - temp_fp.close() - - program_id = self._upload_program(service, data=temp_fp.name) - self.assertTrue(program_id) - program = service.program(program_id) - self.assertTrue(program) - - @unittest.skip("Skip until authorized to upload public on cloud") - @unittest.skipIf( - not os.environ.get("QISKIT_IBM_USE_STAGING_CREDENTIALS", ""), - "Only runs on staging", - ) - @run_integration_test - def test_upload_public_program(self, service): - """Test uploading a public program.""" - max_execution_time = 3000 - is_public = True - program_id = self._upload_program( - service, max_execution_time=max_execution_time, is_public=is_public - ) - self.assertTrue(program_id) - program = service.program(program_id) - self.assertTrue(program) - self.assertEqual(max_execution_time, program.max_execution_time) - self.assertEqual(program.is_public, is_public) - - @unittest.skip("Skip until authorized to upload public on cloud") - @unittest.skipIf( - not os.environ.get("QISKIT_IBM_USE_STAGING_CREDENTIALS", ""), - "Only runs on staging", - ) - @run_integration_test - def test_set_visibility(self, service): - """Test setting the visibility of a program.""" - program_id = self._upload_program(service) - # Get the initial visibility - prog: RuntimeProgram = service.program(program_id) - start_vis = prog.is_public - # Flip the original value - service.set_program_visibility(program_id, not start_vis) - # Get the new visibility - prog: RuntimeProgram = service.program(program_id, refresh=True) - end_vis = prog.is_public - # Verify changed - self.assertNotEqual(start_vis, end_vis) - - @run_integration_test - @quantum_only - def test_delete_program(self, service): - """Test deleting program.""" - program_id = self._upload_program(service) - service.delete_program(program_id) - with self.assertRaises(RuntimeProgramNotFound): - service.program(program_id, refresh=True) - - @run_integration_test - @quantum_only - def test_double_delete_program(self, service): - """Test deleting a deleted program.""" - program_id = self._upload_program(service) - service.delete_program(program_id) - with self.assertRaises(RuntimeProgramNotFound): - service.delete_program(program_id) - - @run_integration_test - @quantum_only - def test_update_program_data(self, service): - """Test updating program data.""" - program_v1 = """ -def main(backend, user_messenger, **kwargs): - return "version 1" - """ - program_v2 = """ -def main(backend, user_messenger, **kwargs): - return "version 2" - """ - program_id = self._upload_program(service, data=program_v1) - self.assertEqual(program_v1, service.program(program_id).data) - service.update_program(program_id=program_id, data=program_v2) - self.assertEqual(program_v2, service.program(program_id).data) - - @run_integration_test - @quantum_only - def test_update_program_metadata(self, service): - """Test updating program metadata.""" - program_id = self._upload_program(service) - original = service.program(program_id) - new_metadata = { - "name": PROGRAM_PREFIX, - "description": "test_update_program_metadata", - "max_execution_time": original.max_execution_time + 100, - "spec": {"return_values": {"type": "object", "description": "Some return value"}}, - } - service.update_program(program_id=program_id, metadata=new_metadata) - updated = service.program(program_id, refresh=True) - self.assertEqual(new_metadata["name"], updated.name) - self.assertEqual(new_metadata["description"], updated.description) - self.assertEqual(new_metadata["max_execution_time"], updated.max_execution_time) - self.assertEqual(new_metadata["spec"]["return_values"], updated.return_values) - - def _validate_program(self, program): - """Validate a program.""" - self.assertTrue(program) - self.assertTrue(program.name) - self.assertTrue(program.program_id) - self.assertTrue(program.description) - self.assertTrue(program.max_execution_time) - self.assertTrue(program.creation_date) - self.assertTrue(program.update_date) diff --git a/test/integration/test_proxies.py b/test/integration/test_proxies.py index 6f63e26f72..a6c6c0ed52 100644 --- a/test/integration/test_proxies.py +++ b/test/integration/test_proxies.py @@ -64,7 +64,7 @@ def test_proxies_cloud_runtime_client(self, dependencies: IntegrationTestDepende params = dependencies.service._client_params params.proxies = ProxyConfiguration(urls=VALID_PROXIES) client = RuntimeClient(params) - client.list_programs(limit=1) + client.jobs_get(limit=1) api_line = pproxy_desired_access_log_line(params.url) self.proxy_process.terminate() # kill to be able of reading the output proxy_output = self.proxy_process.stdout.read().decode("utf-8") @@ -81,7 +81,7 @@ def test_proxies_ibm_quantum_runtime_client( url=dependencies.url, proxies={"urls": VALID_PROXIES}, ) - service.programs(limit=1) + service.jobs(limit=1) auth_line = pproxy_desired_access_log_line(dependencies.url) api_line = list(service._hgps.values())[0]._runtime_client._session.base_url @@ -162,7 +162,7 @@ def test_invalid_proxy_port_runtime_client( ) with self.assertRaises(RuntimeRequestsApiError) as context_manager: client = RuntimeClient(params) - client.list_programs(limit=1) + client.jobs_get(limit=1) self.assertIsInstance(context_manager.exception.__cause__, ProxyError) @integration_test_setup(supported_channel=["ibm_quantum"], init_service=False) @@ -203,7 +203,7 @@ def test_invalid_proxy_address_runtime_client( ) with self.assertRaises(RuntimeRequestsApiError) as context_manager: client = RuntimeClient(params) - client.list_programs(limit=1) + client.jobs_get(limit=1) self.assertIsInstance(context_manager.exception.__cause__, ProxyError) diff --git a/test/program.py b/test/program.py index 25ee2a8f82..dfa2f1fc22 100644 --- a/test/program.py +++ b/test/program.py @@ -12,57 +12,7 @@ """Utility functions for runtime testing.""" -import uuid -import copy from datetime import datetime, timezone -from qiskit_ibm_runtime import QiskitRuntimeService - - -DEFAULT_DATA = "def main() {}" -DEFAULT_METADATA = { - "name": "qiskit-test", - "description": "Test program.", - "max_execution_time": 300, - "spec": { - "backend_requirements": {"min_num_qubits": 5}, - "parameters": { - "properties": { - "param1": { - "description": "Desc 1", - "type": "string", - "enum": ["a", "b", "c"], - }, - "param2": {"description": "Desc 2", "type": "integer", "min": 0}, - }, - "required": ["param1"], - }, - "return_values": { - "type": "object", - "description": "Return values", - "properties": {"ret_val": {"description": "Some return value.", "type": "string"}}, - }, - "interim_results": { - "properties": {"int_res": {"description": "Some interim result", "type": "string"}} - }, - }, -} - - -def upload_program( - service: QiskitRuntimeService, - name: str = None, - max_execution_time: int = 300, - is_public: bool = False, -) -> str: - """Upload a new program.""" - name = name or uuid.uuid4().hex - data = DEFAULT_DATA - metadata = copy.deepcopy(DEFAULT_METADATA) - metadata.update(name=name) - metadata.update(is_public=is_public) - metadata.update(max_execution_time=max_execution_time) - program_id = service.upload_program(data=data, metadata=metadata) - return program_id def run_program( @@ -95,8 +45,8 @@ def run_program( service._api_client.set_final_status(final_status) elif job_classes: service._api_client.set_job_classes(job_classes) - if program_id is None: - program_id = upload_program(service) + if not program_id: + program_id = "sampler" job = service.run( program_id=program_id, options=options, diff --git a/test/serialization.py b/test/serialization.py index 372cea5ddd..f7f38c6836 100644 --- a/test/serialization.py +++ b/test/serialization.py @@ -14,7 +14,7 @@ import json -from qiskit_ibm_runtime.program import ResultDecoder +from qiskit_ibm_runtime.utils.result_decoder import ResultDecoder def get_complex_types(): diff --git a/test/templates.py b/test/templates.py deleted file mode 100644 index a30b38bc44..0000000000 --- a/test/templates.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. - -"""Templates for use with unit tests.""" - -RUNTIME_PROGRAM = """ -import random -import time -import warnings -import logging - -from qiskit import transpile -from qiskit.circuit.random import random_circuit - -logger = logging.getLogger("qiskit-test") - -def prepare_circuits(backend): - circuit = random_circuit(num_qubits=5, depth=4, measure=True, - seed=random.randint(0, 1000)) - return transpile(circuit, backend) - -def main(backend, user_messenger, **kwargs): - iterations = kwargs['iterations'] - sleep_per_iteration = kwargs.pop('sleep_per_iteration', 0) - interim_results = kwargs.pop('interim_results', {}) - final_result = kwargs.pop("final_result", {}) - for it in range(iterations): - time.sleep(sleep_per_iteration) - qc = prepare_circuits(backend) - user_messenger.publish({"iteration": it, "interim_results": interim_results}) - backend.run(qc).result() - - user_messenger.publish(final_result, final=True) - print("this is a stdout message") - warnings.warn("this is a stderr message") - logger.info("this is an info log") - """ - -RUNTIME_PROGRAM_METADATA = { - "max_execution_time": 600, - "description": "Qiskit test program", -} -PROGRAM_PREFIX = "qiskit-test" diff --git a/test/unit/mock/fake_runtime_client.py b/test/unit/mock/fake_runtime_client.py index 1f2c16fb8d..55f5d29e3f 100644 --- a/test/unit/mock/fake_runtime_client.py +++ b/test/unit/mock/fake_runtime_client.py @@ -119,7 +119,7 @@ def __init__( self._backend_name = backend_name self._params = params self._image = image - self._interim_results = json.dumps("foo") + self._interim_results = json.dumps({"quasi_dists": [{0: 0.5, 3: 0.5}], "metadata": []}) self._job_tags = job_tags self.log_level = log_level self._session_id = session_id @@ -130,7 +130,7 @@ def __init__( self._future = self._executor.submit(self._auto_progress) self._result = None elif final_status == "COMPLETED": - self._result = json.dumps("foo") + self._result = json.dumps({"quasi_dists": [{0: 0.5, 3: 0.5}], "metadata": []}) self._final_status = final_status self._channel_strategy = channel_strategy @@ -141,7 +141,7 @@ def _auto_progress(self): self._status = status if self._status == "COMPLETED": - self._result = json.dumps("foo") + self._result = json.dumps({"quasi_dists": [{0: 0.5, 3: 0.5}], "metadata": []}) def to_dict(self): """Convert to dictionary format.""" @@ -246,7 +246,7 @@ def _auto_progress(self): self._status = "COMPLETED" if self._status == "COMPLETED": - self._result = json.dumps("foo") + self._result = json.dumps({"quasi_dists": [{0: 0.5, 3: 0.5}], "metadata": []}) class BaseFakeRuntimeClient: @@ -289,73 +289,6 @@ def set_final_status(self, final_status): """Set job status to passed in final status instantly.""" self._final_status = final_status - def list_programs(self, limit, skip): - """List all programs.""" - programs = [] - for prog in self._programs.values(): - programs.append(prog.to_dict()) - return {"programs": programs[skip : limit + skip], "count": len(self._programs)} - - def program_create( - self, - program_data, - name, - description, - max_execution_time, - spec=None, - is_public=False, - ): - """Create a program.""" - program_id = name - if program_id in self._programs: - raise RequestsApiError("Program already exists.", status_code=409) - backend_requirements = spec.get("backend_requirements", None) - parameters = spec.get("parameters", None) - return_values = spec.get("return_values", None) - interim_results = spec.get("interim_results", None) - self._programs[program_id] = BaseFakeProgram( - program_id=program_id, - name=name, - data=program_data, - cost=max_execution_time, - description=description, - backend_requirements=backend_requirements, - parameters=parameters, - return_values=return_values, - interim_results=interim_results, - is_public=is_public, - ) - return {"id": program_id} - - def program_update( - self, - program_id: str, - program_data: str = None, - name: str = None, - description: str = None, - max_execution_time: int = None, - spec: Optional[Dict] = None, - ) -> None: - """Update a program.""" - program = self._get_program(program_id) - program._data = program_data or program._data - program._name = name or program._name - program._description = description or program._description - program._cost = max_execution_time or program._cost - if spec: - program._backend_requirements = ( - spec.get("backend_requirements") or program._backend_requirements - ) - program._parameters = spec.get("parameters") or program._parameters - program._return_values = spec.get("return_values") or program._return_values - program._interim_results = spec.get("interim_results") or program._interim_results - - def program_get(self, program_id: str) -> Dict[str, Any]: - """Return a specific program.""" - if program_id not in self._programs: - raise RequestsApiError("Program not found", status_code=404) - return self._programs[program_id].to_dict(include_data=True) - def program_run( self, program_id: str, @@ -372,7 +305,6 @@ def program_run( channel_strategy: Optional[str] = None, ) -> Dict[str, Any]: """Run the specified program.""" - _ = self._get_program(program_id) job_id = uuid.uuid4().hex job_cls = self._job_classes.pop(0) if len(self._job_classes) > 0 else BaseFakeRuntimeJob if hgp: @@ -408,11 +340,6 @@ def program_run( self._jobs[job_id] = job return {"id": job_id, "backend": backend_name} - def program_delete(self, program_id: str) -> None: - """Delete the specified program.""" - self._get_program(program_id) - del self._programs[program_id] - def job_get(self, job_id: str, exclude_params: bool = None) -> Any: """Get the specific job.""" return self._get_job(job_id, exclude_params).to_dict() @@ -468,17 +395,6 @@ def jobs_get( return {"jobs": [job.to_dict() for job in jobs], "count": count} - def set_program_visibility(self, program_id: str, public: bool) -> None: - """Sets a program's visibility. - - Args: - program_id: Program ID. - public: If ``True``, make the program visible to all. - If ``False``, make the program visible to just your account. - """ - program = self._get_program(program_id) - program._is_public = public - def job_results(self, job_id): """Get the results of a program job.""" return self._get_job(job_id).result() @@ -503,12 +419,6 @@ def wait_for_final_state(self, job_id): while status not in final_states: status = self._get_job(job_id).status() - def _get_program(self, program_id): - """Get program.""" - if program_id not in self._programs: - raise RequestsApiError("Program not found", status_code=404) - return self._programs[program_id] - # pylint: disable=unused-argument def _get_job(self, job_id: str, exclude_params: bool = None) -> Any: """Get job.""" diff --git a/test/unit/test_job_retrieval.py b/test/unit/test_job_retrieval.py index 4014827df0..72e186cf38 100644 --- a/test/unit/test_job_retrieval.py +++ b/test/unit/test_job_retrieval.py @@ -16,7 +16,7 @@ from .mock.fake_runtime_service import FakeRuntimeService from ..ibm_test_case import IBMTestCase from ..decorators import run_quantum_and_cloud_fake -from ..program import run_program, upload_program +from ..program import run_program from ..utils import mock_wait_for_final_state @@ -31,7 +31,7 @@ def setUp(self): @run_quantum_and_cloud_fake def test_retrieve_job(self, service): """Test retrieving a job.""" - program_id = upload_program(service) + program_id = "sampler" params = {"param1": "foo"} job = run_program(service=service, program_id=program_id, inputs=params) rjob = service.job(job.job_id()) @@ -41,7 +41,7 @@ def test_retrieve_job(self, service): @run_quantum_and_cloud_fake def test_jobs_no_limit(self, service): """Test retrieving jobs without limit.""" - program_id = upload_program(service) + program_id = "sampler" jobs = [] for _ in range(25): @@ -52,7 +52,7 @@ def test_jobs_no_limit(self, service): @run_quantum_and_cloud_fake def test_jobs_limit(self, service): """Test retrieving jobs with limit.""" - program_id = upload_program(service) + program_id = "sampler" jobs = [] job_count = 25 @@ -68,7 +68,7 @@ def test_jobs_limit(self, service): @run_quantum_and_cloud_fake def test_jobs_skip(self, service): """Test retrieving jobs with skip.""" - program_id = upload_program(service) + program_id = "sampler" jobs = [] for _ in range(5): @@ -79,7 +79,7 @@ def test_jobs_skip(self, service): def test_jobs_skip_limit(self): """Test retrieving jobs with skip and limit.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" jobs = [] for _ in range(10): @@ -90,7 +90,7 @@ def test_jobs_skip_limit(self): @run_quantum_and_cloud_fake def test_jobs_pending(self, service): """Test retrieving pending jobs (QUEUED, RUNNING).""" - program_id = upload_program(service) + program_id = "sampler" _, pending_jobs_count, _ = self._populate_jobs_with_all_statuses( service, program_id=program_id @@ -101,7 +101,7 @@ def test_jobs_pending(self, service): def test_jobs_limit_pending(self): """Test retrieving pending jobs (QUEUED, RUNNING) with limit.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" self._populate_jobs_with_all_statuses(service, program_id=program_id) limit = 4 @@ -111,7 +111,7 @@ def test_jobs_limit_pending(self): def test_jobs_skip_pending(self): """Test retrieving pending jobs (QUEUED, RUNNING) with skip.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" _, pending_jobs_count, _ = self._populate_jobs_with_all_statuses( service, program_id=program_id @@ -123,7 +123,7 @@ def test_jobs_skip_pending(self): def test_jobs_limit_skip_pending(self): """Test retrieving pending jobs (QUEUED, RUNNING) with limit and skip.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" self._populate_jobs_with_all_statuses(service, program_id=program_id) limit = 2 @@ -134,7 +134,7 @@ def test_jobs_limit_skip_pending(self): def test_jobs_returned(self): """Test retrieving returned jobs (COMPLETED, FAILED, CANCELLED).""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" _, _, returned_jobs_count = self._populate_jobs_with_all_statuses( service, program_id=program_id @@ -145,7 +145,7 @@ def test_jobs_returned(self): def test_jobs_limit_returned(self): """Test retrieving returned jobs (COMPLETED, FAILED, CANCELLED) with limit.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" self._populate_jobs_with_all_statuses(service, program_id=program_id) limit = 6 @@ -155,7 +155,7 @@ def test_jobs_limit_returned(self): def test_jobs_skip_returned(self): """Test retrieving returned jobs (COMPLETED, FAILED, CANCELLED) with skip.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" _, _, returned_jobs_count = self._populate_jobs_with_all_statuses( service, program_id=program_id @@ -167,7 +167,7 @@ def test_jobs_skip_returned(self): def test_jobs_limit_skip_returned(self): """Test retrieving returned jobs (COMPLETED, FAILED, CANCELLED) with limit and skip.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" self._populate_jobs_with_all_statuses(service, program_id=program_id) limit = 6 @@ -175,25 +175,10 @@ def test_jobs_limit_skip_returned(self): rjobs = service.jobs(limit=limit, skip=skip, pending=False) self.assertEqual(limit, len(rjobs)) - @run_quantum_and_cloud_fake - def test_jobs_filter_by_program_id(self, service): - """Test retrieving jobs by Program ID.""" - program_id = upload_program(service) - program_id_1 = upload_program(service) - - job = run_program(service=service, program_id=program_id) - job_1 = run_program(service=service, program_id=program_id_1) - with mock_wait_for_final_state(service, job): - job.wait_for_final_state() - job_1.wait_for_final_state() - rjobs = service.jobs(program_id=program_id) - self.assertEqual(program_id, rjobs[0].program_id) - self.assertEqual(1, len(rjobs)) - def test_jobs_filter_by_instance(self): """Test retrieving jobs by instance.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" instance = FakeRuntimeService.DEFAULT_HGPS[1] job = run_program(service=service, program_id=program_id, instance=instance) @@ -209,7 +194,7 @@ def test_jobs_filter_by_instance(self): def test_jobs_filter_by_job_tags(self): """Test retrieving jobs by job tags.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" job_tags = ["test_tag"] job = run_program(service=service, program_id=program_id, job_tags=job_tags) @@ -224,7 +209,7 @@ def test_jobs_filter_by_job_tags(self): def test_jobs_filter_by_session_id(self): """Test retrieving jobs by session id.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" job = run_program(service=service, program_id=program_id) job_2 = run_program(service=service, program_id=program_id, session_id=job.job_id()) @@ -256,7 +241,7 @@ def test_jobs_filter_by_date(self): def test_jobs_sort_by_date(self): """Test retrieving jobs sorted by the date.""" service = self._ibm_quantum_service - program_id = upload_program(service) + program_id = "sampler" job = run_program(service=service, program_id=program_id) job_2 = run_program(service=service, program_id=program_id) @@ -284,7 +269,7 @@ def test_different_hgps(self): token="some_token", instance=FakeRuntimeService.DEFAULT_HGPS[0], ) - program_id = upload_program(service) + program_id = "sampler" # Run with hgp1 backend. backend_name = FakeRuntimeService.DEFAULT_UNIQUE_BACKEND_PREFIX + "1" diff --git a/test/unit/test_jobs.py b/test/unit/test_jobs.py index af28cadfb6..9ad78e8a7e 100644 --- a/test/unit/test_jobs.py +++ b/test/unit/test_jobs.py @@ -25,7 +25,6 @@ RuntimeJobFailureError, RuntimeJobNotFound, RuntimeJobMaxTimeoutError, - RuntimeProgramNotFound, IBMInputValueError, RuntimeInvalidStateError, ) @@ -33,13 +32,11 @@ FailedRuntimeJob, FailedRanTooLongRuntimeJob, CancelableRuntimeJob, - CustomResultRuntimeJob, ) from .mock.fake_runtime_service import FakeRuntimeService from ..ibm_test_case import IBMTestCase from ..decorators import run_quantum_and_cloud_fake -from ..program import run_program, upload_program -from ..serialization import get_complex_types +from ..program import run_program from ..utils import mock_wait_for_final_state @@ -60,12 +57,6 @@ def test_run_program(self, service): self.assertEqual(job.status(), JobStatus.DONE) self.assertTrue(job.result()) - @run_quantum_and_cloud_fake - def test_run_phantom_program(self, service): - """Test running a phantom program.""" - with self.assertRaises(RuntimeProgramNotFound): - _ = run_program(service=service, program_id="phantom_program") - @run_quantum_and_cloud_fake def test_run_program_phantom_backend(self, service): """Test running on a phantom backend.""" @@ -182,14 +173,6 @@ def test_run_program_failed_ran_too_long(self, service): with self.assertRaises(RuntimeJobMaxTimeoutError): job.result() - @run_quantum_and_cloud_fake - def test_program_params_namespace(self, service): - """Test running a program using parameter namespace.""" - program_id = upload_program(service) - params = service.program(program_id).parameters() - params.param1 = "Hello World" - run_program(service, program_id, inputs=params) - @run_quantum_and_cloud_fake def test_cancel_job(self, service): """Test canceling a job.""" @@ -233,13 +216,6 @@ def test_job_inputs(self, service): job = run_program(service, inputs=inputs) self.assertEqual(inputs, job.inputs) - @run_quantum_and_cloud_fake - def test_job_program_id(self, service): - """Test job program ID.""" - program_id = upload_program(service) - job = run_program(service, program_id=program_id) - self.assertEqual(program_id, job.program_id) - @run_quantum_and_cloud_fake def test_wait_for_final_state(self, service): """Test wait for final state.""" @@ -248,18 +224,6 @@ def test_wait_for_final_state(self, service): job.wait_for_final_state() self.assertEqual(JobStatus.DONE, job.status()) - @run_quantum_and_cloud_fake - def test_get_result_twice(self, service): - """Test getting results multiple times.""" - custom_result = get_complex_types() - job_cls = CustomResultRuntimeJob - job_cls.custom_result = custom_result - - job = run_program(service=service, job_classes=job_cls) - with mock_wait_for_final_state(service, job): - _ = job.result() - _ = job.result() - @run_quantum_and_cloud_fake def test_delete_job(self, service): """Test deleting a job.""" @@ -281,9 +245,6 @@ def test_download_external_job_result(self, service): request_mock.get.return_value = mock_response with mock_wait_for_final_state(service, job): job.wait_for_final_state() - job._api_client.job_results = MagicMock( - return_value='{"url": "https://external-url.com/"}' - ) result = job.result() - self.assertEqual(result, "content-from-external-url") + self.assertTrue(result)