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

Support IBMBackend.run() #1138

Merged
merged 70 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from 69 commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
2ff792e
initial support for backend.run()
merav-aharoni Oct 9, 2023
c877f1e
Added temporary session support
merav-aharoni Oct 9, 2023
cb4c0b0
Merge branch 'main' into backend_run
merav-aharoni Oct 9, 2023
af9f907
Copied test_backend.py from the provider
merav-aharoni Oct 10, 2023
7ce7531
Merge branch 'main' of github.com:merav-aharoni/qiskit-ibm-runtime in…
merav-aharoni Oct 10, 2023
cc6d58a
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Oct 10, 2023
7867c17
Added all status types from the provider
merav-aharoni Oct 10, 2023
0c4e0cb
Added test_ibm_job_states.py from provider. Added 'transpiler' direct…
merav-aharoni Oct 10, 2023
17c4ec2
lint
merav-aharoni Oct 10, 2023
7b9f6cc
black
merav-aharoni Oct 10, 2023
aab2a87
lint
merav-aharoni Oct 10, 2023
71cb86d
Added integration tests from the provider
merav-aharoni Oct 12, 2023
0878511
Merge branch 'main' into backend_run
merav-aharoni Oct 12, 2023
d0a5a5b
added test_ibm_job and made necessary changes
merav-aharoni Oct 15, 2023
e99cd2e
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Oct 15, 2023
8ca95c5
Fixed several tests
merav-aharoni Oct 16, 2023
b17413b
Fixed missing job methods in test
merav-aharoni Oct 16, 2023
2a95e9b
Changed exception type
merav-aharoni Oct 16, 2023
40917a4
Added test_ibm_job_attributes.py
merav-aharoni Oct 16, 2023
d117151
Added test_ibm_job_attributes.py that was missed in previous commit
merav-aharoni Oct 17, 2023
e72524d
Added test class TestBackendRunInSession for backend.run with session
merav-aharoni Oct 17, 2023
03c8fb8
Cleaning up code
merav-aharoni Oct 17, 2023
ac1e464
lint, added missing parameter
merav-aharoni Oct 17, 2023
e5ac535
Merge branch 'main' into backend_run
merav-aharoni Oct 17, 2023
ef7b314
Merge branch 'main' into backend_run
merav-aharoni Oct 19, 2023
535c875
Added more tests from qiskit-ibm-provider
merav-aharoni Oct 19, 2023
f173124
Inherit from BaseQiskitTestCase
merav-aharoni Oct 19, 2023
21103e6
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Oct 19, 2023
757af8e
Enabled several tests
merav-aharoni Oct 19, 2023
862723e
removed method _deprecate_id_instruction
merav-aharoni Oct 22, 2023
3697ec3
lint, unused imports
merav-aharoni Oct 22, 2023
373ace1
Merge branch 'main' into backend_run
merav-aharoni Oct 22, 2023
b74f848
Merge branch 'main' into backend_run
kt474 Oct 24, 2023
b9dcf5e
Merge branch 'main' into backend_run
merav-aharoni Oct 26, 2023
e98456b
Removed instance parameter from tests with backend.run()
merav-aharoni Oct 26, 2023
3806d0a
Removed instance from decorator
merav-aharoni Oct 26, 2023
191837e
Changed test to run on quantum channel only
merav-aharoni Oct 26, 2023
ddcc326
Removed instance parameter when getting backend
merav-aharoni Oct 26, 2023
2f24d01
lint
merav-aharoni Oct 26, 2023
f900340
Copied transpiler directory from the provider
merav-aharoni Oct 26, 2023
60c5ecd
black
merav-aharoni Oct 26, 2023
c3b8b79
Merge branch 'main' into backend_run
merav-aharoni Oct 26, 2023
5906284
fix more tests
kt474 Oct 27, 2023
9b8d4aa
update test_session
kt474 Oct 27, 2023
064a8c0
added tranpiler passes entry point
merav-aharoni Oct 29, 2023
f53e227
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Oct 29, 2023
15daa81
Merge branch 'main' into backend_run
merav-aharoni Oct 29, 2023
7d1c885
Removed obsolete JobStatus types, and removed the tests that were che…
merav-aharoni Oct 29, 2023
99c27f0
Removed unnecessary check
merav-aharoni Oct 29, 2023
37f2bf1
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Oct 29, 2023
ebf5647
Removed exception parameter from validate_job_tags. Use 'import_job_t…
merav-aharoni Oct 29, 2023
349e12d
Put back the check if circuit is indeed of type 'QuantumCircuit'. Upd…
merav-aharoni Oct 29, 2023
db9afb4
Update qiskit_ibm_runtime/ibm_backend.py
merav-aharoni Oct 31, 2023
7bb3447
Cleaned up code involving session setup
merav-aharoni Oct 31, 2023
bdbcdae
Removed setting of 'skip_transpilation' because set by default by Qasm3
merav-aharoni Oct 31, 2023
c13d879
Replaced in path 'qiskit-ibm-provider' with 'qiskit-ibm-runtime'.
merav-aharoni Oct 31, 2023
9f9c099
Added None to get() statement
merav-aharoni Oct 31, 2023
bd10133
Merge branch 'main' into backend_run
kt474 Oct 31, 2023
f3b295e
Merge branch 'main' into backend_run
merav-aharoni Nov 2, 2023
b78acfc
Changed warning to error when init_circuit is boolean
merav-aharoni Nov 6, 2023
7a53693
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Nov 6, 2023
f85c85c
Merge branch 'main' into backend_run
merav-aharoni Nov 6, 2023
c004bbb
Fixed setting of start_session
merav-aharoni Nov 6, 2023
e6022c2
Removed max_time parameter, because wasn't reaching the server.
merav-aharoni Nov 6, 2023
db9534c
Merge branch 'main' into backend_run
kt474 Nov 6, 2023
7dbe1d4
Release note
merav-aharoni Nov 7, 2023
4edc1d4
Merge branch 'backend_run' of github.com:merav-aharoni/qiskit-ibm-run…
merav-aharoni Nov 7, 2023
9c71052
Merge branch 'main' into backend_run
merav-aharoni Nov 7, 2023
47c5ceb
Merge branch 'main' into backend_run
kt474 Nov 7, 2023
12fa82f
address comment
kt474 Nov 7, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 297 additions & 10 deletions qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@
"""Module for interfacing with an IBM Quantum Backend."""

import logging

from typing import Iterable, Union, Optional, Any, List
from typing import Iterable, Union, Optional, Any, List, Dict
from datetime import datetime as python_datetime
from copy import deepcopy
from dataclasses import asdict
import warnings

from qiskit import QuantumCircuit
from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit.tools.events.pubsub import Publisher

from qiskit.providers.backend import BackendV2 as Backend
from qiskit.providers.options import Options
from qiskit.providers.models import (
Expand All @@ -42,11 +45,19 @@
defaults_from_server_data,
properties_from_server_data,
)
from qiskit_ibm_provider.utils import local_to_utc
from qiskit_ibm_provider.utils import local_to_utc, are_circuits_dynamic
from qiskit_ibm_provider.utils.options import QASM2Options, QASM3Options
from qiskit_ibm_provider.exceptions import IBMBackendValueError, IBMBackendApiError
from qiskit_ibm_provider.api.exceptions import RequestsApiError

from qiskit_ibm_runtime import ( # pylint: disable=unused-import,cyclic-import
qiskit_runtime_service,
)
# temporary until we unite the 2 Session classes
from qiskit_ibm_provider.session import (
Session as ProviderSession,
) # temporary until we unite the 2 Session classes
kt474 marked this conversation as resolved.
Show resolved Hide resolved

from .utils.utils import validate_job_tags
from . import qiskit_runtime_service # pylint: disable=unused-import,cyclic-import
from .runtime_job import RuntimeJob

from .api.clients import RuntimeClient
from .api.clients.backend import BaseBackendClient
Expand All @@ -57,6 +68,9 @@

