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

Support session #699

Merged
merged 61 commits into from
Aug 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
5b423b0
Initial support for session
merav-aharoni Jul 18, 2023
cb02578
Merge branch 'main' into support_session
merav-aharoni Jul 20, 2023
cc1f28f
Added test for session. Cleaned up some of the unnecessary code
merav-aharoni Jul 20, 2023
c9ca298
Added max_execution_time parameter when calling program_run
merav-aharoni Jul 20, 2023
f8890bf
Added hms_to_seconds
merav-aharoni Jul 20, 2023
59de266
Placeholder for session close
merav-aharoni Jul 20, 2023
6135081
Merge branch 'main' into support_session
merav-aharoni Jul 21, 2023
a715bf1
Updated example in documentation. Removed Session._circuits_map.
merav-aharoni Jul 23, 2023
766c36e
Merge branch 'support_session' of github.com:merav-aharoni/qiskit-ibm…
merav-aharoni Jul 23, 2023
79607f8
New test file for session in unit test
merav-aharoni Jul 23, 2023
9107e8f
More unit tests
merav-aharoni Jul 23, 2023
5a4e9f0
Added more tests, unit and integration
merav-aharoni Jul 23, 2023
0dc9756
Changed tests to use FakeProvider
merav-aharoni Jul 23, 2023
91c6f0a
Added support to closing a session
merav-aharoni Jul 24, 2023
ee7f100
lint and black
merav-aharoni Jul 24, 2023
d9ab78a
Adding runtime_session.py
merav-aharoni Jul 24, 2023
f6c864a
Moved tests from unit to integration
merav-aharoni Jul 24, 2023
43b2bd3
lint
merav-aharoni Jul 24, 2023
366c9f0
Updated documentation
merav-aharoni Jul 24, 2023
bd74605
Added test
merav-aharoni Jul 24, 2023
92efec9
Merge branch 'main' into support_session
merav-aharoni Jul 27, 2023
9f97104
Added Session to __init__
merav-aharoni Jul 27, 2023
577e3f9
Fixed tab of documentation
merav-aharoni Jul 27, 2023
bf19d18
Added newline
merav-aharoni Jul 27, 2023
320e9ca
Merge branch 'main' into support_session
merav-aharoni Aug 3, 2023
5fc999f
Added test for session as a parameter
merav-aharoni Aug 3, 2023
a2fe052
Merge branch 'support_session' of github.com:merav-aharoni/qiskit-ibm…
merav-aharoni Aug 3, 2023
64212a3
Revised session to be a member of IBMBackend
merav-aharoni Aug 10, 2023
0995d0a
Added support for Session as context manager
merav-aharoni Aug 10, 2023
ff82468
Updated Session documentation
merav-aharoni Aug 10, 2023
e393e5f
Improved the tests
merav-aharoni Aug 10, 2023
0eb465f
Added a session property to IBMBackend
merav-aharoni Aug 10, 2023
d5f5fd8
Release note
merav-aharoni Aug 10, 2023
9eddc89
Merge branch 'main' into session_in_backend
merav-aharoni Aug 10, 2023
eacbe38
Removed code related to default session
merav-aharoni Aug 10, 2023
bce45ee
Revert "Removed code related to default session"
merav-aharoni Aug 10, 2023
c2cf208
Merge branch 'session_in_backend' of github.com:merav-aharoni/qiskit-…
merav-aharoni Aug 10, 2023
7205efb
Fixed import
merav-aharoni Aug 10, 2023
eb2493a
Fixed mistake in type hints
merav-aharoni Aug 10, 2023
358fc6a
Fixes in type hints
merav-aharoni Aug 10, 2023
ff34281
Merge branch 'main' into session_in_backend
merav-aharoni Aug 13, 2023
bf5c78a
black, lint and mypy fixes
merav-aharoni Aug 13, 2023
4ad7f42
Merge branch 'session_in_backend' of github.com:merav-aharoni/qiskit-…
merav-aharoni Aug 13, 2023
1409583
Fixed tests for CI
merav-aharoni Aug 13, 2023
9a06d68
lint
merav-aharoni Aug 13, 2023
290b340
Fixed provider in tests
merav-aharoni Aug 13, 2023
83e2f9d
lint
merav-aharoni Aug 13, 2023
4ae8904
Added a test and improved others
merav-aharoni Aug 13, 2023
e8f1918
Merge branch 'main' into session_in_backend
merav-aharoni Aug 20, 2023
c4724e4
Update releasenotes/notes/session_support-90c41a3c3f58a46b.yaml
merav-aharoni Aug 20, 2023
83b402d
Added code-block to release note, from code review
merav-aharoni Aug 20, 2023
8bb4060
Set backend._session to None after closing session
merav-aharoni Aug 20, 2023
3b6efde
Modfied tests so that after close_session, backend will run without s…
merav-aharoni Aug 20, 2023
5bd3761
lint
merav-aharoni Aug 20, 2023
3e9c120
Merge branch 'main' into session_in_backend
kt474 Aug 22, 2023
037499a
Fixed bug where session was always started
merav-aharoni Aug 22, 2023
6235507
Added handling of session_id in ibm_job
merav-aharoni Aug 22, 2023
9fa36f7
Merge branch 'session_in_backend' of github.com:merav-aharoni/qiskit-…
merav-aharoni Aug 22, 2023
ec2218a
black
merav-aharoni Aug 22, 2023
b1f9e4a
Added test_session to integration-test-3
merav-aharoni Aug 22, 2023
f010762
Merge branch 'main' into session_in_backend
merav-aharoni Aug 22, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ integration-test-2:
python -m unittest -v test/integration/test_ibm_job.py test/integration/test_ibm_job_attributes.py

integration-test-3:
python -m unittest -v test/integration/test_serialization.py test/integration/test_ibm_qasm_simulator.py
python -m unittest -v test/integration/test_serialization.py test/integration/test_ibm_qasm_simulator.py \
test/integration/test_session.py

e2e-test:
python -m unittest discover --verbose --top-level-directory . --start-directory test/e2e
Expand Down
2 changes: 2 additions & 0 deletions qiskit_ibm_provider/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@
IBMProvider
IBMBackend
IBMBackendService
Session

Exceptions
==========
Expand All @@ -80,6 +81,7 @@

from .ibm_provider import IBMProvider
from .ibm_backend import IBMBackend
from .session import Session
from .job.ibm_job import IBMJob
from .exceptions import *
from .ibm_backend_service import IBMBackendService
Expand Down
8 changes: 8 additions & 0 deletions qiskit_ibm_provider/api/clients/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -309,3 +309,11 @@ def update_tags(self, job_id: str, tags: list) -> Response:
API Response.
"""
return self._api.program_job(job_id).update_tags(tags)

def close_session(self, session_id: str) -> None:
"""Close session

Args:
session_id (str): the id of the session to close
"""
self._api.runtime_session(session_id=session_id).close()
12 changes: 12 additions & 0 deletions qiskit_ibm_provider/api/rest/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from .backend import Backend
from .base import RestAdapterBase
from .program_job import ProgramJob
from .runtime_session import RuntimeSession
from ...utils import RuntimeEncoder
from ...utils.converters import local_to_utc

Expand Down Expand Up @@ -56,6 +57,17 @@ def program_job(self, job_id: str) -> "ProgramJob":
"""
return ProgramJob(self.session, job_id)

def runtime_session(self, session_id: str) -> "RuntimeSession":
"""Return an adapter for the session.

Args:
session_id: Job ID of the first job in a session.

Returns:
The session adapter.
"""
return RuntimeSession(self.session, session_id)

