Skip to content

Commit

Permalink
Fix QuantumInstance compatibility with BackendV2 (Qiskit/qiskit#7563)
Browse files Browse the repository at this point in the history
* Fix QuantumInstance compatibility with BackendV2

This commit fixes compatibility with the QuantumInstance class when
targetting a BackendV2 backend. When BackendV2 was introduced testing
with the QuantumInstance (and therefore all of qiskit.algorithms) was
neglected and there are several compatibility issues with the class
around hard coded assumptions that the backend being wrapped was a
BaseBackend or BackendV1 object.

* Remove invalid test

The tests for the QuantumInstance with BackendV2 were just ported over
from the equivalent testing with BackendV1. However, one of those tests
was to test error mitigation with a custom noise model which requires
qiskit-aer. We can't test this without aer having been migrated to
BackendV2. Until qiskit-aer is backendv2 enabled this commit just
removes the test.

* Remove unused imports

* Remove duplicate backend version checks from QuantumInstance

The QuantumInstance's is_simulator() and is_local() methods had
duplicate checking on the backend version. This was an artifact of
testing where in earlier revisions of this PR branch it was done in the
QuantumInstance before being moved to the inner helper functions. Since
the helper functions are updated to handle the version checking now we
no longer need it in the QuantumInstance methods too. This commit
removes this and just relies on the helper functions to do the version
checking.

* Use more descriptive local variable name for backend version

This commit attempts to clarify how the version fields are used by
naming the local variables backend_interface_version to make it clear
what we're checking in that context.

* Fix handling of statevector simulator check for BackendV2

This commit fixes the check for whether we're running with a statevector
simulator (to determine whether we need to insert save_statevector
instructions) to be BackendV2 compatible. This isn't strictly needed yet
as qiskit-aer is still based on BackendV1, but this would cause a bug
when it does switch to the newer backend version. This commit just gets
in front of that potential issue so we don't have a bug in the future.

* Use backend_interface_version everywhere

There were a few straglers still using backend_version instead of
backend_interface_version in the code. This commit updates all these
instances so there is consistent naming around this inside the utils
subpackage.

* Fix handling of statevector simulator detection

This commit fixes an oversight from an earlier development version that
slipped through later revisions where the statevector simulator backend
detection in the QuantumInstance class was hard coded to False for
BackendV2. This was just meant as a temporary debugging step while
getting the initial commit ready, but was never updated to remove this
until now.

* Actually commit local variable backend_interface_version renames

* Fix typo

Co-authored-by: Jake Lishman <[email protected]>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
  • Loading branch information
3 people authored Feb 1, 2022
1 parent e0c3254 commit 6d64e59
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 24 deletions.
43 changes: 37 additions & 6 deletions qiskit_algorithms/utils/backend_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,25 @@ def __init__(self) -> None:
_PROVIDER_CHECK = ProviderCheck()


def _get_backend_interface_version(backend):
"""Get the backend version int."""
backend_interface_version = getattr(backend, "version", None)
# Handle deprecated BaseBackend based backends which have a version()
# method
if not isinstance(backend_interface_version, int):
backend_interface_version = 0
return backend_interface_version


def _get_backend_provider(backend):
backend_interface_version = _get_backend_interface_version(backend)
if backend_interface_version > 1:
provider = backend.provider
else:
provider = backend.provider()
return provider


def has_ibmq():
"""Check if IBMQ is installed"""
if not _PROVIDER_CHECK.checked_ibmq:
Expand Down Expand Up @@ -78,7 +97,7 @@ def is_aer_provider(backend):
if has_aer():
from qiskit.providers.aer import AerProvider

if isinstance(backend.provider(), AerProvider):
if isinstance(_get_backend_provider(backend), AerProvider):
return True
from qiskit.providers.aer.backends.aerbackend import AerBackend

Expand All @@ -97,7 +116,7 @@ def is_basicaer_provider(backend):
"""
from qiskit.providers.basicaer import BasicAerProvider

return isinstance(backend.provider(), BasicAerProvider)
return isinstance(_get_backend_provider(backend), BasicAerProvider)


def is_ibmq_provider(backend):
Expand All @@ -111,7 +130,7 @@ def is_ibmq_provider(backend):
if has_ibmq():
from qiskit.providers.ibmq.accountprovider import AccountProvider

return isinstance(backend.provider(), AccountProvider)
return isinstance(_get_backend_provider(backend), AccountProvider)

return False

Expand Down Expand Up @@ -144,7 +163,13 @@ def is_statevector_backend(backend):
return True
if isinstance(backend, AerSimulator) and backend.name() == "aer_simulator_statevector":
return True
return backend.name().startswith("statevector") if backend is not None else False
if backend is None:
return False
backend_interface_version = _get_backend_interface_version(backend)
if backend_interface_version <= 1:
return backend.name().startswith("statevector")
else:
return backend.name.startswith("statevector")


def is_simulator_backend(backend):
Expand All @@ -156,7 +181,10 @@ def is_simulator_backend(backend):
Returns:
bool: True is a simulator
"""
return backend.configuration().simulator
backend_interface_version = _get_backend_interface_version(backend)
if backend_interface_version <= 1:
return backend.configuration().simulator
return False


def is_local_backend(backend):
Expand All @@ -168,7 +196,10 @@ def is_local_backend(backend):
Returns:
bool: True is a local backend
"""
return backend.configuration().local
backend_interface_version = _get_backend_interface_version(backend)
if backend_interface_version <= 1:
return backend.configuration().local
return False


def is_aer_qasm(backend):
Expand Down
35 changes: 22 additions & 13 deletions qiskit_algorithms/utils/quantum_instance.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
is_aer_qasm,
is_basicaer_provider,
support_backend_options,
_get_backend_provider,
_get_backend_interface_version,
)
from qiskit.utils.mitigation import (
CompleteMeasFitter,
Expand Down Expand Up @@ -239,15 +241,16 @@ def __init__(
QiskitError: set backend_options but the backend does not support that
"""
self._backend = backend
self._backend_interface_version = _get_backend_interface_version(self._backend)
self._pass_manager = pass_manager
self._bound_pass_manager = bound_pass_manager

# if the shots are none, try to get them from the backend
if shots is None:
from qiskit.providers.basebackend import BaseBackend # pylint: disable=cyclic-import
from qiskit.providers.backend import BackendV1 # pylint: disable=cyclic-import
from qiskit.providers.backend import Backend # pylint: disable=cyclic-import

if isinstance(backend, (BaseBackend, BackendV1)):
if isinstance(backend, (BaseBackend, Backend)):
if hasattr(backend, "options"): # should always be true for V1
backend_shots = backend.options.get("shots", 1024)
if shots != backend_shots:
Expand Down Expand Up @@ -280,9 +283,12 @@ def __init__(
self._run_config = run_config

# setup backend config
basis_gates = basis_gates or backend.configuration().basis_gates
coupling_map = coupling_map or getattr(backend.configuration(), "coupling_map", None)
self._backend_config = {"basis_gates": basis_gates, "coupling_map": coupling_map}
if self._backend_interface_version <= 1:
basis_gates = basis_gates or backend.configuration().basis_gates
coupling_map = coupling_map or getattr(backend.configuration(), "coupling_map", None)
self._backend_config = {"basis_gates": basis_gates, "coupling_map": coupling_map}
else:
self._backend_config = {}

# setup compile config
self._compile_config = {
Expand All @@ -306,7 +312,7 @@ def __init__(
"The noise model is not supported "
"on the selected backend {} ({}) "
"only certain backends, such as Aer qasm simulator "
"support noise.".format(self.backend_name, self._backend.provider())
"support noise.".format(self.backend_name, _get_backend_provider(self._backend))
)

# setup backend options for run
Expand Down Expand Up @@ -374,7 +380,7 @@ def __str__(self) -> str:
info = f"\nQiskit Terra version: {terra_version}\n"
info += "Backend: '{} ({})', with following setting:\n{}\n{}\n{}\n{}\n{}\n{}".format(
self.backend_name,
self._backend.provider(),
_get_backend_provider(self._backend),
self._backend_config,
self._compile_config,
self._run_config,
Expand Down Expand Up @@ -505,10 +511,10 @@ def execute(self, circuits, had_transpiled: bool = False):
# transpile here, the method always returns a copied list
circuits = self.transpile(circuits)

from qiskit.providers import BackendV1
from qiskit.providers import Backend

circuit_job = isinstance(self._backend, BackendV1)
if self.is_statevector and self._backend.name() == "aer_simulator_statevector":
circuit_job = isinstance(self._backend, Backend)
if self.is_statevector and self.backend_name == "aer_simulator_statevector":
try:
from qiskit.providers.aer.library import SaveStatevector

Expand Down Expand Up @@ -836,7 +842,7 @@ def set_config(self, **kwargs):
if not support_backend_options(self._backend):
raise QiskitError(
"backend_options can not be used with this backend "
"{} ({}).".format(self.backend_name, self._backend.provider())
"{} ({}).".format(self.backend_name, _get_backend_provider(self._backend))
)

if k in QuantumInstance._BACKEND_OPTIONS_QASM_ONLY and self.is_statevector:
Expand All @@ -853,7 +859,7 @@ def set_config(self, **kwargs):
raise QiskitError(
"The noise model is not supported on the selected backend {} ({}) "
"only certain backends, such as Aer qasm support "
"noise.".format(self.backend_name, self._backend.provider())
"noise.".format(self.backend_name, _get_backend_provider(self._backend))
)

self._noise_config[k] = v
Expand Down Expand Up @@ -967,7 +973,10 @@ def backend(self):
@property
def backend_name(self):
"""Return backend name."""
return self._backend.name()
if self._backend_interface_version <= 1:
return self._backend.name()
else:
return self._backend.name

@property
def is_statevector(self):
Expand Down
26 changes: 21 additions & 5 deletions qiskit_algorithms/utils/run_circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
is_simulator_backend,
is_local_backend,
is_ibmq_provider,
_get_backend_interface_version,
)

MAX_CIRCUITS_PER_JOB = os.environ.get("QISKIT_AQUA_MAX_CIRCUITS_PER_JOB", None)
Expand Down Expand Up @@ -277,7 +278,11 @@ def run_qobj(
if is_local_backend(backend):
max_circuits_per_job = sys.maxsize
else:
max_circuits_per_job = backend.configuration().max_experiments
backend_interface_version = _get_backend_interface_version(backend)
if backend_interface_version <= 1:
max_circuits_per_job = backend.configuration().max_experiments
else:
max_circuits_per_job = backend.max_circuits

# split qobj if it exceeds the payload of the backend

Expand Down Expand Up @@ -470,18 +475,29 @@ def run_circuits(
Raises:
QiskitError: Any error except for JobError raised by Qiskit Terra
"""
backend_interface_version = _get_backend_interface_version(backend)

backend_options = backend_options or {}
noise_config = noise_config or {}
run_config = run_config or {}
with_autorecover = not is_simulator_backend(backend)
if backend_interface_version <= 1:
with_autorecover = not is_simulator_backend(backend)
else:
with_autorecover = False

if MAX_CIRCUITS_PER_JOB is not None:
max_circuits_per_job = int(MAX_CIRCUITS_PER_JOB)
else:
if is_local_backend(backend):
max_circuits_per_job = sys.maxsize
if backend_interface_version <= 1:
if is_local_backend(backend):
max_circuits_per_job = sys.maxsize
else:
max_circuits_per_job = backend.configuration().max_experiments
else:
max_circuits_per_job = backend.configuration().max_experiments
if backend.max_circuits is not None:
max_circuits_per_job = backend.max_circuits
else:
max_circuits_per_job = sys.maxsize

if len(circuits) > max_circuits_per_job:
jobs = []
Expand Down

0 comments on commit 6d64e59

Please sign in to comment.