logger = logging.getLogger(__name__)

QOBJRUNNERPROGRAMID = "circuit-runner"
QASM3RUNNERPROGRAMID = "qasm3-runner"


class IBMBackend(Backend):
"""Backend class interfacing with an IBM Quantum backend.
Expand Down Expand Up @@ -180,6 +194,7 @@ def __init__(
self._defaults = None
self._target = None
self._max_circuits = configuration.max_experiments
self._session: ProviderSession = None
if (
not self._configuration.simulator
and hasattr(self.options, "noise_model")
Expand Down Expand Up @@ -492,10 +507,25 @@ def __call__(self) -> "IBMBackend":
# For backward compatibility only, can be removed later.
return self

def run(self, *args: Any, **kwargs: Any) -> None:
"""Not supported method"""
# pylint: disable=arguments-differ
raise RuntimeError("IBMBackend.run() is not supported in the Qiskit Runtime environment.")
def _check_circuits_attributes(self, circuits: List[Union[QuantumCircuit, str]]) -> None:
"""Check that circuits can be executed on backend.
Raises:
IBMBackendValueError:
- If one of the circuits contains more qubits than on the backend."""

if len(circuits) > self._max_circuits:
raise IBMBackendValueError(
f"Number of circuits, {len(circuits)} exceeds the "
f"maximum for this backend, {self._max_circuits})"
)
for circ in circuits:
if isinstance(circ, QuantumCircuit):
merav-aharoni marked this conversation as resolved.
Show resolved Hide resolved
if circ.num_qubits > self._configuration.num_qubits:
raise IBMBackendValueError(
f"Circuit contains {circ.num_qubits} qubits, "
f"but backend has only {self.num_qubits}."
)
self.check_faulty(circ)

def check_faulty(self, circuit: QuantumCircuit) -> None:
"""Check if the input circuit uses faulty qubits or edges.
Expand Down Expand Up @@ -549,6 +579,263 @@ def __deepcopy__(self, _memo: dict = None) -> "IBMBackend":
cpy._options = deepcopy(self._options, _memo)
return cpy

