Skip to content

Commit

Permalink
Remove caching of results in RuntimeJob (#1205)
Browse files Browse the repository at this point in the history
* Removed storing result in RuntimeJob._results. Instead retrieve results every time the results() method is called

* Release note

---------

Co-authored-by: kevin-tian <[email protected]>
  • Loading branch information
merav-aharoni and kt474 authored Nov 8, 2023
1 parent e6e7a3b commit e9f93ec
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 32 deletions.
1 change: 1 addition & 0 deletions qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -787,6 +787,7 @@ def _runtime_run(
program_id=program_id,
session_id=session_id,
service=self.service,
tags=job_tags,
)
logger.debug("Job %s was successfully submitted.", job.job_id())
except TypeError as err:
Expand Down
32 changes: 14 additions & 18 deletions qiskit_ibm_runtime/runtime_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@ def __init__(
"""
super().__init__(backend=backend, job_id=job_id)
self._api_client = api_client
self._results: Optional[Any] = None
self._interim_results: Optional[Any] = None
self._params = params or {}
self._creation_date = creation_date
Expand Down Expand Up @@ -212,25 +211,22 @@ def result( # pylint: disable=arguments-differ
RuntimeInvalidStateError: If the job was cancelled, and attempting to retrieve result.
"""
_decoder = decoder or self._final_result_decoder
if self._results is None or (_decoder != self._final_result_decoder):
self.wait_for_final_state(timeout=timeout)
if self._status == JobStatus.ERROR:
error_message = self._reason if self._reason else self._error_message
if self._reason == "RAN TOO LONG":
raise RuntimeJobMaxTimeoutError(error_message)
raise RuntimeJobFailureError(f"Unable to retrieve job result. {error_message}")
if self._status is JobStatus.CANCELLED:
raise RuntimeInvalidStateError(
"Unable to retrieve result for job {}. "
"Job was cancelled.".format(self.job_id())
)

result_raw = self._download_external_result(
self._api_client.job_results(job_id=self.job_id())
self.wait_for_final_state(timeout=timeout)
if self._status == JobStatus.ERROR:
error_message = self._reason if self._reason else self._error_message
if self._reason == "RAN TOO LONG":
raise RuntimeJobMaxTimeoutError(error_message)
raise RuntimeJobFailureError(f"Unable to retrieve job result. {error_message}")
if self._status is JobStatus.CANCELLED:
raise RuntimeInvalidStateError(
"Unable to retrieve result for job {}. " "Job was cancelled.".format(self.job_id())
)

self._results = _decoder.decode(result_raw) if result_raw else None
return self._results
result_raw = self._download_external_result(
self._api_client.job_results(job_id=self.job_id())
)

return _decoder.decode(result_raw) if result_raw else None

def cancel(self) -> None:
"""Cancel the job.
Expand Down
5 changes: 3 additions & 2 deletions qiskit_ibm_runtime/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
IAMAuthenticator,
)
from ibm_platform_services import ResourceControllerV2 # pylint: disable=import-error
from qiskit_ibm_runtime.exceptions import IBMInputValueError


def validate_job_tags(job_tags: Optional[List[str]]) -> None:
Expand All @@ -36,12 +37,12 @@ def validate_job_tags(job_tags: Optional[List[str]]) -> None:
job_tags: Job tags to be validated.
Raises:
ValueError: If the job tags are invalid.
IBMInputValueError: If the job tags are invalid.
"""
if job_tags and (
not isinstance(job_tags, list) or not all(isinstance(tag, str) for tag in job_tags)
):
raise ValueError("job_tags needs to be a list of strings.")
raise IBMInputValueError("job_tags needs to be a list of strings.")


def get_iam_api_url(cloud_url: str) -> str:
Expand Down
5 changes: 5 additions & 0 deletions releasenotes/notes/no_cached_results-54d063390b9b0ae6.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
Removed storing result in ``RuntimeJob._results``. Instead retrieve results every time the
``results()`` method is called.
7 changes: 3 additions & 4 deletions test/integration/test_ibm_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,9 +286,8 @@ def test_retrieve_jobs_order(self):
)
self.assertNotIn(job.job_id(), [rjob.job_id() for rjob in oldest_jobs])

@skip("how do we support refresh")
def test_refresh_job_result(self):
"""Test re-retrieving job result via refresh."""
"""Test re-retrieving job result."""
result = self.sim_job.result()

# Save original cached results.
Expand All @@ -300,8 +299,8 @@ def test_refresh_job_result(self):
self.assertNotEqual(cached_result, result.to_dict())
self.assertEqual(result.results[0].header.name, "modified_result")

# Re-retrieve result via refresh.
result = self.sim_job.result(refresh=True)
# Re-retrieve result.
result = self.sim_job.result()
self.assertDictEqual(cached_result, result.to_dict())
self.assertNotEqual(result.results[0].header.name, "modified_result")

Expand Down
33 changes: 25 additions & 8 deletions test/integration/test_ibm_job_attributes.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@
from qiskit.providers.jobstatus import JobStatus, JOB_FINAL_STATES
from qiskit.test.reference_circuits import ReferenceCircuits

from qiskit_ibm_provider.exceptions import (
IBMBackendValueError,
)
from qiskit_ibm_provider.exceptions import IBMBackendValueError

from qiskit_ibm_runtime import IBMBackend, RuntimeJob
from qiskit_ibm_runtime.exceptions import IBMInputValueError
from ..decorators import (
IntegrationTestDependencies,
integration_test_setup,
Expand Down Expand Up @@ -88,7 +87,7 @@ def test_job_creation_date(self):
)

def test_esp_readout_not_enabled(self):
"""Test that an error is thrown is ESP readout is used and the backend does not support it."""
"""Test that an error is thrown if ESP readout is used and the backend does not support it."""
# sim backend does not have ``measure_esp_enabled`` flag: defaults to ``False``
with self.assertRaises(IBMBackendValueError) as context_manager:
self.sim_backend.run(self.bell, use_measure_esp=True)
Expand Down Expand Up @@ -147,14 +146,32 @@ def test_job_tags(self):
len(rjobs), 1, "Expected job {}, got {}".format(job.job_id(), rjobs)
)
self.assertEqual(rjobs[0].job_id(), job.job_id())
# TODO check why this sometimes fails
# self.assertEqual(set(rjobs[0].tags()), set(job_tags))
self.assertEqual(set(rjobs[0].tags), set(job_tags))

def test_job_tags_replace(self):
"""Test updating job tags by replacing a job's existing tags."""
initial_job_tags = [uuid.uuid4().hex[:16]]
job = self.sim_backend.run(self.bell, job_tags=initial_job_tags)

tags_to_replace_subtests = [
[], # empty tags.
list("{}_new_tag_{}".format(uuid.uuid4().hex[:5], i) for i in range(2)), # unique tags.
initial_job_tags + ["foo"],
]
for tags_to_replace in tags_to_replace_subtests:
with self.subTest(tags_to_replace=tags_to_replace):
# Update the job tags.
_ = job.update_tags(new_tags=tags_to_replace)

# Wait a bit so we don't get cached results.
time.sleep(2)
self.assertEqual(set(tags_to_replace), set(job.tags))

def test_invalid_job_tags(self):
"""Test using job tags with an and operator."""
self.assertRaises(ValueError, self.sim_backend.run, self.bell, job_tags={"foo"})
self.assertRaises(IBMInputValueError, self.sim_backend.run, self.bell, job_tags={"foo"})
self.assertRaises(
ValueError,
IBMInputValueError,
self.service.jobs,
job_tags=[1, 2, 3],
)
Expand Down

0 comments on commit e9f93ec

Please sign in to comment.