diff --git a/qiskit_ibm_runtime/ibm_backend.py b/qiskit_ibm_runtime/ibm_backend.py index df71c6540..8d7f4f426 100644 --- a/qiskit_ibm_runtime/ibm_backend.py +++ b/qiskit_ibm_runtime/ibm_backend.py @@ -545,6 +545,10 @@ def run( """ # pylint: disable=arguments-differ options = {"backend_name": self.name} + if job_tags: + options["job_tags"] = job_tags + if max_execution_time: + options["max_execution_time"] = max_execution_time inputs = {"circuits": circuits} if shots: inputs["shots"] = shots @@ -573,8 +577,6 @@ def run( result_decoder=result_decoder, instance=instance, session_id=session_id, - job_tags=job_tags, - max_execution_time=max_execution_time, start_session=start_session, ) diff --git a/qiskit_ibm_runtime/options.py b/qiskit_ibm_runtime/options.py index 70efd6a66..77d7676a2 100644 --- a/qiskit_ibm_runtime/options.py +++ b/qiskit_ibm_runtime/options.py @@ -160,6 +160,7 @@ class EnvironmentOptions: log_level: str = "WARNING" image: Optional[str] = None instance: Optional[str] = None + job_tags: Optional[List[str]] = None @_flexible @@ -168,6 +169,9 @@ class Options: """Options for the primitive programs. Args: + max_execution_time: Maximum execution time in seconds. If + a job exceeds this time limit, it is forcibly cancelled. + optimization_level: How much optimization to perform on the circuits. Higher levels generate more optimized circuits, at the expense of longer transpilation times. @@ -263,8 +267,13 @@ class Options: * instance: The hub/group/project to use, in that format. This is only supported for ``ibm_quantum`` channel. If ``None``, a hub/group/project that provides access to the target backend is randomly selected. + + * job_tags: Tags to be assigned to the job. The tags can subsequently be used + as a filter in the :meth:`qiskit_ibm_runtime.qiskit_runtime_service.jobs()` + function call. """ + max_execution_time: int = None optimization_level: int = 1 resilience_level: int = 0 transpilation: Union[TranspilationOptions, Dict] = field( @@ -344,7 +353,7 @@ def _get_runtime_options(options: Dict) -> Dict: for fld in fields(RuntimeOptions): if fld.name in environment: out[fld.name] = environment[fld.name] - + out["max_execution_time"] = options.get("max_execution_time") return out def _merge_options(self, new_options: Optional[Dict] = None) -> Dict: diff --git a/qiskit_ibm_runtime/qiskit_runtime_service.py b/qiskit_ibm_runtime/qiskit_runtime_service.py index 9e66feb8b..3a9a68bfd 100644 --- a/qiskit_ibm_runtime/qiskit_runtime_service.py +++ b/qiskit_ibm_runtime/qiskit_runtime_service.py @@ -47,7 +47,6 @@ from .utils import RuntimeDecoder, to_base64_string, to_python_identifier from .utils.backend_decoder import configuration_from_server_data from .utils.hgp import to_instance_format, from_instance_format -from .utils.utils import validate_job_tags from .api.client_parameters import ClientParameters from .runtime_options import RuntimeOptions from .utils.deprecation import ( @@ -854,8 +853,6 @@ def run( result_decoder: Optional[Type[ResultDecoder]] = None, instance: Optional[str] = None, session_id: Optional[str] = None, - job_tags: Optional[List[str]] = None, - max_execution_time: Optional[int] = None, start_session: Optional[bool] = False, ) -> RuntimeJob: """Execute the runtime program. @@ -873,6 +870,10 @@ def run( * log_level: logging level to set in the execution environment. The valid log levels are: ``DEBUG``, ``INFO``, ``WARNING``, ``ERROR``, and ``CRITICAL``. The default level is ``WARNING``. + * job_tags: Tags to be assigned to the job. The tags can subsequently be used + as a filter in the :meth:`jobs()` function call. + * max_execution_time: Maximum execution time in seconds. If + a job exceeds this time limit, it is forcibly cancelled. callback: Callback function to be invoked for any interim results and final result. The callback function will receive 2 positional parameters: @@ -885,10 +886,6 @@ def run( instance: This is only supported for ``ibm_quantum`` runtime and is in the hub/group/project format. session_id: Job ID of the first job in a runtime session. - job_tags: Tags to be assigned to the job. The tags can subsequently be used - as a filter in the :meth:`jobs()` function call. - max_execution_time: Maximum execution time in seconds. This overrides - the max_execution_time of the program and cannot exceed it. start_session: Set to True to explicitly start a runtime session. Defaults to False. Returns: @@ -905,7 +902,6 @@ def run( DeprecationWarning, stacklevel=2, ) - validate_job_tags(job_tags, IBMInputValueError) qrt_options: RuntimeOptions = options if options is None: @@ -932,7 +928,9 @@ def run( hgp_name = None if self._channel == "ibm_quantum": # Find the right hgp - hgp = self._get_hgp(instance=instance, backend_name=qrt_options.backend) + hgp = self._get_hgp( + instance=qrt_options.instance, backend_name=qrt_options.backend + ) backend = hgp.backend(qrt_options.backend) hgp_name = hgp.name @@ -946,8 +944,8 @@ def run( hgp=hgp_name, log_level=qrt_options.log_level, session_id=session_id, - job_tags=job_tags, - max_execution_time=max_execution_time, + job_tags=qrt_options.job_tags, + max_execution_time=qrt_options.max_execution_time, start_session=start_session, ) except RequestsApiError as ex: diff --git a/qiskit_ibm_runtime/runtime_options.py b/qiskit_ibm_runtime/runtime_options.py index e8dadfd17..9ef94c96a 100644 --- a/qiskit_ibm_runtime/runtime_options.py +++ b/qiskit_ibm_runtime/runtime_options.py @@ -15,12 +15,13 @@ import re import logging from dataclasses import dataclass -from typing import Optional +from typing import Optional, List from qiskit.utils.deprecation import deprecate_arguments from .exceptions import IBMInputValueError from .utils.deprecation import issue_deprecation_msg +from .utils.utils import validate_job_tags @dataclass(init=False) @@ -31,6 +32,8 @@ class RuntimeOptions: image: Optional[str] = None log_level: Optional[str] = None instance: Optional[str] = None + job_tags: Optional[List[str]] = None + max_execution_time: Optional[int] = None @deprecate_arguments({"backend_name": "backend"}) def __init__( @@ -39,6 +42,8 @@ def __init__( image: Optional[str] = None, log_level: Optional[str] = None, instance: Optional[str] = None, + job_tags: Optional[List[str]] = None, + max_execution_time: Optional[int] = None, ) -> None: """RuntimeOptions constructor. @@ -53,11 +58,17 @@ def __init__( instance: The hub/group/project to use, in that format. This is only supported for ``ibm_quantum`` channel. If ``None``, a hub/group/project that provides access to the target backend is randomly selected. + job_tags: Tags to be assigned to the job. The tags can subsequently be used + as a filter in the :meth:`jobs()` function call. + max_execution_time: Maximum execution time in seconds. If + a job exceeds this time limit, it is forcibly cancelled. """ self.backend = backend self.image = image self.log_level = log_level self.instance = instance + self.job_tags = job_tags + self.max_execution_time = max_execution_time def validate(self, channel: str) -> None: """Validate options. @@ -91,6 +102,8 @@ def validate(self, channel: str) -> None: f"{self.log_level} is not a valid log level. The valid log levels are: `DEBUG`, " f"`INFO`, `WARNING`, `ERROR`, and `CRITICAL`." ) + if self.job_tags: + validate_job_tags(self.job_tags, IBMInputValueError) @property def backend_name(self) -> str: diff --git a/qiskit_ibm_runtime/runtime_session.py b/qiskit_ibm_runtime/runtime_session.py index 62368a348..67d9bbad8 100644 --- a/qiskit_ibm_runtime/runtime_session.py +++ b/qiskit_ibm_runtime/runtime_session.py @@ -91,7 +91,6 @@ def _run(self, inputs: Union[Dict, ParameterNamespace]) -> RuntimeJob: inputs=inputs, session_id=self._session_id, start_session=self._start_session, - max_execution_time=self._max_time, ) @_active_session diff --git a/qiskit_ibm_runtime/session.py b/qiskit_ibm_runtime/session.py index 10f00a327..de97ecf39 100644 --- a/qiskit_ibm_runtime/session.py +++ b/qiskit_ibm_runtime/session.py @@ -137,13 +137,14 @@ def run( Returns: Submitted job. """ - # TODO: Cache data when server supports it. - # TODO: Do we really need to specify a None max time if session has started? - max_time = self._max_time if not self._session_id else None options = options or {} options["backend"] = self._backend + # TODO: Do we really need to specify a None max time if session has started? + max_time = self._max_time if not self._session_id else None + if max_time: + options["max_execution_time"] = max_time job = self._service.run( program_id=program_id, @@ -151,7 +152,6 @@ def run( inputs=inputs, session_id=self._session_id, start_session=self._session_id is None, - max_execution_time=max_time, callback=callback, result_decoder=result_decoder, ) diff --git a/releasenotes/notes/add-options-26c127fcd40623a5.yaml b/releasenotes/notes/add-options-26c127fcd40623a5.yaml new file mode 100644 index 000000000..dc5754fba --- /dev/null +++ b/releasenotes/notes/add-options-26c127fcd40623a5.yaml @@ -0,0 +1,6 @@ +--- +upgrade: + - | + The :class:`qiskit_ibm_runtime.Options` class now accepts + ``max_execution_time`` as a first level option and ``job_tags`` as an option under `environment`. + :class:`qiskit_ibm_runtime.RuntimeOptions` has also been updated to include these two parameters. diff --git a/test/ibm_test_case.py b/test/ibm_test_case.py index 971e1ce1b..6fe0cb16b 100644 --- a/test/ibm_test_case.py +++ b/test/ibm_test_case.py @@ -203,13 +203,16 @@ def _run_program( backend_name = ( backend if backend is not None else self.sim_backends[service.channel] ) - options = {"backend": backend_name, "log_level": log_level} + options = { + "backend": backend_name, + "log_level": log_level, + "job_tags": job_tags, + "max_execution_time": max_execution_time, + } job = service.run( program_id=pid, inputs=inputs, options=options, - job_tags=job_tags, - max_execution_time=max_execution_time, session_id=session_id, callback=callback, start_session=start_session, diff --git a/test/program.py b/test/program.py index 7804f81e1..e4fc4af72 100644 --- a/test/program.py +++ b/test/program.py @@ -86,7 +86,13 @@ def run_program( ): """Run a program.""" backend_name = backend_name if backend_name is not None else "common_backend" - options = {"backend": backend_name, "image": image, "log_level": log_level} + options = { + "backend": backend_name, + "image": image, + "log_level": log_level, + "job_tags": job_tags, + "max_execution_time": max_execution_time, + } if final_status is not None: service._api_client.set_final_status(final_status) elif job_classes: @@ -99,8 +105,6 @@ def run_program( inputs=inputs, result_decoder=decoder, instance=instance, - job_tags=job_tags, - max_execution_time=max_execution_time, session_id=session_id, ) job._creation_date = datetime.now(timezone.utc)