def run(
self,
circuits: Union[QuantumCircuit, str, List[Union[QuantumCircuit, str]]],
dynamic: bool = None,
job_tags: Optional[List[str]] = None,
init_circuit: Optional[QuantumCircuit] = None,
init_num_resets: Optional[int] = None,
header: Optional[Dict] = None,
shots: Optional[Union[int, float]] = None,
memory: Optional[bool] = None,
meas_level: Optional[Union[int, MeasLevel]] = None,
meas_return: Optional[Union[str, MeasReturnType]] = None,
rep_delay: Optional[float] = None,
init_qubits: Optional[bool] = None,
use_measure_esp: Optional[bool] = None,
noise_model: Optional[Any] = None,
seed_simulator: Optional[int] = None,
**run_config: Dict,
merav-aharoni marked this conversation as resolved.
Show resolved Hide resolved
) -> RuntimeJob:
"""Run on the backend.
If a keyword specified here is also present in the ``options`` attribute/object,
the value specified here will be used for this run.

Args:
circuits: An individual or a
list of :class:`~qiskit.circuits.QuantumCircuit`.
dynamic: Whether the circuit is dynamic (uses in-circuit conditionals)
job_tags: Tags to be assigned to the job. The tags can subsequently be used
as a filter in the :meth:`jobs()` function call.
init_circuit: A quantum circuit to execute for initializing qubits before each circuit.
If specified, ``init_num_resets`` is ignored. Applicable only if ``dynamic=True``
is specified.
init_num_resets: The number of qubit resets to insert before each circuit execution.

The following parameters are applicable only if ``dynamic=False`` is specified or
defaulted to.

header: User input that will be attached to the job and will be
copied to the corresponding result header. Headers do not affect the run.
This replaces the old ``Qobj`` header.
shots: Number of repetitions of each circuit, for sampling. Default: 4000
or ``max_shots`` from the backend configuration, whichever is smaller.
memory: If ``True``, per-shot measurement bitstrings are returned as well
(provided the backend supports it). For OpenPulse jobs, only
measurement level 2 supports this option.
meas_level: Level of the measurement output for pulse experiments. See
`OpenPulse specification <https://arxiv.org/pdf/1809.03452.pdf>`_ for details:

* ``0``, measurements of the raw signal (the measurement output pulse envelope)
* ``1``, measurement kernel is selected (a complex number obtained after applying the
measurement kernel to the measurement output signal)
* ``2`` (default), a discriminator is selected and the qubit state is stored (0 or 1)

meas_return: Level of measurement data for the backend to return. For ``meas_level`` 0 and 1:

* ``single`` returns information from every shot.
* ``avg`` returns average measurement output (averaged over number of shots).

rep_delay: Delay between programs in seconds. Only supported on certain
backends (if ``backend.configuration().dynamic_reprate_enabled=True``).
If supported, ``rep_delay`` must be from the range supplied
by the backend (``backend.configuration().rep_delay_range``). Default is given by
``backend.configuration().default_rep_delay``.
init_qubits: Whether to reset the qubits to the ground state for each shot.
Default: ``True``.
use_measure_esp: Whether to use excited state promoted (ESP) readout for measurements
which are the terminal instruction to a qubit. ESP readout can offer higher fidelity
than standard measurement sequences. See
`here <https://arxiv.org/pdf/2008.08571.pdf>`_.
Default: ``True`` if backend supports ESP readout, else ``False``. Backend support
for ESP readout is determined by the flag ``measure_esp_enabled`` in
``backend.configuration()``.
noise_model: Noise model. (Simulators only)
seed_simulator: Random seed to control sampling. (Simulators only)
**run_config: Extra arguments used to configure the run.

Returns:
The job to be executed.

Raises:
IBMBackendApiError: If an unexpected error occurred while submitting
the job.
IBMBackendApiProtocolError: If an unexpected value received from
the server.
IBMBackendValueError:
- If an input parameter value is not valid.
- If ESP readout is used and the backend does not support this.
"""
# pylint: disable=arguments-differ
validate_job_tags(job_tags)
if not isinstance(circuits, List):
circuits = [circuits]
self._check_circuits_attributes(circuits)

if use_measure_esp and getattr(self.configuration(), "measure_esp_enabled", False) is False:
raise IBMBackendValueError(
"ESP readout not supported on this device. Please make sure the flag "
"'use_measure_esp' is unset or set to 'False'."
)
actually_dynamic = are_circuits_dynamic(circuits)
if dynamic is False and actually_dynamic:
warnings.warn(
"Parameter 'dynamic' is False, but the circuit contains dynamic constructs."
)
dynamic = dynamic or actually_dynamic

if dynamic and "qasm3" not in getattr(self.configuration(), "supported_features", []):
warnings.warn(f"The backend {self.name} does not support dynamic circuits.")

status = self.status()
if status.operational is True and status.status_msg != "active":
warnings.warn(f"The backend {self.name} is currently paused.")

program_id = str(run_config.get("program_id", ""))
if program_id:
run_config.pop("program_id", None)
else:
program_id = QASM3RUNNERPROGRAMID if dynamic else QOBJRUNNERPROGRAMID

image: Optional[str] = run_config.get("image", None) # type: ignore
if image is not None:
image = str(image)

if isinstance(init_circuit, bool):
raise IBMBackendApiError(
"init_circuit does not accept boolean values. "
"A quantum circuit should be passed in instead."
)

if isinstance(shots, float):
shots = int(shots)

run_config_dict = self._get_run_config(
program_id=program_id,
init_circuit=init_circuit,
init_num_resets=init_num_resets,
header=header,
shots=shots,
memory=memory,
meas_level=meas_level,
meas_return=meas_return,
rep_delay=rep_delay,
init_qubits=init_qubits,
use_measure_esp=use_measure_esp,
noise_model=noise_model,
seed_simulator=seed_simulator,
**run_config,
)

