diff --git a/qiskit/providers/backend.py b/qiskit/providers/backend.py index 51b0da959157..a706c2e62666 100644 --- a/qiskit/providers/backend.py +++ b/qiskit/providers/backend.py @@ -577,6 +577,15 @@ def options(self): """ return self._options + @property + def provider(self): + """Return the backend Provider. + + Returns: + Provider: the Provider responsible for the backend. + """ + return self._provider + @abstractmethod def run(self, run_input, **options): """Run on the backend. diff --git a/qiskit/test/mock/fake_backend_v2.py b/qiskit/test/mock/fake_backend_v2.py index e46683e90e24..31b8698adcf2 100644 --- a/qiskit/test/mock/fake_backend_v2.py +++ b/qiskit/test/mock/fake_backend_v2.py @@ -25,10 +25,14 @@ UGate, ECRGate, RXGate, + SXGate, + XGate, + RZGate, ) from qiskit.providers.backend import BackendV2, QubitProperties from qiskit.providers.options import Options from qiskit.transpiler import Target, InstructionProperties +from qiskit.providers.basicaer.qasm_simulator import QasmSimulatorPy class FakeBackendV2(BackendV2): @@ -176,3 +180,40 @@ def qubit_properties(self, qubit): if isinstance(qubit, int): return self._qubit_properties[qubit] return [self._qubit_properties[i] for i in qubit] + + +class FakeBackendSimple(BackendV2): + """A fake simple backend that wraps BasicAer to implement run().""" + + def __init__(self): + super().__init__( + None, + name="FakeSimpleV2", + description="A fake simple BackendV2 example", + online_date=datetime.datetime.utcnow(), + backend_version="0.0.1", + ) + self._lam = Parameter("lambda") + self._target = Target(num_qubits=20) + self._target.add_instruction(SXGate()) + self._target.add_instruction(XGate()) + self._target.add_instruction(RZGate(self._lam)) + self._target.add_instruction(CXGate()) + self._target.add_instruction(Measure()) + self._runner = QasmSimulatorPy() + + @property + def target(self): + return self._target + + @property + def max_circuits(self): + return None + + @classmethod + def _default_options(cls): + return QasmSimulatorPy._default_options() + + def run(self, run_input, **options): + self._runner._options = self._options + return self._runner.run(run_input, **options) diff --git a/qiskit/utils/backend_utils.py b/qiskit/utils/backend_utils.py index c377b6aa5f3c..219e9c9a88fb 100644 --- a/qiskit/utils/backend_utils.py +++ b/qiskit/utils/backend_utils.py @@ -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: @@ -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 @@ -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): @@ -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 @@ -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): @@ -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): @@ -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): diff --git a/qiskit/utils/quantum_instance.py b/qiskit/utils/quantum_instance.py index 37abf37ab031..ec7276e9ea83 100644 --- a/qiskit/utils/quantum_instance.py +++ b/qiskit/utils/quantum_instance.py @@ -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, @@ -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: @@ -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 = { @@ -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 @@ -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, @@ -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 @@ -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: @@ -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 @@ -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): diff --git a/qiskit/utils/run_circuits.py b/qiskit/utils/run_circuits.py index fccc1b6ab1a9..802229131b7e 100644 --- a/qiskit/utils/run_circuits.py +++ b/qiskit/utils/run_circuits.py @@ -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) @@ -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 @@ -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 = [] diff --git a/releasenotes/notes/fix-quantum-instance-backend-v2-a4e2678fe3ce39d1.yaml b/releasenotes/notes/fix-quantum-instance-backend-v2-a4e2678fe3ce39d1.yaml new file mode 100644 index 000000000000..c7cf64238c6e --- /dev/null +++ b/releasenotes/notes/fix-quantum-instance-backend-v2-a4e2678fe3ce39d1.yaml @@ -0,0 +1,12 @@ +--- +fixes: + - | + Added a missing :attr:`.BackendV2.provider` attribute to implementations + of the :class:`.BackendV2` abstract class. Previously, :class:`.BackendV2` + backends could be initialized with a provider but that was not accesible + to users. + - | + Fixed support for the :class:`.QuantumInstance` class when running with + a :class:`.BackendV2` backend. Previously, attempting to use a + :class:`.QuantumInstance` with a :class:`.BackendV2` would have resulted in + an error. diff --git a/test/python/algorithms/test_backendv2.py b/test/python/algorithms/test_backendv2.py new file mode 100644 index 000000000000..e96d5d2176bf --- /dev/null +++ b/test/python/algorithms/test_backendv2.py @@ -0,0 +1,100 @@ +# This code is part of Qiskit. +# +# (C) Copyright IBM 2021. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE.txt file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" Test Providers that support BackendV2 interface """ + +import unittest +from test.python.algorithms import QiskitAlgorithmsTestCase +from qiskit import QuantumCircuit +from qiskit.test.mock import FakeProvider +from qiskit.test.mock.fake_backend_v2 import FakeBackendSimple +from qiskit.utils import QuantumInstance +from qiskit.algorithms import Shor, VQE, Grover, AmplificationProblem +from qiskit.opflow import X, Z, I +from qiskit.algorithms.optimizers import SPSA +from qiskit.circuit.library import TwoLocal + + +class TestBackendV2(QiskitAlgorithmsTestCase): + """test BackendV2 interface""" + + def setUp(self): + super().setUp() + self._provider = FakeProvider() + self._qasm = FakeBackendSimple() + self.seed = 50 + + def test_shor_factoring(self): + """shor factoring test""" + n_v = 15 + factors = [3, 5] + qasm_simulator = QuantumInstance( + self._qasm, shots=1000, seed_simulator=self.seed, seed_transpiler=self.seed + ) + shor = Shor(quantum_instance=qasm_simulator) + result = shor.factor(N=n_v) + self.assertListEqual(result.factors[0], factors) + self.assertTrue(result.total_counts >= result.successful_counts) + + def test_vqe_qasm(self): + """Test the VQE on QASM simulator.""" + h2_op = ( + -1.052373245772859 * (I ^ I) + + 0.39793742484318045 * (I ^ Z) + - 0.39793742484318045 * (Z ^ I) + - 0.01128010425623538 * (Z ^ Z) + + 0.18093119978423156 * (X ^ X) + ) + optimizer = SPSA(maxiter=300, last_avg=5) + wavefunction = TwoLocal(rotation_blocks="ry", entanglement_blocks="cz") + qasm_simulator = QuantumInstance( + self._qasm, shots=1024, seed_simulator=self.seed, seed_transpiler=self.seed + ) + vqe = VQE( + ansatz=wavefunction, + optimizer=optimizer, + max_evals_grouped=1, + quantum_instance=qasm_simulator, + ) + + result = vqe.compute_minimum_eigenvalue(operator=h2_op) + self.assertAlmostEqual(result.eigenvalue.real, -1.86, delta=0.05) + + def test_run_circuit_oracle(self): + """Test execution with a quantum circuit oracle""" + oracle = QuantumCircuit(2) + oracle.cz(0, 1) + problem = AmplificationProblem(oracle, is_good_state=["11"]) + qi = QuantumInstance( + self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 + ) + grover = Grover(quantum_instance=qi) + result = grover.amplify(problem) + self.assertIn(result.top_measurement, ["11"]) + + def test_run_circuit_oracle_single_experiment_backend(self): + """Test execution with a quantum circuit oracle""" + oracle = QuantumCircuit(2) + oracle.cz(0, 1) + problem = AmplificationProblem(oracle, is_good_state=["11"]) + backend = self._provider.get_backend("fake_yorktown") + backend._configuration.max_experiments = 1 + qi = QuantumInstance( + self._provider.get_backend("fake_yorktown"), seed_simulator=12, seed_transpiler=32 + ) + grover = Grover(quantum_instance=qi) + result = grover.amplify(problem) + self.assertIn(result.top_measurement, ["11"]) + + +if __name__ == "__main__": + unittest.main()