diff --git a/projectq/backends/_ibm/_ibm.py b/projectq/backends/_ibm/_ibm.py index d14dbc27f..b1899f043 100755 --- a/projectq/backends/_ibm/_ibm.py +++ b/projectq/backends/_ibm/_ibm.py @@ -46,6 +46,7 @@ class IBMBackend(BasicEngine): """ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, user=None, password=None, device='ibmqx4', + num_retries=3000, interval=1, retrieve_execution=None): """ Initialize the Backend object. @@ -62,6 +63,11 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, password (string): IBM Quantum Experience password device (string): Device to use ('ibmqx4', or 'ibmqx5') if use_hardware is set to True. Default is ibmqx4. + num_retries (int): Number of times to retry to obtain + results from the IBM API. (default is 3000) + interval (float, int): Number of seconds between successive + attempts to obtain results from the IBM API. + (default is 1) retrieve_execution (int): Job ID to retrieve instead of re- running the circuit (e.g., if previous run timed out). """ @@ -75,6 +81,8 @@ def __init__(self, use_hardware=False, num_runs=1024, verbose=False, self._verbose = verbose self._user = user self._password = password + self._num_retries = num_retries + self._interval = interval self._probabilities = dict() self.qasm = "" self._measured_ids = [] @@ -256,11 +264,17 @@ def _run(self): if self._retrieve_execution is None: res = send(info, device=self.device, user=self._user, password=self._password, - shots=self._num_runs, verbose=self._verbose) + shots=self._num_runs, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) else: res = retrieve(device=self.device, user=self._user, password=self._password, - jobid=self._retrieve_execution) + jobid=self._retrieve_execution, + num_retries=self._num_retries, + interval=self._interval, + verbose=self._verbose) counts = res['data']['counts'] # Determine random outcome diff --git a/projectq/backends/_ibm/_ibm_http_client.py b/projectq/backends/_ibm/_ibm_http_client.py index 954a94521..d713b17fa 100755 --- a/projectq/backends/_ibm/_ibm_http_client.py +++ b/projectq/backends/_ibm/_ibm_http_client.py @@ -17,6 +17,7 @@ import requests import getpass import json +import signal import sys import time from requests.compat import urljoin @@ -35,7 +36,8 @@ def is_online(device): return r.json()['state'] -def retrieve(device, user, password, jobid): +def retrieve(device, user, password, jobid, num_retries=3000, + interval=1, verbose=False): """ Retrieves a previously run job by its ID. @@ -46,12 +48,13 @@ def retrieve(device, user, password, jobid): jobid (str): Id of the job to retrieve """ user_id, access_token = _authenticate(user, password) - res = _get_result(device, jobid, access_token) + res = _get_result(device, jobid, access_token, num_retries=num_retries, + interval=interval, verbose=verbose) return res def send(info, device='sim_trivial_2', user=None, password=None, - shots=1, verbose=False): + shots=1, num_retries=3000, interval=1, verbose=False): """ Sends QASM through the IBM API and runs the quantum circuit. @@ -84,7 +87,9 @@ def send(info, device='sim_trivial_2', user=None, password=None, execution_id = _run(info, device, user_id, access_token, shots) if verbose: print("- Waiting for results...") - res = _get_result(device, execution_id, access_token) + res = _get_result(device, execution_id, access_token, + num_retries=num_retries, + interval=interval, verbose=verbose) if verbose: print("- Done.") return res @@ -143,32 +148,47 @@ def _run(qasm, device, user_id, access_token, shots): def _get_result(device, execution_id, access_token, num_retries=3000, - interval=1): + interval=1, verbose=False): suffix = 'Jobs/{execution_id}'.format(execution_id=execution_id) status_url = urljoin(_api_url, 'Backends/{}/queue/status'.format(device)) - print("Waiting for results. [Job ID: {}]".format(execution_id)) - - for retries in range(num_retries): - r = requests.get(urljoin(_api_url, suffix), - params={"access_token": access_token}) - r.raise_for_status() - - r_json = r.json() - if 'qasms' in r_json: - qasm = r_json['qasms'][0] - if 'result' in qasm and qasm['result'] is not None: - return qasm['result'] - time.sleep(interval) - if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: - r = requests.get(status_url) + if verbose: + print("Waiting for results. [Job ID: {}]".format(execution_id)) + + original_sigint_handler = signal.getsignal(signal.SIGINT) + + def _handle_sigint_during_get_result(*_): + raise Exception("Interrupted. The ID of your submitted job is {}." + .format(execution_id)) + + try: + signal.signal(signal.SIGINT, _handle_sigint_during_get_result) + + for retries in range(num_retries): + r = requests.get(urljoin(_api_url, suffix), + params={"access_token": access_token}) + r.raise_for_status() r_json = r.json() - if 'state' in r_json and not r_json['state']: - raise DeviceOfflineError("Device went offline. The ID of your " - "submitted job is {}." - .format(execution_id)) - if 'lengthQueue' in r_json: - print("Currently there are {} jobs queued for execution on {}." - .format(r_json['lengthQueue'], device)) + if 'qasms' in r_json: + qasm = r_json['qasms'][0] + if 'result' in qasm and qasm['result'] is not None: + return qasm['result'] + time.sleep(interval) + if device in ['ibmqx4', 'ibmqx5'] and retries % 60 == 0: + r = requests.get(status_url) + r_json = r.json() + if 'state' in r_json and not r_json['state']: + raise DeviceOfflineError("Device went offline. The ID of " + "your submitted job is {}." + .format(execution_id)) + if verbose and 'lengthQueue' in r_json: + print("Currently there are {} jobs queued for execution " + "on {}." + .format(r_json['lengthQueue'], device)) + + finally: + if original_sigint_handler is not None: + signal.signal(signal.SIGINT, original_sigint_handler) + raise Exception("Timeout. The ID of your submitted job is {}." .format(execution_id))