run_config_dict["circuits"] = circuits

return self._runtime_run(
program_id=program_id,
inputs=run_config_dict,
backend_name=self.name,
job_tags=job_tags,
image=image,
)

def _runtime_run(
self,
program_id: str,
inputs: Dict,
backend_name: str,
job_tags: Optional[List[str]] = None,
image: Optional[str] = None,
) -> RuntimeJob:
"""Runs the runtime program and returns the corresponding job object"""
hgp_name = None
if self._service._channel == "ibm_quantum":
hgp_name = self._instance or self._service._get_hgp().name

session = self._session
if session and not session.active:
raise RuntimeError(f"The session {session.session_id} is closed.")
session_id = session.session_id if session else None
start_session = session is not None and session_id is None

log_level = getattr(self.options, "log_level", None) # temporary
try:
response = self._api_client.program_run(
program_id=program_id,
backend_name=backend_name,
params=inputs,
hgp=hgp_name,
log_level=log_level,
job_tags=job_tags,
session_id=session_id,
start_session=start_session,
image=image,
)
except RequestsApiError as ex:
raise IBMBackendApiError("Error submitting job: {}".format(str(ex))) from ex
session_id = response.get("session_id", None)
if self._session:
self._session._session_id = session_id
try:
job = RuntimeJob(
backend=self,
api_client=self._api_client,
client_params=self._service._client_params,
job_id=response["id"],
program_id=program_id,
session_id=session_id,
service=self.service,
)
logger.debug("Job %s was successfully submitted.", job.job_id())
except TypeError as err:
logger.debug("Invalid job data received: %s", response)
raise IBMBackendApiProtocolError(
"Unexpected return value received from the server "
"when submitting job: {}".format(str(err))
) from err
Publisher().publish("ibm.job.start", job)
return job

def _get_run_config(self, program_id: str, **kwargs: Any) -> Dict:
"""Return the consolidated runtime configuration."""
# Check if is a QASM3 like program id.
if program_id.startswith(QASM3RUNNERPROGRAMID):
fields = asdict(QASM3Options()).keys()
run_config_dict = QASM3Options().to_transport_dict()
else:
fields = asdict(QASM2Options()).keys()
run_config_dict = QASM2Options().to_transport_dict()
backend_options = self._options.__dict__
for key, val in kwargs.items():
if val is not None:
run_config_dict[key] = val
if key not in fields and not self.configuration().simulator:
warnings.warn( # type: ignore[unreachable]
f"{key} is not a recognized runtime option and may be ignored by the backend.",
stacklevel=4,
)
elif backend_options.get(key) is not None and key in fields:
run_config_dict[key] = backend_options[key]
return run_config_dict

def open_session(self) -> ProviderSession:
"""Open session"""
self._session = ProviderSession()
return self._session

@property
def session(self) -> ProviderSession:
"""Return session"""
return self._session

def cancel_session(self) -> None:
"""Cancel session. All pending jobs will be cancelled."""
if self._session:
self._session.cancel()
if self._session.session_id:
self._api_client.close_session(self._session.session_id)

self._session = None


class IBMRetiredBackend(IBMBackend):
"""Backend class interfacing with an IBM Quantum device no longer available."""
Expand Down
3 changes: 3 additions & 0 deletions qiskit_ibm_runtime/qiskit_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from qiskit_ibm_provider.utils.backend_decoder import configuration_from_server_data
from qiskit_ibm_runtime import ibm_backend

from .utils.utils import validate_job_tags
from .accounts import AccountManager, Account, ChannelType
from .api.clients import AuthClient, VersionClient
from .api.clients.runtime import RuntimeClient
Expand Down Expand Up @@ -1441,6 +1442,8 @@ def jobs(
"The 'instance' keyword is only supported for ``ibm_quantum`` runtime."
)
hub, group, project = from_instance_format(instance)
if job_tags:
validate_job_tags(job_tags)

job_responses = [] # type: List[Dict[str, Any]]
current_page_limit = limit or 20
Expand Down
Loading
Loading