Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Retrieve interim results from runtime jobs #15

Merged
merged 12 commits into from
Dec 5, 2021
11 changes: 11 additions & 0 deletions qiskit_ibm_runtime/api/clients/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ def job_results(self, job_id: str) -> str:
"""
return self.api.program_job(job_id).results()

def job_interim_results(self, job_id: str) -> str:
"""Get the interim results of a program job.

Args:
job_id: Program job ID.

Returns:
Job interim results.
"""
return self.api.program_job(job_id).interim_results()

def job_cancel(self, job_id: str) -> None:
"""Cancel a job.

Expand Down
17 changes: 16 additions & 1 deletion qiskit_ibm_runtime/api/rest/runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,13 @@ def update_metadata(
class ProgramJob(RestAdapterBase):
"""Rest adapter for program job related endpoints."""

URL_MAP = {"self": "", "results": "/results", "cancel": "/cancel", "logs": "/logs"}
URL_MAP = {
"self": "",
"results": "/results",
"cancel": "/cancel",
"logs": "/logs",
"interim_results": "/interim_results",
}

def __init__(
self, session: RetrySession, job_id: str, url_prefix: str = ""
Expand All @@ -308,6 +314,15 @@ def delete(self) -> None:
"""Delete program job."""
self.session.delete(self.get_url("self"))

def interim_results(self) -> str:
"""Return program job interim results.

Returns:
Interim results.
"""
response = self.session.get(self.get_url("interim_results"))
return response.text

def results(self) -> str:
"""Return program job results.

Expand Down
24 changes: 24 additions & 0 deletions qiskit_ibm_runtime/runtime_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,13 +109,15 @@ def __init__(
self._backend = backend
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
self._program_id = program_id
self._status = JobStatus.INITIALIZING
self._error_message = None # type: Optional[str]
self._result_decoder = result_decoder
self._image = image
self._final_interim_results = False

# Used for streaming
self._ws_client_future = None # type: Optional[futures.Future]
Expand All @@ -130,6 +132,28 @@ def __init__(
if user_callback is not None:
self.stream_results(user_callback)

def interim_results(self, decoder: Optional[Type[ResultDecoder]] = None) -> Any:
"""Return the interim results of the job.

Args:
decoder: A :class:`ResultDecoder` subclass used to decode interim results.

Returns:
Runtime job interim results.

Raises:
RuntimeJobFailureError: If the job failed.
"""
if not self._final_interim_results:
_decoder = decoder or self._result_decoder
interim_results_raw = self._api_client.job_interim_results(
job_id=self.job_id
)
self._interim_results = _decoder.decode(interim_results_raw)
if self.status() in JOB_FINAL_STATES:
self._final_interim_results = True
return self._interim_results

def result(
self,
timeout: Optional[float] = None,
Expand Down
7 changes: 7 additions & 0 deletions releasenotes/notes/interim-results-b5a18a3784063d56.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
features:
- |
You can now use the :meth:`qiskit_ibm_runtime.RuntimeJob.interim_results`
method to retrieve runtime program interim results.
Note that interim results will only be available for
up to two days.
9 changes: 9 additions & 0 deletions test/ibm/runtime/fake_runtime_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ def __init__(
self._backend_name = backend_name
self._params = params
self._image = image
self._interim_results = json.dumps("foo")
if final_status is None:
self._future = self._executor.submit(self._auto_progress)
self._result = None
Expand Down Expand Up @@ -143,6 +144,10 @@ def result(self):
"""Return job result."""
return self._result

def interim_results(self):
"""Return job interim results."""
return self._interim_results


class FailedRuntimeJob(BaseFakeRuntimeJob):
"""Class for faking a failed runtime job."""
Expand Down Expand Up @@ -422,6 +427,10 @@ def job_results(self, job_id):
"""Get the results of a program job."""
return self._get_job(job_id).result()

def job_interim_results(self, job_id):
"""Get the interim results of a program job."""
return self._get_job(job_id).interim_results()

def job_cancel(self, job_id):
"""Cancel the job."""
self._get_job(job_id).cancel()
Expand Down
6 changes: 6 additions & 0 deletions test/ibm/runtime/test_runtime.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,12 @@ def test_final_result(self):
result = job.result()
self.assertTrue(result)

def test_interim_results(self):
"""Test getting interim results."""
job = self._run_program()
interim_results = job.interim_results()
self.assertTrue(interim_results)

def test_job_status(self):
"""Test job status."""
job = self._run_program()
Expand Down
8 changes: 8 additions & 0 deletions test/ibm/runtime/test_runtime_integration.py
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,14 @@ def result_callback(job_id, interim_result):
self.assertFalse(called_back)
self.assertIsNotNone(job._ws_client._server_close_code)

def test_retrieve_interim_results(self):
"""Test retrieving interim results with API endpoint"""
int_res = "foo"
job = self._run_program(interim_results=int_res)
job.wait_for_final_state()
interim_results = job.interim_results()
self.assertIn(int_res, interim_results[0])

def test_callback_error(self):
"""Test error in callback method."""

Expand Down