diff --git a/CHANGELOG.md b/CHANGELOG.md index c9a826999..b7fd48a83 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ device, ``BasicSimulator``, as the backend. [(#493)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/493) +* Backwards compatibility with Qiskit BackendV2 has now been implemented. Previously, only backends of type + BackendV1 were supported but now users can choose to use BackendV2 as well. + [(#514)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/514) + ### Improvements 🛠 * Following updates to allow device compatibility with Qiskit 1.0, the version of `qiskit-ibm-runtime` is @@ -30,6 +34,7 @@ This release contains contributions from (in alphabetical order): Lillian M. A. Frederiksen +Austin Huang --- # Release 0.35.1 diff --git a/pennylane_qiskit/qiskit_device.py b/pennylane_qiskit/qiskit_device.py index 70c640150..c6f429db2 100644 --- a/pennylane_qiskit/qiskit_device.py +++ b/pennylane_qiskit/qiskit_device.py @@ -26,7 +26,7 @@ from qiskit.circuit import library as lib from qiskit.compiler import transpile from qiskit.converters import circuit_to_dag, dag_to_circuit -from qiskit.providers import Backend, QiskitBackendNotFoundError +from qiskit.providers import Backend, BackendV2, QiskitBackendNotFoundError from pennylane import QubitDevice, DeviceError from pennylane.measurements import SampleMP, CountsMP, ClassicalShadowMP, ShadowExpvalMP @@ -181,8 +181,11 @@ def __init__(self, wires, provider, backend, shots=1024, **kwargs): self._capabilities["returns_state"] = self._is_state_backend # Perform validation against backend - backend_qubits = self.backend.configuration().n_qubits - # if the backend has a set number of qubits, ensure wires doesn't exceed (some simulators have n_qubits=None) + backend_qubits = ( + backend.num_qubits + if isinstance(backend, BackendV2) + else self.backend.configuration().n_qubits + ) if backend_qubits and len(self.wires) > int(backend_qubits): raise ValueError(f"Backend '{backend}' supports maximum {backend_qubits} wires") diff --git a/tests/test_qiskit_device.py b/tests/test_qiskit_device.py index 9c8b71dcf..3a721f32c 100644 --- a/tests/test_qiskit_device.py +++ b/tests/test_qiskit_device.py @@ -5,6 +5,67 @@ from pennylane_qiskit import AerDevice from pennylane_qiskit.qiskit_device import QiskitDevice from qiskit_aer import noise +from qiskit.providers import BackendV1, BackendV2 +from qiskit_ibm_runtime.fake_provider import FakeManila, FakeManilaV2 +from unittest.mock import Mock +from qiskit_ibm_runtime.options import Options + + +class Configuration: + def __init__(self, n_qubits, backend_name): + self.n_qubits = n_qubits + self.backend_name = backend_name + self.noise_model = None + self.method = "placeholder" + + def get(self, attribute, default=None): + return getattr(self, attribute, default) + + +class MockedBackend(BackendV2): + def __init__(self, num_qubits=10, name="mocked_backend"): + self._options = Configuration(num_qubits, name) + self._service = "SomeServiceProvider" + self.name = name + self._target = Mock() + self._target.num_qubits = num_qubits + + def set_options(self, noise_model): + self.options.noise_model = noise_model + + def _default_options(self): + return {} + + def max_circuits(self): + return 10 + + def run(self, *args, **kwargs): + return None + + @property + def target(self): + return self._target + + +class MockedBackendLegacy(BackendV1): + def __init__(self, num_qubits=10, name="mocked_backend_legacy"): + self._configuration = Configuration(num_qubits, backend_name=name) + self._service = "SomeServiceProvider" + self._options = self._default_options() + + def configuration(self): + return self._configuration + + def _default_options(self): + return {} + + def run(self, *args, **kwargs): + return None + + @property + def options(self): + return self._options + test_transpile_options = [ {}, @@ -13,6 +74,45 @@ ] test_device_options = [{}, {"optimization_level": 3}, {"optimization_level": 1}] +backend = MockedBackend() +legacy_backend = MockedBackendLegacy() + + +class TestSupportForV1andV2: + """Tests compatibility with BackendV1 and BackendV2""" + + @pytest.mark.parametrize( + "backend", + [ + legacy_backend, + backend, + ], + ) + def test_v1_and_v2_mocked(self, backend): + """Test that device initializes with no error mocked""" + dev = qml.device("qiskit.remote", wires=10, backend=backend, use_primitives=True) + assert dev._backend == backend + + @pytest.mark.parametrize( + "backend", + [ + FakeManila(), + FakeManilaV2(), + ] + ) + def test_v1_and_v2_manila(self, backend): + """Test that device initializes with no error with V1 and V2 backends by Qiskit""" + dev = qml.device("qiskit.remote", wires=5, backend=backend, use_primitives=True) + + @qml.qnode(dev) + def circuit(x): + qml.RX(x, wires=[0]) + qml.CNOT(wires=[0, 1]) + return qml.sample(qml.PauliZ(0)) + + res = circuit(np.pi/2) + assert(isinstance(res, np.ndarray)) + assert(np.shape(res) == (1024,)) class TestProbabilities: