-
Notifications
You must be signed in to change notification settings - Fork 2.4k
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
get jobs from IBM Q backend #501
Changes from 29 commits
bcac889
cd825f4
93aa378
3df6680
e00a1b5
a49ea94
5909a0d
f4e9898
5a77bd9
641becc
8284926
a72d9ec
f437e0a
3d5904e
c9fb95f
97abf82
0afb90d
7516e7a
674af67
2b79d6b
0cfd4cb
00cd596
d7d1a4f
10d0bc2
2e72457
49edb41
8581dcb
11d859b
a426d1b
c2d1033
cee799c
13274e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -24,7 +24,6 @@ class Result(object): | |
|
||
Internal:: | ||
|
||
qobj = { -- the quantum object that was complied --} | ||
result = { | ||
"job_id": --job-id (string), | ||
#This string links the result with the job that computes it, | ||
|
@@ -45,9 +44,8 @@ class Result(object): | |
} | ||
""" | ||
|
||
def __init__(self, qobj_result, qobj): | ||
def __init__(self, qobj_result): | ||
self._result = qobj_result | ||
self._qobj = qobj | ||
|
||
def __str__(self): | ||
"""Get the status of the run. | ||
|
@@ -73,30 +71,20 @@ def __iadd__(self, other): | |
Raises: | ||
QISKitError: if the Results cannot be combined. | ||
""" | ||
# TODO: reevaluate if moving equality to Backend themselves (part of | ||
# todo: reevaluate if moving equality to Backend themselves (part of | ||
# a bigger problem - backend instances will not persist between | ||
# sessions) | ||
my_config = copy.deepcopy(self._qobj['config']) | ||
other_config = copy.deepcopy(other._qobj['config']) | ||
my_backend = my_config.pop('backend_name') | ||
other_backend = other_config.pop('backend_name') | ||
|
||
if my_config == other_config and my_backend == other_backend: | ||
if isinstance(self._qobj['id'], str): | ||
self._qobj['id'] = [self._qobj['id']] | ||
self._qobj['id'].append(other._qobj['id']) | ||
self._qobj['circuits'] += other._qobj['circuits'] | ||
this_backend = self._result.get('backend_name') | ||
other_backend = other._result.get('backend_name') | ||
if this_backend == other_backend: | ||
self._result['result'] += other._result['result'] | ||
return self | ||
else: | ||
raise QISKitError('Result objects have different configs and cannot be combined.') | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Don't we still need to do some checks in order to make sure the results can be combined, and if not raise this QISKitError? For example one of the checks is to make sure they come from the same backend. Previously this was done based on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Previously it checked that the 'config' section of the qobj matched, however, perhaps we can just match the backend_name since I don't think 'shots' or 'max_credits' mismatch should raise an error. |
||
raise QISKitError('Result objects from different backends cannot be combined.') | ||
|
||
def __add__(self, other): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does the docstring of this method need updating? I think |
||
"""Combine Result objects. | ||
|
||
Note that the qobj id of the returned result will be the same as the | ||
first result. | ||
|
||
Arg: | ||
other (Result): a Result object to combine. | ||
Returns: | ||
|
@@ -110,7 +98,7 @@ def _is_error(self): | |
return self._result['status'] == 'ERROR' | ||
|
||
def get_status(self): | ||
"""Return whole qobj result status.""" | ||
"""Return whole result status.""" | ||
return self._result['status'] | ||
|
||
def circuit_statuses(self): | ||
|
@@ -152,10 +140,9 @@ def get_ran_qasm(self, name): | |
QISKitError: if the circuit was not found. | ||
""" | ||
try: | ||
qobj = self._qobj | ||
for index in range(len(qobj["circuits"])): | ||
if qobj["circuits"][index]['name'] == name: | ||
return qobj["circuits"][index]["compiled_circuit_qasm"] | ||
for exp_result in self._result['result']: | ||
if exp_result.get('name') == name: | ||
return exp_result['compiled_circuit_qasm'] | ||
except KeyError: | ||
pass | ||
raise QISKitError('No qasm for circuit "{0}"'.format(name)) | ||
|
@@ -211,20 +198,18 @@ def get_data(self, circuit=None): | |
circuit = circuit.name | ||
|
||
if circuit is None: | ||
circuits = list([i['name'] for i in self._qobj['circuits']]) | ||
if len(circuits) == 1: | ||
circuit = circuits[0] | ||
if len(self._result['result']) == 1: | ||
return self._result['result'][0]['data'] | ||
else: | ||
raise QISKitError("You have to select a circuit when there is more than" | ||
"one available") | ||
|
||
if not isinstance(circuit, str): | ||
circuit = str(circuit) | ||
try: | ||
qobj = self._qobj | ||
for index in range(len(qobj['circuits'])): | ||
if qobj['circuits'][index]['name'] == circuit: | ||
return self._result['result'][index]['data'] | ||
for circuit_result in self._result['result']: | ||
if circuit_result.get('name') == circuit: | ||
return circuit_result['data'] | ||
except (KeyError, TypeError): | ||
pass | ||
raise QISKitError('No data for circuit "{0}"'.format(circuit)) | ||
|
@@ -365,7 +350,7 @@ def get_names(self): | |
Returns: | ||
List: A list of circuit names. | ||
""" | ||
return [c['name'] for c in self._qobj['circuits']] | ||
return [c.get('name') for c in self._result['result']] | ||
|
||
def average_data(self, name, observable): | ||
"""Compute the mean value of an diagonal observable. | ||
|
@@ -390,22 +375,22 @@ def average_data(self, name, observable): | |
temp += counts[key] * observable[key] / tot | ||
return temp | ||
|
||
def get_qubitpol_vs_xval(self, xvals_dict=None): | ||
def get_qubitpol_vs_xval(self, nqubits, xvals_dict=None): | ||
"""Compute the polarization of each qubit for all circuits and pull out each circuits | ||
xval into an array. Assumes that each circuit has the same number of qubits and that | ||
all qubits are measured. | ||
|
||
Args: | ||
nqubits (int): number of qubits | ||
xvals_dict (dict): xvals for each circuit {'circuitname1': xval1,...}. If this | ||
is none then the xvals list is just left as an array of zeros | ||
|
||
Returns: | ||
qubit_pol: mxn double array where m is the number of circuit, n the number of qubits | ||
xvals: mx1 array of the circuit xvals | ||
""" | ||
ncircuits = len(self._qobj['circuits']) | ||
ncircuits = len(self._result['result']) | ||
# Is this the best way to get the number of qubits? | ||
nqubits = self._qobj['circuits'][0]['compiled_circuit']['header']['number_of_qubits'] | ||
qubitpol = numpy.zeros([ncircuits, nqubits], dtype=float) | ||
xvals = numpy.zeros([ncircuits], dtype=float) | ||
|
||
|
@@ -421,10 +406,11 @@ def get_qubitpol_vs_xval(self, xvals_dict=None): | |
|
||
# go through each circuit and for eqch qubit and apply the operators using "average_data" | ||
for circuit_ind in range(ncircuits): | ||
circuit_name = self._result['result'][circuit_ind]['name'] | ||
if xvals_dict: | ||
xvals[circuit_ind] = xvals_dict[self._qobj['circuits'][circuit_ind]['name']] | ||
xvals[circuit_ind] = xvals_dict[circuit_name] | ||
for qubit_ind in range(nqubits): | ||
qubitpol[circuit_ind, qubit_ind] = self.average_data( | ||
self._qobj['circuits'][circuit_ind]['name'], z_dicts[qubit_ind]) | ||
circuit_name, z_dicts[qubit_ind]) | ||
|
||
return qubitpol, xvals |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,8 +11,10 @@ | |
""" | ||
import logging | ||
|
||
from qiskit import QISKitError | ||
from qiskit._util import _camel_case_to_snake_case | ||
from qiskit.backends import BaseBackend | ||
from qiskit.backends import JobStatus | ||
from qiskit.backends.ibmq.ibmqjob import IBMQJob | ||
|
||
logger = logging.getLogger(__name__) | ||
|
@@ -139,3 +141,58 @@ def status(self): | |
raise LookupError( | ||
"Couldn't get backend status: {0}".format(ex)) | ||
return status | ||
|
||
def jobs(self, limit=50, skip=0, status=None): | ||
"""Attempt to get the jobs submitted to the backend | ||
|
||
Args: | ||
limit (int): number of jobs to retrieve | ||
skip (int): starting index of retrieval | ||
status (None or JobStatus or str): only get jobs with this status, | ||
where status is e.g. JobStatus.RUNNING or 'RUNNING'. | ||
Returns: | ||
list(IBMQJob): list of IBMQJob instances | ||
""" | ||
backend_name = self.configuration['name'] | ||
job_list = [] | ||
base_index = skip | ||
job_info_list = [] | ||
if isinstance(status, str): | ||
status = JobStatus[status] | ||
while len(job_list) < limit or len(job_info_list) < limit: | ||
job_info_list = self._api.get_jobs(limit=limit, skip=base_index, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. With the latest version of IBMQuantumExperience package (1.9.4), we can pass a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That's great! Thanks for getting that through. |
||
backend=backend_name) | ||
for job_info in job_info_list: | ||
is_device = not bool(self._configuration.get('simulator')) | ||
job = IBMQJob.from_api(job_info, self._api, is_device) | ||
if len(job_list) < limit: | ||
if status is None: | ||
job_list.append(job) | ||
elif job.status.get('status') == status: | ||
job_list.append(job) | ||
base_index += limit | ||
return job_list | ||
|
||
def retrieve_job(self, job_id): | ||
"""Attempt to get the specified job by job_id | ||
|
||
Args: | ||
job_id (str): the job id of the job to retrieve | ||
|
||
Returns: | ||
IBMQJob: class instance | ||
|
||
Raises: | ||
IBMQBackendError: if retrieval failed | ||
""" | ||
job_info = self._api.get_job(job_id) | ||
if 'error' in job_info: | ||
raise IBMQBackendError('failed to get job id "{}"'.format(job_id)) | ||
is_device = not bool(self._configuration.get('simulator')) | ||
job = IBMQJob.from_api(job_info, self._api, is_device) | ||
return job | ||
|
||
|
||
class IBMQBackendError(QISKitError): | ||
"""IBM Q Backend Errors""" | ||
pass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think now that
Result._qobj
is removed, we can unpackResult._result
, and not have so many levels of indirection. That means havingResult._job_id
,Result._status
,Result._result
, where the latter contains all the data.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This sounds ok to me.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can leave this for a subsequent PR since it's not directly related to this one?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yeah i think we should leave this, also because it's not clear how qobj and result may be represented together later.
I think the most important thing in this PR is to not give errors when
metadata
is unavailable from a past job.