def program_run(
self,
program_id: str,
Expand Down
42 changes: 42 additions & 0 deletions qiskit_ibm_provider/api/rest/runtime_session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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 Session REST adapter."""


from qiskit_ibm_provider.api.rest.base import RestAdapterBase
from ..session import RetrySession


class RuntimeSession(RestAdapterBase):
"""Rest adapter for session related endpoints."""

URL_MAP = {
"close": "/close",
}

def __init__(
self, session: RetrySession, session_id: str, url_prefix: str = ""
) -> None:
"""Job constructor.

Args:
session: RetrySession to be used in the adapter.
session_id: Job ID of the first job in a runtime session.
url_prefix: Prefix to use in the URL.
"""
super().__init__(session, "{}/sessions/{}".format(url_prefix, session_id))

def close(self) -> None:
"""Close this session."""
url = self.get_url("close")
self.session.delete(url)
52 changes: 48 additions & 4 deletions qiskit_ibm_provider/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,14 +44,18 @@
from qiskit.transpiler.passmanager import PassManager
from qiskit.transpiler.target import Target

from qiskit_ibm_provider import ibm_provider # pylint: disable=unused-import
from qiskit_ibm_provider import ( # pylint: disable=unused-import
ibm_provider,
)
from .session import Session
from .api.clients import AccountClient
from .exceptions import (
IBMBackendError,
IBMBackendValueError,
IBMBackendApiError,
IBMBackendApiProtocolError,
)

from .job import IBMJob, IBMCircuitJob
from .transpiler.passes.basis.convert_id_to_delay import (
ConvertIdToDelay,
Expand Down Expand Up @@ -214,6 +218,7 @@ def __init__(
self._defaults = None
self._target = None
self._max_circuits = configuration.max_experiments
self._session: Session = None
if not self._configuration.simulator:
self.options.set_validator("noise_model", type(None))
self.options.set_validator("seed_simulator", type(None))
Expand Down Expand Up @@ -529,24 +534,44 @@ def _runtime_run(
) -> IBMCircuitJob:
"""Runs the runtime program and returns the corresponding job object"""
hgp_name = self._instance or self.provider._get_hgp().name

session = self._session

if session:
if not session.active:
raise RuntimeError(f"The session {session.session_id} is closed.")
kt474 marked this conversation as resolved.
Show resolved Hide resolved
session_id = session.session_id
max_execution_time = session._max_time
start_session = session_id is None
else:
session_id = None
max_execution_time = None
start_session = False

try:
response = self.provider._runtime_client.program_run(
program_id=program_id,
backend_name=backend_name,
params=inputs,
hgp=hgp_name,
job_tags=job_tags,
session_id=session_id,
start_session=start_session,
max_execution_time=max_execution_time,
image=image,
)
except RequestsApiError as ex:
raise IBMBackendApiError("Error submitting job: {}".format(str(ex))) from ex
session_id = response.get("session_id")
if self._session:
self._session._session_id = session_id
try:
job_id = response["id"]
job = IBMCircuitJob(
backend=self,
api_client=self._api_client,
runtime_client=self.provider._runtime_client,
job_id=job_id,
job_id=response["id"],
session_id=session_id,
)
logger.debug("Job %s was successfully submitted.", job.job_id())
except TypeError as err:
Expand Down Expand Up @@ -810,7 +835,8 @@ def _deprecate_id_instruction(

return circuits

def get_translation_stage_plugin(self) -> str:
@classmethod
def get_translation_stage_plugin(cls) -> str:
"""Return the default translation stage plugin name for IBM backends."""
return "ibm_dynamic_circuits"

Expand Down Expand Up @@ -878,6 +904,24 @@ def _check_faulty(self, circuit: QuantumCircuit) -> None:
f"{instr} operating on a faulty edge {qubit_indices}"
)

def open_session(self, max_time: Optional[Union[int, str]] = None) -> Session:
"""Open session"""
self._session = Session(max_time)
return self._session

@property
def session(self) -> Session:
"""Return session"""
return self._session

def close_session(self) -> None:
"""Close session"""
if self._session:
self._session.close()
if self._session.session_id:
self.provider._runtime_client.close_session(self._session.session_id)
self._session = None


class IBMRetiredBackend(IBMBackend):
"""Backend class interfacing with an IBM Quantum device no longer available."""
Expand Down
8 changes: 7 additions & 1 deletion qiskit_ibm_provider/job/ibm_circuit_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ def __init__(
time_per_step: Optional[dict] = None,
result: Optional[dict] = None,
error: Optional[dict] = None,
session_id: Optional[str] = None,
tags: Optional[List[str]] = None,
run_mode: Optional[str] = None,
client_info: Optional[Dict[str, str]] = None,
Expand All @@ -150,7 +151,12 @@ def __init__(
kwargs: Additional job attributes.
"""
super().__init__(
backend=backend, api_client=api_client, job_id=job_id, name=name, tags=tags
backend=backend,
api_client=api_client,
job_id=job_id,
name=name,
session_id=session_id,
tags=tags,
)
self._runtime_client = runtime_client
self._creation_date = None
Expand Down
3 changes: 3 additions & 0 deletions qiskit_ibm_provider/job/ibm_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ def __init__(
api_client: AccountClient,
job_id: str,
name: Optional[str] = None,
session_id: Optional[str] = None,
tags: Optional[List[str]] = None,
**kwargs: Any
) -> None:
Expand All @@ -52,12 +53,14 @@ def __init__(
api_client: Object for connecting to the server.
job_id: Job ID.
name: Job name.
session_id: Job ID of the first job in a runtime session.
tags: Job tags.
kwargs: Additional job attributes.
"""
Job.__init__(self, backend, job_id)
self._api_client = api_client
self._name = name
self._session_id = session_id
self._tags = tags or []
self._provider = backend.provider

Expand Down
134 changes: 134 additions & 0 deletions qiskit_ibm_provider/session.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# This code is part of Qiskit.
#
# (C) Copyright IBM 2022.
#
# 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 flexible session."""

from typing import Optional, Type, Union
from types import TracebackType
from contextvars import ContextVar

from qiskit_ibm_provider.utils.converters import hms_to_seconds


class Session:
"""Class for creating a flexible Qiskit Runtime session.

A Qiskit Runtime ``session`` allows you to group a collection of iterative calls to
the quantum computer. A session is started when the first job within the session
is started. Subsequent jobs within the session are prioritized by the scheduler.
Data used within a session, such as transpiled circuits, is also cached to avoid
unnecessary overhead.

You can open a Qiskit Runtime session using this ``Session`` class
and submit one or more jobs.

For example::

from qiskit.test.reference_circuits import ReferenceCircuits
from qiskit_ibm_provider import IBMProvider

circ = ReferenceCircuits.bell()
backend = IBMProvider().get_backend("ibmq_qasm_simulator")
backend.open_session()
job = backend.run(circ)
print(f"Job ID: {job.job_id()}")
print(f"Result: {job.result()}")
# Close the session only if all jobs are finished and
# you don't need to run more in the session.
backend.close_session()

Session can also be used as a context manager::

with backend.open_session() as session:
job = backend.run(ReferenceCircuits.bell())
assert job.job_id() == session.session_id

"""

def __init__(
self,
max_time: Optional[Union[int, str]] = None,
):
"""Session constructor.

Args:
max_time: (EXPERIMENTAL setting, can break between releases without warning)
Maximum amount of time, a runtime session can be open before being
forcibly closed. Can be specified as seconds (int) or a string like "2h 30m 40s".
This value must be in between 300 seconds and the
`system imposed maximum
<https://qiskit.org/documentation/partners/qiskit_ibm_runtime/faqs/max_execution_time.html>`_.

Raises:
ValueError: If an input value is invalid.
"""
self._instance = None
self._session_id: Optional[str] = None
self._active = True

self._max_time = (
max_time
if max_time is None or isinstance(max_time, int)
else hms_to_seconds(max_time, "Invalid max_time value: ")
)

@property
def session_id(self) -> str:
"""Return the session ID.

Returns:
Session ID. None until a job runs in the session.
"""
return self._session_id

@property
def active(self) -> bool:
"""Return the status of the session.

Returns:
True if the session is active, False otherwise.
"""
return self._active

def close(self) -> None:
"""Set the session._active status to False"""
self._active = False

def __enter__(self) -> "Session":
set_cm_session(self)
return self

def __exit__(
self,
exc_type: Optional[Type[BaseException]],
exc_val: Optional[BaseException],
exc_tb: Optional[TracebackType],
) -> None:
set_cm_session(None)


# Default session
_DEFAULT_SESSION: ContextVar[Optional[Session]] = ContextVar(
"_DEFAULT_SESSION", default=None
)
_IN_SESSION_CM: ContextVar[bool] = ContextVar("_IN_SESSION_CM", default=False)


def set_cm_session(session: Optional[Session]) -> None:
"""Set the context manager session."""
_DEFAULT_SESSION.set(session)
_IN_SESSION_CM.set(session is not None)


def get_cm_session() -> Session:
"""Return the context managed session."""
return _DEFAULT_SESSION.get()
Loading