Skip to content

Commit

Permalink
Update provider to versioned provider interface (#14)
Browse files Browse the repository at this point in the history
* Update provider to versioned provider interface

This commit updates the honeywell provider to use the latest versioned
abstract interface from terra (which was introduced in 0.16.0). Terra
will be deprecating the legacy interface in the near future, so to get
in front of that we should update the provider to use the current
interface version. At the same time this fixes some compatibility issues
that arise when running with the latest version of terra (which is
necessary because the minimum version is bumped to ensure the new
provider interface exists).

* Fix handling of job and result metadata

In manually testing the job submission against the backend there were
several places where the job metadata was not getting updated correctly.
While this was not fatal and the job would execute successfully and the
counts objects were all correct, the metadata bout the job's result
would not properly set the config or header for a circuit based job.
This commit corrects this issue so that we're properly setting the
config and headers from the job config and circuit metadata.
  • Loading branch information
mtreinish authored Jan 24, 2022
1 parent d9c15a0 commit d9015f3
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 32 deletions.
51 changes: 44 additions & 7 deletions qiskit/providers/honeywell/honeywellbackend.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,17 +27,23 @@
"""Module for interfacing with a Honeywell Backend."""

import logging
import warnings

from qiskit.circuit import QuantumCircuit
from qiskit.exceptions import QiskitError
from qiskit.providers import BaseBackend
from qiskit.providers import BackendV1
from qiskit.providers.models import BackendStatus
from qiskit.providers import Options
from qiskit.utils import deprecate_arguments
from qiskit import qobj as qobj_mod
from qiskit import pulse

from .honeywelljob import HoneywellJob

logger = logging.getLogger(__name__)


class HoneywellBackend(BaseBackend):
class HoneywellBackend(BackendV1):
"""Backend class interfacing with a Honeywell backend."""

def __init__(self, name, configuration, provider, api):
Expand All @@ -56,16 +62,47 @@ def __init__(self, name, configuration, provider, api):
self._api = api
self._name = name

def run(self, qobj):
"""Run a Qobj.
@classmethod
def _default_options(cls):
return Options(shots=1024, priority='normal')

@deprecate_arguments({'qobj': 'run_input'})
def run(self, run_input, **kwargs):
"""Run a circuit on the backend.
Args:
qobj (Qobj): description of job
run_input (QuantumCircuit|list): A QuantumCircuit or a list of
QuantumCircuit objects to run on the backend
Returns:
HoneywelJob: an instance derived from BaseJob
HoneywelJob: a handle to the async execution of the circuit(s) on
the backend
Raises:
QiskitError: If a pulse schedule is passed in for ``run_input``
"""
job = HoneywellJob(self, None, self._api, qobj=qobj)
if isinstance(run_input, qobj_mod.QasmQobj):
warnings.warn("Passing in a QASMQobj object to run() is "
"deprecated and will be removed in a future "
"release", DeprecationWarning)
job = HoneywellJob(self, None, self._api, circuits=run_input)
elif isinstance(run_input, (qobj_mod.PulseQobj, pulse.Schedule)):
raise QiskitError("Pulse jobs are not accepted")
else:
if isinstance(run_input, QuantumCircuit):
run_input = [run_input]
job_config = {}
for kwarg in kwargs:
if not hasattr(self.options, kwarg):
warnings.warn(
"Option %s is not used by this backend" % kwarg,
UserWarning, stacklevel=2)
else:
job_config[kwarg] = kwargs[kwarg]
if 'shots' not in job_config:
job_config['shots'] = self.options.shots
job_config['priority'] = self.options.priority
job = HoneywellJob(self, None, self._api, circuits=run_input,
job_config=job_config)
job.submit()
return job

Expand Down
57 changes: 36 additions & 21 deletions qiskit/providers/honeywell/honeywelljob.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,9 @@
import nest_asyncio
import websockets
from qiskit.assembler.disassemble import disassemble
from qiskit.providers import BaseJob, JobError
from qiskit.providers import JobV1, JobError
from qiskit.providers.jobstatus import JOB_FINAL_STATES, JobStatus
from qiskit.qobj import validate_qobj_against_schema
from qiskit import qobj as qobj_mod
from qiskit.result import Result

from .apiconstants import ApiJobStatus
Expand All @@ -56,7 +56,7 @@
nest_asyncio.apply()


class HoneywellJob(BaseJob):
class HoneywellJob(JobV1):
"""Representation of a job that will be execute on a Honeywell backend.
Represent the jobs that will be executed on Honeywell devices. Jobs are
Expand Down Expand Up @@ -123,21 +123,24 @@ class HoneywellJob(BaseJob):
``JobStatus.ERROR`` and you can call ``error_message()`` to get more
info.
"""
def __init__(self, backend, job_id, api=None, qobj=None):
def __init__(self, backend, job_id, api=None, circuits=None, job_config=None):
"""HoneywellJob init function.
We can instantiate jobs from two sources: A QObj, and an already submitted job returned by
the API servers.
We can instantiate jobs from two sources: A circuit, and an already
submitted job returned by the API servers.
Args:
backend (BaseBackend): The backend instance used to run this job.
backend (HoneywellBackend): The backend instance used to run this job.
job_id (str or None): The job ID of an already submitted job.
Pass `None` if you are creating a new job.
api (HoneywellClient): Honeywell api client.
qobj (Qobj): The Quantum Object. See notes below
circuits (list): A list of quantum circuit objects to run. Can also
be a ``QasmQobj`` object, but this is deprecated (and won't raise a
warning (since it's raised by ``backend.run()``). See notes below
job_config (dict): A dictionary for the job configuration options
Notes:
It is mandatory to pass either ``qobj`` or ``job_id``. Passing a ``qobj``
It is mandatory to pass either ``circuits`` or ``job_id``. Passing a ``circuits``
will ignore ``job_id`` and will create an instance to be submitted to the
API server for job creation. Passing only a `job_id` will create an instance
representing an already-created job retrieved from the API server.
Expand All @@ -148,7 +151,6 @@ def __init__(self, backend, job_id, api=None, qobj=None):
self._api = api
else:
self._api = HoneywellClient(backend.provider().credentials)
print(backend.provider().credentials.api_url)
self._creation_date = datetime.utcnow().replace(tzinfo=timezone.utc).isoformat()

# Properties used for caching.
Expand All @@ -159,23 +161,29 @@ def __init__(self, backend, job_id, api=None, qobj=None):
self._experiment_results = []

self._qobj_payload = {}
if qobj:
validate_qobj_against_schema(qobj)
self._qobj_payload = qobj.to_dict()
# Extract individual experiments
# if we want user qobj headers, the third argument contains it
self._experiments, self._qobj_config, _ = disassemble(qobj)
self._status = JobStatus.INITIALIZING
self._circuits_job = False
if circuits:
if isinstance(circuits, qobj_mod.QasmQobj):
self._qobj_payload = circuits.to_dict()
# Extract individual experiments
# if we want user qobj headers, the third argument contains it
self._experiments, self._job_config, _ = disassemble(circuits)
self._status = JobStatus.INITIALIZING
else:
self._experiments = circuits
self._job_config = job_config
self._circuits_job = True
else:
self._status = JobStatus.INITIALIZING
self._job_ids.append(job_id)
self._job_config = {}

def submit(self):
"""Submit the job to the backend."""
backend_name = self.backend().name()

for exp in self._experiments:
submit_info = self._api.job_submit(backend_name, self._qobj_config, exp.qasm())
submit_info = self._api.job_submit(backend_name, self._job_config, exp.qasm())
# Error in job after submission:
# Transition to the `ERROR` final state.
if 'error' in submit_info:
Expand Down Expand Up @@ -333,13 +341,20 @@ def _process_results(self):
counts = dict(Counter(hex(int("".join(r), 2)) for r in [*zip(*list(res.values()))]))

experiment_result = {
'shots': self._qobj_payload.get('config', {}).get('shots', 1),
'shots': self._job_config.get('shots', 1),
'success': ApiJobStatus(status) is ApiJobStatus.COMPLETED,
'data': {'counts': counts},
'header': self._qobj_payload[
'experiments'][i]['header'] if self._qobj_payload else {},
'job_id': self._job_ids[i]
}
if self._circuits_job:
if self._experiments[i].metadata is None:
metadata = {}
else:
metadata = self._experiments[i].metadata
experiment_result['header'] = metadata
else:
experiment_result['header'] = self._qobj_payload[
'experiments'][i]['header'] if self._qobj_payload else {}
results.append(experiment_result)

result = {
Expand Down
4 changes: 2 additions & 2 deletions qiskit/providers/honeywell/honeywellprovider.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import logging
from collections import OrderedDict

from qiskit.providers import BaseProvider
from qiskit.providers import ProviderV1
from qiskit.providers.models import BackendConfiguration

from .api import HoneywellClient
Expand All @@ -41,7 +41,7 @@
logger = logging.getLogger(__name__)


class HoneywellProvider(BaseProvider):
class HoneywellProvider(ProviderV1):
"""Provider for Honeywell backends."""

def __init__(self):
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
nest-asyncio>=1.2.0
qiskit-terra>=0.10
qiskit-terra>=0.16.0
requests>=2.19
websockets>=7
pyjwt>=1.7.1,<2
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@

requirements = [
'nest-asyncio>=1.2.0',
'qiskit-terra>=0.10',
'qiskit-terra>=0.16.0',
'requests>=2.19',
'websockets>=7',
'pyjwt>=1.7.1,<2',
Expand Down
3 changes: 3 additions & 0 deletions test/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,14 @@

def _has_connection(hostname, port):
"""Checks if internet connection exists to host via specified port.
If any exception is raised while trying to open a socket this will return
false.
Args:
hostname (str): Hostname to connect to.
port (int): Port to connect to
Returns:
bool: Has connection or not
"""
Expand Down

0 comments on commit d9015f3

Please sign in to comment.