diff --git a/.github/workflows/ibmq_tests.yml b/.github/workflows/ibmq_tests.yml index f7e05b817..a094d1b0f 100644 --- a/.github/workflows/ibmq_tests.yml +++ b/.github/workflows/ibmq_tests.yml @@ -28,7 +28,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-ci.txt + pip install -r requirements-ci-legacy.txt pip install wheel pytest pytest-cov pytest-mock flaky --upgrade pip freeze diff --git a/.github/workflows/ibmq_tests_1.yml b/.github/workflows/ibmq_tests_1.yml new file mode 100644 index 000000000..edbf3e7f8 --- /dev/null +++ b/.github/workflows/ibmq_tests_1.yml @@ -0,0 +1,44 @@ +name: IBMQ integration tests with Qiskit 1.0 +on: + schedule: + - cron: '1 0 * * 0,4' # At 01:00 on Sunday and Thursday. + workflow_dispatch: + +jobs: + tests: + runs-on: ubuntu-latest + + strategy: + matrix: + python-version: [3.9] + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-ci.txt + pip install wheel pytest pytest-cov pytest-mock flaky --upgrade + pip freeze + + - name: Install Plugin + run: | + pip install git+https://github.com/PennyLaneAI/pennylane-qiskit.git@${{ github.ref }} + pip freeze + + - name: Run tests + # Only run IBMQ and Runtime tests (skipped otherwise) + run: python -m pytest tests -k 'test_ibmq.py or test_runtime.py' --cov=pennylane_qiskit --cov-report=term-missing --cov-report=xml -p no:warnings --tb=native + env: + IBMQX_TOKEN: ${{ secrets.IBMQX_TOKEN_TEST }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a84f2cbda..c5d43a89a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,13 +29,14 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-ci.txt + pip install -r requirements-ci-legacy.txt pip install wheel pytest pytest-cov pytest-mock flaky --upgrade + pip freeze - name: Install Plugin run: | - python setup.py bdist_wheel - pip install dist/PennyLane*.whl + pip install git+https://github.com/PennyLaneAI/pennylane-qiskit.git@${{ github.ref }} + pip freeze - name: Run tests # Skip IBMQ and Runtime tests as they depend on IBMQ's availability and @@ -69,7 +70,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-ci.txt + pip install -r requirements-ci-legacy.txt pip install wheel pytest pytest-cov pytest-mock pytest-benchmark flaky --upgrade - name: Install Plugin diff --git a/.github/workflows/tests_qiskit_1.yml b/.github/workflows/tests_qiskit_1.yml index f292adc94..103abf4ef 100644 --- a/.github/workflows/tests_qiskit_1.yml +++ b/.github/workflows/tests_qiskit_1.yml @@ -29,18 +29,61 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements-ci0.txt + pip install -r requirements-ci.txt pip install wheel pytest pytest-cov pytest-mock flaky --upgrade + pip freeze + + - name: Install Plugin + run: | + pip install git+https://github.com/PennyLaneAI/pennylane-qiskit.git@${{ github.ref }} + pip freeze + + - name: Run standard Qiskit plugin tests + # Run the standard tests with the most recent version of Qiskit + run: python -m pytest tests -k 'not test_ibmq.py and not test_runtime.py' --cov=pennylane_qiskit --cov-report=term-missing --cov-report=xml -p no:warnings --tb=native + + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.codecov_token }} + file: ./coverage.xml + + integration-tests: + runs-on: ubuntu-latest + + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.4.1 + with: + access_token: ${{ github.token }} + + - uses: actions/checkout@v2 + + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: 3.9 + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-ci.txt + pip install wheel pytest pytest-cov pytest-mock pytest-benchmark flaky --upgrade - name: Install Plugin run: | python setup.py bdist_wheel pip install dist/PennyLane*.whl - - name: Run Qiskit converter tests - # Test conversion to PennyLane with Qiskit 1.0.0 - run: python -m pytest tests/test_converter.py + - name: Run tests + run: | + pl-device-test --device=qiskit.basicsim --tb=short --skip-ops --shots=20000 --device-kwargs backend=basic_simulator + pl-device-test --device=qiskit.aer --tb=short --skip-ops --shots=20000 --device-kwargs backend=qasm_simulator + pl-device-test --device=qiskit.aer --tb=short --skip-ops --shots=None --device-kwargs backend=statevector_simulator + pl-device-test --device=qiskit.aer --tb=short --skip-ops --shots=None --device-kwargs backend=unitary_simulator - - name: Run temporary tests - # tests that test intermediate functionality, will be removed when everything is fully compatible with 1.0 - run: python -m pytest tests/test_new_qiskit_temp.py + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v4 + with: + token: ${{ secrets.codecov_token }} + file: ./coverage.xml diff --git a/.github/workflows/upload.yml b/.github/workflows/upload.yml index 2b43b4026..af9e39e6b 100644 --- a/.github/workflows/upload.yml +++ b/.github/workflows/upload.yml @@ -17,7 +17,7 @@ jobs: - name: Build and install Plugin run: | python -m pip install --upgrade pip wheel - pip install 'qiskit<0.46' + pip install qiskit python setup.py bdist_wheel pip install dist/PennyLane*.whl diff --git a/CHANGELOG.md b/CHANGELOG.md index 06f12042c..c9a826999 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,22 @@ ### New features since last release +* Support is added for using the plugin devices with Qiskit 1.0. As the backend provider ``qiskit.BasicAer`` + is no longer supported by Qiskit in 1.0, this added support does not extend to the ``"qiskit.aer"`` device. + Instead, a ``"qiskit.basicsim"`` device is added, with the new Qiskit implementation of a Python simulator + device, ``BasicSimulator``, as the backend. + [(#493)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/493) + ### Improvements 🛠 +* Following updates to allow device compatibility with Qiskit 1.0, the version of `qiskit-ibm-runtime` is + no longer capped. + [(#508)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/508) + +* The test suite now runs with the most recent `qiskit` and `qiskit-ibm-runtime`, and well as with + `'qiskit==0.45'` and `qiskit-ibm-runtime<0.21` to monitor backward-compatibility. + [(#508)](https://github.com/PennyLaneAI/pennylane-qiskit/pull/508) + ### Breaking changes 💔 ### Deprecations 👋 @@ -15,6 +29,7 @@ ### Contributors ✍️ This release contains contributions from (in alphabetical order): +Lillian M. A. Frederiksen --- # Release 0.35.1 diff --git a/README.rst b/README.rst index baf418632..fe3651891 100644 --- a/README.rst +++ b/README.rst @@ -58,23 +58,15 @@ Installation of this plugin, as well as all dependencies, can be done using ``pi .. code-block:: bash - pip install "qiskit<0.46" pip install pennylane-qiskit -If you prefer to use Qiskit 1.0, you can omit the first line above and instead consult the -`Qiskit installation guide `__, which includes -details on how to migrate to 1.0 if you already have Qiskit installed. -Note that only conversion to PennyLane is supported with Qiskit 1.0; support -for devices with 1.0 be available in a later release of the plugin. - To test that the PennyLane-Qiskit plugin is working correctly you can run .. code-block:: bash make test -in the source folder. Tests restricted to a specific provider can be run by executing -``make test-basicaer``, ``make test-aer``, and ``make test-ibmq``. +in the source folder. .. note:: @@ -88,9 +80,8 @@ in the source folder. Tests restricted to a specific provider can be run by exec `new IBMProvider `_ If this is the case, running ``make test`` also executes tests on the ``ibmq`` device. - By default tests on the ``ibmq`` device run with ``ibmq_qasm_simulator`` backend - and those done by the ``basicaer`` and ``aer`` device are run with the ``qasm_simulator`` - backend. At the time of writing this means that the test are "free". + By default, tests on the ``ibmq`` device run with ``ibmq_qasm_simulator`` backend. At + the time of writing this means that the test are "free". Please verify that this is also the case for your account. .. installation-end-inclusion-marker-do-not-remove diff --git a/doc/devices/aer.rst b/doc/devices/aer.rst index b9ad981eb..2f39c6cf0 100644 --- a/doc/devices/aer.rst +++ b/doc/devices/aer.rst @@ -1,3 +1,5 @@ +.. _aer device page: + The Aer device ============== The ``qiskit.aer`` device provided by the PennyLane-Qiskit plugin allows you to use PennyLane diff --git a/doc/devices/basicaer.rst b/doc/devices/basicaer.rst index 222e4300e..e8746b2c8 100644 --- a/doc/devices/basicaer.rst +++ b/doc/devices/basicaer.rst @@ -1,6 +1,12 @@ The BasicAer device =================== +.. note:: + + Qiskit discontinued their ``BasicAer`` device in the 1.0 release, so this device + is only available for lower versions of Qiskit. For a simple Python simulator + compatible with Qiskit 1.0, use the :ref:`BasicSim device ` instead. + While the ``'qiskit.aer'`` device is the standard go-to simulator that is provided along the Qiskit main package installation, there exists a natively included python simulator that is slower but will work usually without the need to install other dependencies diff --git a/doc/devices/basicsim.rst b/doc/devices/basicsim.rst new file mode 100644 index 000000000..cd4060b0b --- /dev/null +++ b/doc/devices/basicsim.rst @@ -0,0 +1,24 @@ +.. _basicsim device page: + +The BasicSim device +=================== + +Qiskit comes packed with a +`basic pure-Python simulator `_ +that can be accessed in this plugin through: + +.. code-block:: python + + import pennylane as qml + dev = qml.device('qiskit.basicsim', wires=2) + +This device uses the Qiskit ``BasicSimulator`` backend from the +`basic_provider `_ module in Qiskit. + +.. note:: + + The `Qiskit Aer `_ device + provides a fast simulator that is also capable of simulating + noise. It is available as :ref:`"qiskit.aer" `, but the backend must be + installed separately with ``pip install qiskit-aer``. + \ No newline at end of file diff --git a/doc/index.rst b/doc/index.rst index b97654d36..372bf84d8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -14,7 +14,7 @@ can be accessed straightaway in PennyLane, without the need to import new packag Devices ~~~~~~~ -Currently, there are three different devices available: +The following devices are available: .. title-card:: :name: 'qiskit.aer' @@ -27,25 +27,31 @@ Currently, there are three different devices available: :link: devices/basicaer.html .. title-card:: - :name: 'qiskit.ibmq' - :description: Allows integration with qiskit's hardware backends, and hardware-specific simulators. - :link: devices/ibmq.html + :name: 'qiskit.basicsim' + :description: A simple local Python simulator running the Qiskit ``BasicSimulator``. + :link: devices/basicsim.html .. title-card:: :name: 'qiskit.ibmq.circuit_runner' - :description: Allows integration with qiskit's circuit runner runtime program. + :description: Allows integration with Qiskit's circuit runner runtime program. :link: devices/runtime.html .. title-card:: :name: 'qiskit.ibmq.sampler' - :description: Allows integration with qiskit's sampler runtime program. + :description: Allows integration with Qiskit's sampler runtime program. :link: devices/runtime.html .. title-card:: :name: 'qiskit.remote' - :description: Allows integration with any qiskit backend. + :description: Allows integration with any Qiskit backend. :link: devices/remote.html +.. title-card:: + :name: 'qiskit.ibmq' + :description: Allows integration with Qiskit's hardware backends, and hardware-specific simulators. + :link: devices/ibmq.html + + .. raw:: html
@@ -133,6 +139,7 @@ hardware access. devices/aer devices/basicaer + devices/basicsim devices/ibmq devices/runtime devices/remote diff --git a/pennylane_qiskit/__init__.py b/pennylane_qiskit/__init__.py index 1c364a1c1..991b73d7c 100644 --- a/pennylane_qiskit/__init__.py +++ b/pennylane_qiskit/__init__.py @@ -15,7 +15,7 @@ from ._version import __version__ from .aer import AerDevice -from .basic_aer import BasicAerDevice +from .basic_aer import BasicAerDevice, BasicSimulatorDevice from .ibmq import IBMQDevice from .remote import RemoteDevice from .converter import load, load_pauli_op, load_qasm, load_qasm_from_file diff --git a/pennylane_qiskit/basic_aer.py b/pennylane_qiskit/basic_aer.py index 91f985e72..ecf26fce8 100644 --- a/pennylane_qiskit/basic_aer.py +++ b/pennylane_qiskit/basic_aer.py @@ -22,9 +22,12 @@ from .qiskit_device import QiskitDevice +if Version(qiskit.__version__) >= Version("1.0.0"): + from qiskit.providers.basic_provider import BasicProvider + class BasicAerDevice(QiskitDevice): - """A PennyLane device for the native Python Qiskit simulator. + """A PennyLane device for the native Python Qiskit simulator BasicAer. Please see the `Qiskit documentations `_ further information on the backend options and transpile options. @@ -38,7 +41,7 @@ class BasicAerDevice(QiskitDevice): Args: wires (int or Iterable[Number, str]]): Number of subsystems represented by the device, or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) - or strings (``['ancilla', 'q1', 'q2']``). + or strings (``['aux_wire', 'q1', 'q2']``). backend (str): the desired backend shots (int or None): number of circuit evaluations/random samples used to estimate expectation values and variances of observables. For statevector backends, @@ -54,14 +57,55 @@ class BasicAerDevice(QiskitDevice): def __init__(self, wires, shots=1024, backend="qasm_simulator", **kwargs): - max_ver = Version("0.45.3") + max_ver = Version("0.46", partial=True) if Version(qiskit.__version__) > max_ver: raise RuntimeError( - f"The devices in the PennyLane Qiskit plugin are currently only compatible " - f"with versions of Qiskit below 0.46. You have version {qiskit.__version__} " - f"installed. Please downgrade Qiskit to use the devices. The devices will be " - f"updated in the coming weeks to be compatible with Qiskit 1.0!" + f"Qiskit has discontinued the BasicAer device, so it can only be used in" + f"versions of Qiskit below 1.0. You have version {qiskit.__version__} " + f"installed. For a Python simulator, use the 'qiskit.basicsim' device " + f"instead. Alternatively, you can downgrade Qiskit to use the " + f"'qiskit.basicaer' device." ) super().__init__(wires, provider=qiskit.BasicAer, backend=backend, shots=shots, **kwargs) + + +class BasicSimulatorDevice(QiskitDevice): + """A PennyLane device for the native Python Qiskit simulator. + + For more information on the ``BasicSimulator`` backend options and transpile options, please visit the + `BasicProvider documentation `_. + These options can be passed to this plugin device as keyword arguments. + + Args: + wires (int or Iterable[Number, str]]): Number of subsystems represented by the device, + or iterable that contains unique labels for the subsystems as numbers (i.e., ``[-1, 0, 2]``) + or strings (``['aux_wire', 'q1', 'q2']``). + backend (str): the desired backend + shots (int or None): number of circuit evaluations/random samples used + to estimate expectation values and variances of observables. For statevector backends, + setting to ``None`` results in computing statistics like expectation values and variances analytically. + """ + + short_name = "qiskit.basicsim" + + analytic_warning_message = ( + "The plugin does not currently support analytic calculation of expectations, variances " + "and probabilities with the BasicProvider backend {}. Such statistics obtained from this " + "device are estimates based on samples." + ) + + def __init__(self, wires, shots=1024, backend="basic_simulator", **kwargs): + + min_version = Version("1.0.0") + + if Version(qiskit.__version__) < min_version: + raise RuntimeError( + f"The 'qiskit.simulator' device is not compatible with version of Qiskit prior " + f"to 1.0. You have version {qiskit.__version__} installed. For a Python simulator, " + f"use the 'qiskit.basicaer' device instead. Alternatively, upgrade Qiskit " + f"(see https://docs.quantum.ibm.com/start/install) to use the 'qiskit.basicsim' device." + ) + + super().__init__(wires, provider=BasicProvider(), backend=backend, shots=shots, **kwargs) diff --git a/pennylane_qiskit/qiskit_device.py b/pennylane_qiskit/qiskit_device.py index 822ea0d96..70c640150 100644 --- a/pennylane_qiskit/qiskit_device.py +++ b/pennylane_qiskit/qiskit_device.py @@ -22,7 +22,6 @@ import warnings import numpy as np -import qiskit from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import library as lib from qiskit.compiler import transpile @@ -32,8 +31,6 @@ from pennylane import QubitDevice, DeviceError from pennylane.measurements import SampleMP, CountsMP, ClassicalShadowMP, ShadowExpvalMP -from semantic_version import Version - from ._version import __version__ SAMPLE_TYPES = (SampleMP, CountsMP, ClassicalShadowMP, ShadowExpvalMP) @@ -124,6 +121,7 @@ class QiskitDevice(QubitDevice, abc.ABC): _operation_map = QISKIT_OPERATION_MAP _state_backends = { "statevector_simulator", + "simulator_statevector", "unitary_simulator", "aer_simulator_statevector", "aer_simulator_unitary", @@ -142,7 +140,7 @@ class QiskitDevice(QubitDevice, abc.ABC): "Projector", } - hw_analytic_warning_message = ( + analytic_warning_message = ( "The analytic calculation of expectations, variances and " "probabilities is only supported on statevector backends, not on the {}. " "Such statistics obtained from this device are estimates based on samples." @@ -152,16 +150,6 @@ class QiskitDevice(QubitDevice, abc.ABC): def __init__(self, wires, provider, backend, shots=1024, **kwargs): - max_ver = Version("0.45.3") - - if Version(qiskit.__version__) > max_ver: - raise RuntimeError( - f"The devices in the PennyLane Qiskit plugin are currently only compatible " - f"with versions of Qiskit below 0.46. You have version {qiskit.__version__} " - f"installed. Please downgrade Qiskit to use the devices. The devices will be " - f"updated in the coming weeks to be compatible with Qiskit 1.0!" - ) - super().__init__(wires=wires, shots=shots) self.provider = provider @@ -186,18 +174,17 @@ def __init__(self, wires, provider, backend, shots=1024, **kwargs): # Keep track if the user specified analytic to be True if shots is None and not self._is_state_backend: # Raise a warning if no shots were specified for a hardware device - warnings.warn(self.hw_analytic_warning_message.format(backend), UserWarning) + warnings.warn(self.analytic_warning_message.format(backend), UserWarning) self.shots = 1024 self._capabilities["returns_state"] = self._is_state_backend # Perform validation against backend - b = self.backend - if len(self.wires) > int(b.configuration().n_qubits): - raise ValueError( - f"Backend '{backend}' supports maximum {b.configuration().n_qubits} wires" - ) + 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) + if backend_qubits and len(self.wires) > int(backend_qubits): + raise ValueError(f"Backend '{backend}' supports maximum {backend_qubits} wires") # Initialize inner state self.reset() diff --git a/requirements-ci-legacy.txt b/requirements-ci-legacy.txt new file mode 100644 index 000000000..f33b309b7 --- /dev/null +++ b/requirements-ci-legacy.txt @@ -0,0 +1,5 @@ +pennylane>=0.32 +qiskit<0.46 +qiskit-ibm-runtime<0.21 +numpy +sympy diff --git a/requirements-ci.txt b/requirements-ci.txt index 6f0f93d48..ad1fe23f0 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,4 +1,4 @@ pennylane>=0.32 -qiskit<0.46 +qiskit numpy sympy diff --git a/requirements-ci0.txt b/requirements-ci0.txt deleted file mode 100644 index ad1fe23f0..000000000 --- a/requirements-ci0.txt +++ /dev/null @@ -1,4 +0,0 @@ -pennylane>=0.32 -qiskit -numpy -sympy diff --git a/setup.py b/setup.py index 7f45f4b0f..024750016 100644 --- a/setup.py +++ b/setup.py @@ -24,8 +24,8 @@ requirements = [ "qiskit>=0.32", "qiskit-aer", - "qiskit-ibm-runtime<0.21", "qiskit-ibm-provider", + "qiskit-ibm-runtime", "pennylane>=0.30", "numpy", "networkx>=2.2", @@ -46,6 +46,7 @@ 'qiskit.remote = pennylane_qiskit:RemoteDevice', 'qiskit.aer = pennylane_qiskit:AerDevice', 'qiskit.basicaer = pennylane_qiskit:BasicAerDevice', + 'qiskit.basicsim = pennylane_qiskit:BasicSimulatorDevice', 'qiskit.ibmq = pennylane_qiskit:IBMQDevice', 'qiskit.ibmq.circuit_runner = pennylane_qiskit:IBMQCircuitRunnerDevice', 'qiskit.ibmq.sampler = pennylane_qiskit:IBMQSamplerDevice' diff --git a/tests/conftest.py b/tests/conftest.py index 5c8d41f05..d1da409fc 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -18,10 +18,12 @@ import os import pytest import numpy as np +import qiskit import pennylane as qml +from semantic_version import Version from qiskit_ibm_provider import IBMProvider -from pennylane_qiskit import AerDevice, BasicAerDevice +from pennylane_qiskit import AerDevice, BasicAerDevice, BasicSimulatorDevice np.random.seed(42) @@ -36,13 +38,22 @@ A = np.array([[1.02789352, 1.61296440 - 0.3498192j], [1.61296440 + 0.3498192j, 1.23920938 + 0j]]) -state_backends = [ - "statevector_simulator", - "unitary_simulator", - "aer_simulator_statevector", - "aer_simulator_unitary", -] -hw_backends = ["qasm_simulator", "aer_simulator"] +if Version(qiskit.__version__) < Version("1.0.0"): + test_devices = [AerDevice, BasicAerDevice] + hw_backends = ["qasm_simulator", "aer_simulator"] + state_backends = [ + "statevector_simulator", + "unitary_simulator", + ] +else: + test_devices = [AerDevice, BasicSimulatorDevice] + hw_backends = ["qasm_simulator", "aer_simulator", "basic_simulator"] + state_backends = [ + "statevector_simulator", + "unitary_simulator", + "aer_simulator_statevector", + "aer_simulator_unitary", + ] @pytest.fixture @@ -106,15 +117,22 @@ def hardware_backend(request): return request.param -@pytest.fixture(params=[AerDevice, BasicAerDevice]) +@pytest.fixture(params=test_devices) def device(request, backend, shots): - if backend not in state_backends and shots is None: - pytest.skip("Hardware simulators do not support analytic mode") + print("getting a device") + if backend not in state_backends: + if shots is None: + pytest.skip("Hardware simulators do not support analytic mode") + + if backend == "aer_simulator" and not issubclass(request.param, AerDevice): + print("I should be skipping this test") + pytest.skip("Only the AerDevice can use the aer_simulator backend") + + if issubclass(request.param, BasicSimulatorDevice) and backend != "basic_simulator": + pytest.skip("BasicSimulator is the only supported backend for the BasicSimulatorDevice") - if (issubclass(request.param, AerDevice) and "aer" not in backend) or ( - issubclass(request.param, BasicAerDevice) and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + if backend == "basic_simulator" and not issubclass(request.param, BasicSimulatorDevice): + pytest.skip("BasicSimulator is the only supported backend for the BasicSimulatorDevice") def _device(n, device_options=None): if device_options is None: @@ -124,12 +142,17 @@ def _device(n, device_options=None): return _device -@pytest.fixture(params=[AerDevice, BasicAerDevice]) +@pytest.fixture(params=test_devices) def state_vector_device(request, statevector_backend, shots): - if (issubclass(request.param, AerDevice) and "aer" not in statevector_backend) or ( - issubclass(request.param, BasicAerDevice) and "aer" in statevector_backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + + if backend == "aer_simulator" and not issubclass(request.param, AerDevice): + pytest.skip("Only the AerDevice can use the aer_simulator backend") + + if issubclass(request.param, BasicSimulatorDevice) and backend != "basic_simulator": + pytest.skip("BasicSimulator is the only supported backend for the BasicSimulatorDevice") + + if backend == "basic_simulator" and not issubclass(request.param, BasicSimulatorDevice): + pytest.skip("BasicSimulator is the only supported backend for the BasicSimulatorDevice") def _device(n): return request.param(wires=n, backend=statevector_backend, shots=shots) diff --git a/tests/test_integration.py b/tests/test_integration.py index 74e6812ed..9ee982b91 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -4,16 +4,40 @@ import numpy as np import pennylane as qml from pennylane.numpy import tensor +from semantic_version import Version import pytest import qiskit -import qiskit_aer as aer +import qiskit_aer from pennylane_qiskit.qiskit_device import QiskitDevice from qiskit.providers import QiskitBackendNotFoundError from conftest import state_backends -pldevices = [("qiskit.aer", aer.Aer), ("qiskit.basicaer", qiskit.BasicAer)] +if Version(qiskit.__version__) < Version("1.0.0"): + pldevices = [("qiskit.aer", qiskit_aer.Aer), ("qiskit.basicaer", qiskit.BasicAer)] + + def check_provider_backend_compatibility(pldevice, backend_name): + dev_name, _ = pldevice + if (dev_name == "qiskit.aer" and "aer" not in backend_name) or ( + dev_name == "qiskit.basicaer" and "aer" in backend_name + ): + return (False, "Only the AerSimulator is supported on AerDevice") + return True, None + +else: + from qiskit.providers.basic_provider import BasicProvider + + pldevices = [("qiskit.aer", qiskit_aer.Aer), ("qiskit.basicsim", BasicProvider())] + + def check_provider_backend_compatibility(pldevice, backend_name): + dev_name, _ = pldevice + if dev_name == "qiskit.aer" and backend_name == "basic_simulator": + return (False, "basic_simulator is not supported on the AerDevice") + + if dev_name == "qiskit.basicsim" and backend_name != "basic_simulator": + return (False, "Only the basic_simulator backend works with the BasicSimulatorDevice") + return True, None class TestDeviceIntegration: @@ -22,10 +46,11 @@ class TestDeviceIntegration: @pytest.mark.parametrize("d", pldevices) def test_load_device(self, d, backend): """Test that the qiskit device loads correctly""" - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) dev = qml.device(d[0], wires=2, backend=backend, shots=1024) assert dev.num_wires == 2 @@ -42,7 +67,7 @@ def test_load_remote_device_with_backend_instance(self, d, backend): try: backend_instance = provider.get_backend(backend) except QiskitBackendNotFoundError: - pytest.skip("Only the AerSimulator is supported on AerDevice") + pytest.skip("Backend is not compatible with specified device") dev = qml.device("qiskit.remote", wires=2, backend=backend_instance, shots=1024) assert dev.num_wires == 2 @@ -56,12 +81,13 @@ def test_load_remote_device_by_name(self, d, backend): """Test that the qiskit.remote device loads correctly when passed a provider and a backend name. This test is equivalent to `test_load_device` but on the qiskit.remote device instead of specialized devices that expose more configuration options.""" - _, provider = d - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) + + _, provider = d dev = qml.device("qiskit.remote", wires=2, provider=provider, backend=backend, shots=1024) assert dev.num_wires == 2 @@ -102,10 +128,11 @@ def test_args(self): @pytest.mark.parametrize("shots", [None, 8192]) def test_one_qubit_circuit(self, shots, d, backend, tol): """Test that devices provide correct result for a simple circuit""" - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) if backend not in state_backends and shots is None: pytest.skip("Hardware simulators do not support analytic mode") @@ -131,10 +158,10 @@ def circuit(x, y, z): def test_basis_state_and_rot(self, shots, d, backend, tol): """Integration test for the BasisState and Rot operations for non-analytic mode.""" - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) dev = qml.device(d[0], wires=1, backend=backend, shots=shots) @@ -198,15 +225,18 @@ def test_noise_model_qasm_simulator(self, monkeypatch): cache = [] with monkeypatch.context() as m: - m.setattr(aer.AerSimulator, "set_options", lambda *args, **kwargs: cache.append(kwargs)) + m.setattr( + qiskit_aer.AerSimulator, "set_options", lambda *args, **kwargs: cache.append(kwargs) + ) dev = qml.device("qiskit.aer", wires=2, noise_model="test value") assert cache[-1] == {"noise_model": "test value"} def test_invalid_noise_model(self): """Test that the noise model argument causes an exception to be raised if the backend does not support it""" + dev_name = pldevices[1][0] with pytest.raises(AttributeError, match="field noise_model is not valid for this backend"): - dev = qml.device("qiskit.basicaer", wires=2, noise_model="test value") + dev = qml.device(dev_name, wires=2, noise_model="test value") def test_overflow_kwargs(self): """Test all overflow kwargs are extracted for the AerDevice""" @@ -498,8 +528,8 @@ class TestNoise: def test_noise_applied(self): """Test that the qiskit noise model is applied correctly""" - noise_model = aer.noise.NoiseModel() - bit_flip = aer.noise.pauli_error([("X", 1), ("I", 0)]) + noise_model = qiskit_aer.noise.NoiseModel() + bit_flip = qiskit_aer.noise.pauli_error([("X", 1), ("I", 0)]) # Create a noise model where the RX operation always flips the bit noise_model.add_all_qubit_quantum_error(bit_flip, ["z", "rz"]) @@ -522,10 +552,11 @@ class TestBatchExecution: def test_one_qubit_circuit_batch_params(self, shots, d, backend, tol, mocker): """Test that devices provide correct result for a simple circuit using the batch_params transform.""" - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) if backend not in state_backends and shots is None: pytest.skip("Hardware simulators do not support analytic mode") @@ -561,10 +592,11 @@ def circuit(x, y, z): def test_batch_execute_parameter_shift(self, shots, d, backend, tol, mocker): """Test that devices provide correct result computing the gradient of a circuit using the parameter-shift rule and the batch execution pipeline.""" - if (d[0] == "qiskit.aer" and "aer" not in backend) or ( - d[0] == "qiskit.basicaer" and "aer" in backend - ): - pytest.skip("Only the AerSimulator is supported on AerDevice") + + # check compatibility between provider and backend, and skip if incompatible + is_compatible, failure_msg = check_provider_backend_compatibility(d, backend) + if not is_compatible: + pytest.skip(failure_msg) if backend not in state_backends and shots is None: pytest.skip("Hardware simulators do not support analytic mode") diff --git a/tests/test_new_qiskit_temp.py b/tests/test_new_qiskit_temp.py index 3210ba462..2917123b9 100644 --- a/tests/test_new_qiskit_temp.py +++ b/tests/test_new_qiskit_temp.py @@ -2,33 +2,39 @@ import pennylane as qml import qiskit +from semantic_version import Version from unittest.mock import Mock +from pennylane_qiskit import BasicSimulatorDevice -@pytest.mark.parametrize( - "device_name", - [ - "qiskit.aer", - "qiskit.basicaer", - "qiskit.remote", - ], + +@pytest.mark.skipif( + Version(qiskit.__version__) < Version("1.0.0"), + reason="versions below 1.0 are compatible with BasicAer", ) -def test_error_is_raised_if_initalizing_device(monkeypatch, device_name): +def test_error_is_raised_if_initalizing_basicaer_device(monkeypatch): """Test that when Qiskit 1.0 is installed, an error is raised if you try - to initialize a device. This is a temporary test and will be removed along - with the error one everything is compatible with 1.0""" + to initialize the 'qiskit.basicaer' device.""" + + # test that the correct error is actually raised in Qiskit 1.0 (rather than fx an import error) + with pytest.raises( + RuntimeError, + match="Qiskit has discontinued the BasicAer device", + ): + qml.device("qiskit.basicaer", wires=2) - # make it not fail in the normal tests (running 0.46) - if qiskit.__version__ != "1.0.0": - monkeypatch.setattr(qiskit, "__version__", "1.0.0") + +@pytest.mark.skipif( + Version(qiskit.__version__) >= Version("1.0.0"), + reason="versions 1.0 and above are compatible with BasicSimulator", +) +def test_error_is_raised_if_initalizing_basic_simulator_device(monkeypatch): + """Test that when a version of Qiskit below 1.0 is installed, an error is raised if you try + to initialize the BasicSimulatorDevice.""" # test that the correct error is actually raised in Qiskit 1.0 (rather than fx an import error) with pytest.raises( RuntimeError, - match="The devices in the PennyLane Qiskit plugin are currently only compatible with versions of Qiskit below 0.46", + match="device is not compatible with version of Qiskit prior to 1.0", ): - if device_name in ["qiskit.aer", "qiskit.basicaer"]: - qml.device(device_name, wires=2) - else: - # use a Mock backend to avoid call to the remote service - qml.device(device_name, wires=2, backend=Mock()) + BasicSimulatorDevice(wires=2) diff --git a/tests/test_qiskit_device.py b/tests/test_qiskit_device.py index 12884b126..9c8b71dcf 100644 --- a/tests/test_qiskit_device.py +++ b/tests/test_qiskit_device.py @@ -49,14 +49,12 @@ def test_transpilation_option_update(self, device, wires, device_options, transp class TestAnalyticWarningHWSimulator: """Tests the warnings for when the analytic attribute of a device is set to true""" - def test_warning_raised_for_hardware_backend_analytic_expval(self, hardware_backend, recorder): + def test_warning_raised_for_hardware_backend_analytic_expval(self, recorder): """Tests that a warning is raised if the analytic attribute is true on hardware simulators when calculating the expectation""" - if "aer" in hardware_backend: - pytest.skip("Not supported on basicaer") with pytest.warns(UserWarning) as record: - dev = qml.device("qiskit.basicaer", backend=hardware_backend, wires=2, shots=None) + dev = qml.device("qiskit.aer", backend="aer_simulator", wires=2, shots=None) # check that only one warning was raised assert len(record) == 1 @@ -65,18 +63,17 @@ def test_warning_raised_for_hardware_backend_analytic_expval(self, hardware_back record[0].message.args[0] == "The analytic calculation of " "expectations, variances and probabilities is only supported on " "statevector backends, not on the {}. Such statistics obtained from this " - "device are estimates based on samples.".format(dev.backend) + "device are estimates based on samples.".format(dev.backend.name) ) + @pytest.mark.parametrize("method", ["unitary", "statevector"]) def test_no_warning_raised_for_software_backend_analytic_expval( - self, statevector_backend, recorder, recwarn + self, method, recorder, recwarn ): """Tests that no warning is raised if the analytic attribute is true on statevector simulators when calculating the expectation""" - if "aer" in statevector_backend: - pytest.skip("Not supported on basicaer") - dev = qml.device("qiskit.basicaer", backend=statevector_backend, wires=2, shots=None) + dev = qml.device("qiskit.aer", backend="aer_simulator", method=method, wires=2, shots=None) # check that no warnings were raised assert len(recwarn) == 0