Skip to content

Commit

Permalink
BackendV2 first draft
Browse files Browse the repository at this point in the history
  • Loading branch information
rathishcholarajan committed Jan 19, 2022
1 parent 1c68e2a commit f109588
Show file tree
Hide file tree
Showing 19 changed files with 276 additions and 60 deletions.
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@
params.measurement_error_mitigation = True
# Configure backend options
options = {'backend_name': backend.name()}
options = {'backend_name': backend.name}
# Execute the circuit using the "circuit-runner" program.
job = service.run(program_id="circuit-runner",
Expand Down
119 changes: 107 additions & 12 deletions qiskit_ibm_runtime/ibm_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@

import logging

from typing import Union, Optional, Any
from typing import Union, Optional, Any, List
from datetime import datetime as python_datetime

from qiskit.qobj.utils import MeasLevel, MeasReturnType
from qiskit.providers.backend import BackendV1 as Backend
from qiskit.providers.backend import BackendV2 as Backend, QubitProperties
from qiskit.providers.options import Options
from qiskit.providers.models import (
BackendStatus,
BackendProperties,
PulseDefaults,
GateConfig,
QasmBackendConfiguration,
PulseBackendConfiguration,
)
from qiskit.providers.models import QasmBackendConfiguration, PulseBackendConfiguration
from qiskit.transpiler.target import Target

from .api.clients import AccountClient, RuntimeClient
from .api.clients.backend import BaseBackendClient
from .exceptions import IBMBackendApiProtocolError
from .utils.backend_converter import (
convert_to_target,
qubit_properties_dict_from_properties,
)
from .utils.converters import local_to_utc
from .utils.backend_decoder import (
defaults_from_server_data,
Expand Down Expand Up @@ -73,13 +79,61 @@ def __init__(
configuration: Backend configuration.
api_client: IBM Quantum client used to communicate with the server.
"""
super().__init__(configuration=configuration)

super().__init__(
name=configuration.backend_name,
online_date=configuration.online_date,
backend_version=configuration.backend_version,
)
self._configuration = configuration
self._api_client = api_client

# Attributes used by caching functions.
self._properties = None
self._qubit_properties = None
self._defaults = None
self._target = None
self._max_circuits = configuration.max_experiments

def __getattr__(self, name: str) -> Any:
self._get_properties()
self._get_defaults()
self._convert_to_target()
try:
return super().__getattribute__(name)
except AttributeError:
pass
try:
return self._configuration.__getattribute__(name)
except AttributeError:
raise AttributeError(
"'{}' object has no attribute '{}'".format(
self.__class__.__name__, name
)
)

def _get_properties(self) -> None:
"""Gets backend properties and decodes it"""
if not self._properties:
api_properties = self._api_client.backend_properties(self.name)
if api_properties:
backend_properties = properties_from_server_data(api_properties)
self._properties = backend_properties

def _get_defaults(self) -> None:
"""Gets defaults if pulse backend and decodes it"""
if not self._defaults and isinstance(
self._configuration, PulseBackendConfiguration
):
api_defaults = self._api_client.backend_pulse_defaults(self.name)
if api_defaults:
self._defaults = defaults_from_server_data(api_defaults)

def _convert_to_target(self) -> None:
"""Converts backend configuration, properties and defaults to Target object"""
if not self._target:
self._target = convert_to_target(
configuration=self._configuration.to_dict(),
properties=self._properties.to_dict() if self._properties else None,
defaults=self._defaults.to_dict() if self._defaults else None,
)

@classmethod
def _default_options(cls) -> Options:
Expand All @@ -100,6 +154,47 @@ def _default_options(cls) -> Options:
use_measure_esp=None,
)

@property
def target(self) -> Target:
"""A :class:`qiskit.transpiler.Target` object for the backend.
Returns:
Target
"""
return self._target

@property
def max_circuits(self) -> int:
"""The maximum number of circuits
The maximum number of circuits (or Pulse schedules) that can be
run in a single job. If there is no limit this will return None
"""
return self._max_circuits

def qubit_properties(
self, qubit: Union[int, List[int]]
) -> Union[QubitProperties, List[QubitProperties]]:
"""Return QubitProperties for a given qubit.
Args:
qubit: The qubit to get the
:class:`~qiskit.provider.QubitProperties` object for. This can
be a single integer for 1 qubit or a list of qubits and a list
of :class:`~qiskit.provider.QubitProperties` objects will be
returned in the same order
"""
self._get_properties()
if not self._qubit_properties:
self._qubit_properties = qubit_properties_dict_from_properties(
self._properties.to_dict()
)
if isinstance(qubit, int): # type: ignore[unreachable]
return self._qubit_properties.get(qubit)
if isinstance(qubit, List):
return [self._qubit_properties.get(q) for q in qubit]
return None

def properties(
self, refresh: bool = False, datetime: Optional[python_datetime] = None
) -> Optional[BackendProperties]:
Expand Down Expand Up @@ -147,7 +242,7 @@ def properties(

if datetime or refresh or self._properties is None:
api_properties = self._api_client.backend_properties(
self.name(), datetime=datetime
self.name, datetime=datetime
)
if not api_properties:
return None
Expand All @@ -171,7 +266,7 @@ def status(self) -> BackendStatus:
Raises:
IBMBackendApiProtocolError: If the status for the backend cannot be formatted properly.
"""
api_status = self._api_client.backend_status(self.name())
api_status = self._api_client.backend_status(self.name)

try:
return BackendStatus.from_dict(api_status)
Expand All @@ -196,7 +291,7 @@ def defaults(self, refresh: bool = False) -> Optional[PulseDefaults]:
The backend pulse defaults or ``None`` if the backend does not support pulse.
"""
if refresh or self._defaults is None:
api_defaults = self._api_client.backend_pulse_defaults(self.name())
api_defaults = self._api_client.backend_pulse_defaults(self.name)
if api_defaults:
self._defaults = defaults_from_server_data(api_defaults)
else:
Expand All @@ -222,7 +317,7 @@ def configuration(
return self._configuration

def __repr__(self) -> str:
return "<{}('{}')>".format(self.__class__.__name__, self.name())
return "<{}('{}')>".format(self.__class__.__name__, self.name)

def run(self, *args: Any, **kwargs: Any) -> None:
"""Not supported method"""
Expand Down Expand Up @@ -265,7 +360,7 @@ def __init__(
"""
super().__init__(configuration, api_client)
self._status = BackendStatus(
backend_name=self.name(),
backend_name=self.name,
backend_version=self.configuration().backend_version,
operational=False,
pending_jobs=0,
Expand Down
4 changes: 2 additions & 2 deletions qiskit_ibm_runtime/ibm_runtime_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class IBMRuntimeService:
params.measurement_error_mitigation = True
# Configure backend options
options = {'backend_name': backend.name()}
options = {'backend_name': backend.name}
# Execute the circuit using the "circuit-runner" program.
job = service.run(program_id="circuit-runner",
Expand Down Expand Up @@ -456,7 +456,7 @@ def _get_hgp(
def _discover_backends(self) -> None:
"""Discovers the remote backends for this account, if not already known."""
for backend in self._backends.values():
backend_name = to_python_identifier(backend.name())
backend_name = to_python_identifier(backend.name)
# Append _ if duplicate
while backend_name in self.__dict__:
backend_name += "_"
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/jupyter/backend_info.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def backend_widget(backend: Union[IBMBackend, FakeBackend]) -> None:
vue.ToolbarTitle(
children=[
"{} @ ({}/{}/{})".format(
backend.name(), cred.hub, cred.group, cred.project
backend.name, cred.hub, cred.group, cred.project
)
],
style_="color:white",
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/jupyter/dashboard/backend_widget.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ def make_backend_widget(backend_item: BackendWithProviders) -> wid.HBox:
next_resrv = get_next_reservation(backend)

name_str = "<font size='5' face='monospace'>%s</font>"
backend_name = wid.HTML(value=name_str % backend.name())
backend_name = wid.HTML(value=name_str % backend.name)

qubits_wid = wid.HTML(value=STAT_FONT_TITLE.format("Qubits:"))
qubits_val_wid = wid.HTML(value=STAT_FONT_VALUE.format(config.n_qubits))
Expand Down
6 changes: 3 additions & 3 deletions qiskit_ibm_runtime/jupyter/dashboard/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,12 +94,12 @@ def _get_backends(self) -> None:
)
for back in pro.backends():
if not back.configuration().simulator:
if back.name() not in ibm_backends.keys():
ibm_backends[back.name()] = BackendWithProviders(
if back.name not in ibm_backends.keys():
ibm_backends[back.name] = BackendWithProviders(
backend=back, providers=[pro_name]
)
else:
ibm_backends[back.name()].providers.append(pro_name)
ibm_backends[back.name].providers.append(pro_name)

self.backend_dict = ibm_backends

Expand Down
2 changes: 0 additions & 2 deletions qiskit_ibm_runtime/utils/backend_decoder.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,6 @@
from qiskit.providers.models import (
BackendProperties,
PulseDefaults,
)
from qiskit.providers.models import (
PulseBackendConfiguration,
QasmBackendConfiguration,
)
Expand Down
2 changes: 1 addition & 1 deletion qiskit_ibm_runtime/visualization/interactive/error_map.py
Original file line number Diff line number Diff line change
Expand Up @@ -535,7 +535,7 @@ def iplot_error_map(
for ann in fig["layout"]["annotations"]:
ann["font"] = dict(size=13)

title_text = "{} Error Map".format(backend.name()) if show_title else ""
title_text = "{} Error Map".format(backend.name) if show_title else ""
fig.update_layout(
showlegend=False,
plot_bgcolor=background_color,
Expand Down
2 changes: 1 addition & 1 deletion qiskit_runtime/qaoa/qaoa.py
Original file line number Diff line number Diff line change
Expand Up @@ -1885,7 +1885,7 @@ def __init__(
self._coupling_map = CouplingMap(coupling_map)
self._two_qubit_fidelity = {}
self._max_problem_size = backend.configuration().num_qubits
self._name = backend.name()
self._name = backend.name
self._use_fidelity = use_fidelity

props = backend.properties()
Expand Down
2 changes: 1 addition & 1 deletion qiskit_runtime/vqe/vqe.py
Original file line number Diff line number Diff line change
Expand Up @@ -1153,7 +1153,7 @@ def main(backend, user_messenger, **kwargs):

if __name__ == "__main__":
# the code currently uses Aer instead of runtime provider
_backend = Aer.get_backend("qasm_simulator")
_backend = Aer.backend("qasm_simulator")
user_params = {}
if len(sys.argv) > 1:
# If there are user parameters.
Expand Down
8 changes: 4 additions & 4 deletions test/test_backend_retrieval.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ class TestBackendFilters(IBMTestCase):
def test_no_filter(self, service):
"""Test no filtering."""
# FakeRuntimeService by default creates 3 backends.
backend_name = [back.name() for back in service.backends()]
backend_name = [back.name for back in service.backends()]
self.assertEqual(len(backend_name), 3)

@run_legacy_and_cloud_fake
Expand All @@ -39,7 +39,7 @@ def test_filter_by_name(self, service):
FakeRuntimeService.DEFAULT_UNIQUE_BACKEND_PREFIX + "0",
]:
with self.subTest(name=name):
backend_name = [back.name() for back in service.backends(name=name)]
backend_name = [back.name for back in service.backends(name=name)]
self.assertEqual(len(backend_name), 1)

def test_filter_by_instance_legacy(self):
Expand All @@ -48,7 +48,7 @@ def test_filter_by_instance_legacy(self):
for hgp in FakeRuntimeService.DEFAULT_HGPS:
with self.subTest(hgp=hgp):
backends = service.backends(instance=hgp)
backend_name = [back.name() for back in backends]
backend_name = [back.name for back in backends]
self.assertEqual(len(backend_name), 2)
for back in backends:
self.assertEqual(back._api_client.hgp, hgp)
Expand Down Expand Up @@ -128,7 +128,7 @@ def test_filter_least_busy(self):
for service in services:
with self.subTest(service=service.auth):
backend = service.least_busy()
self.assertEqual(backend.name(), "bingo")
self.assertEqual(backend.name, "bingo")

def test_filter_min_num_qubits(self):
"""Test filtering by minimum number of qubits."""
Expand Down
12 changes: 6 additions & 6 deletions test/test_integration_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ def test_backends(self, service):
"""Test getting all backends."""
backends = service.backends()
self.assertTrue(backends)
backend_names = [back.name() for back in backends]
backend_names = [back.name for back in backends]
self.assertEqual(
len(backend_names),
len(set(backend_names)),
Expand Down Expand Up @@ -59,34 +59,34 @@ def setUpClass(cls, devices):
def test_backend_status(self):
"""Check the status of a real chip."""
for backend in self.devices:
with self.subTest(backend=backend.name()):
with self.subTest(backend=backend.name):
self.assertTrue(backend.status().operational)

def test_backend_properties(self):
"""Check the properties of calibration of a real chip."""
for backend in self.devices:
with self.subTest(backend=backend.name()):
with self.subTest(backend=backend.name):
if backend.configuration().simulator:
raise SkipTest("Skip since simulator does not have properties.")
self.assertIsNotNone(backend.properties())

def test_backend_pulse_defaults(self):
"""Check the backend pulse defaults of each backend."""
for backend in self.devices:
with self.subTest(backend=backend.name()):
with self.subTest(backend=backend.name):
if backend.configuration().simulator:
raise SkipTest("Skip since simulator does not have defaults.")
self.assertIsNotNone(backend.defaults())

def test_backend_configuration(self):
"""Check the backend configuration of each backend."""
for backend in self.devices:
with self.subTest(backend=backend.name()):
with self.subTest(backend=backend.name):
self.assertIsNotNone(backend.configuration())

def test_backend_run(self):
"""Check one cannot do backend.run"""
for backend in self.devices:
with self.subTest(backend=backend.name()):
with self.subTest(backend=backend.name):
with self.assertRaises(RuntimeError):
backend.run()
2 changes: 1 addition & 1 deletion test/test_integration_job.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def test_job_inputs(self, service):
def test_job_backend(self, service):
"""Test job backend."""
job = self._run_program(service)
self.assertEqual(self.sim_backends[service.auth], job.backend.name())
self.assertEqual(self.sim_backends[service.auth], job.backend.name)

@run_cloud_legacy_real
def test_job_program_id(self, service):
Expand Down
Loading

0 comments on commit f109588

Please sign in to comment.