diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 0000000..5b21854 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,37 @@ +### Before submitting + +Please complete the following checklist when submitting a PR: + +- [ ] All new features must include a unit test. + If you've fixed a bug or added code that should be tested, add a test to the + test directory! + +- [ ] All new functions and code must be clearly commented and documented. + If you do make documentation changes, make sure that the docs build and + render correctly by running `make docs`. + +- [ ] Ensure that the test suite passes, by running `make test`. + +- [ ] Add a new entry to the `CHANGELOG.md` file, summarizing the + change, and including a link back to the PR. + +- [ ] The PennyLane source code conforms to + [PEP8 standards](https://www.python.org/dev/peps/pep-0008/). + We check all of our code against [Pylint](https://www.pylint.org/). + To lint modified files, simply `pip install pylint`, and then + run `pylint pennylane/path/to/file.py`. + +When all the above are checked, delete everything above the dashed +line and fill in the pull request template. + +------------------------------------------------------------------------------------------------------------ + +**Context:** + +**Description of the Change:** + +**Benefits:** + +**Possible Drawbacks:** + +**Related GitHub Issues:** diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 56255a6..faf2b2a 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -3,11 +3,26 @@ on: - pull_request jobs: - black: + formatting: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v1 - - name: Black Code Formatter - uses: lgeiger/black-action@v1.0.1 + - name: Check-out code + uses: actions/checkout@v1 + + - name: Set up Python + uses: actions/setup-python@v5 with: - args: "-l 100 pennylane_aqt/ --check" + python-version: "3.10" + + - name: Install dependencies + run: pip install black pylint isort + + - name: Run Black + run: | + black -t py310 -t py311 -t py312 -l 100 pennylane_aqt/ --check + black -t py310 -t py311 -t py312 -l 100 tests/ --check + + - name: Run isort + run: | + isort --py 312 --profile black -l 100 -o autoray -p ./pennylane --skip __init__.py --filter-files ./pennylane_aqt --check + isort --py 312 --profile black -l 100 -o autoray -p ./pennylane --skip __init__.py --filter-files ./tests --check diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml new file mode 100644 index 0000000..ac3d3aa --- /dev/null +++ b/.github/workflows/linting.yml @@ -0,0 +1,33 @@ +name: Linting check +on: + - pull_request + +jobs: + linting: + runs-on: ubuntu-latest + steps: + - name: Check-out code + uses: actions/checkout@v1 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install ruff pylint + + - name: Ruff Linter (source-code) + run: ruff check pennylane_aqt/ --preview + + - name: Ruff Linter (test-suite) + run: ruff check tests/ --preview + + - name: Pylint Linter (source-code) + run: pylint --rcfile .pylintrc pennylane_aqt/ + + - name: Pylint Linter (test-suite) + run: pylint --rcfile tests/.pylintrc tests/ + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..8788278 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,64 @@ +repos: +- repo: https://github.com/psf/black + rev: 24.10.0 + hooks: + - id: black + args: [ + "--line-length=100", + "-t", "py39", + "-t", "py310", + "-t", "py311", + ] + exclude: ^doc/ +- repo: https://github.com/PyCQA/isort + rev: 5.13.2 + hooks: + - id: isort + args: + [ + "--py", + "311", + "--profile", + "black", + "-l", + "100", + "-o", + "autoray", + "-p", + "./pennylane_aqt", + "--skip", + "__init__.py", + "--filter-files", + ] + files: ^(pennylane_aqt/|tests/) +- repo: https://github.com/gauge-sh/tach-pre-commit + rev: v0.19.1 + hooks: + - id: tach +- repo: local + hooks: + - id: pylint + name: pylint + entry: pylint + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + "--rcfile=.pylintrc", # Link to your config file + ] + exclude: ^(doc/|tests/) + - id: pylint-test + name: pylint-test + entry: pylint + language: system + types: [python] + args: + [ + "-rn", # Only display messages + "-sn", # Don't display the score + "--rcfile=tests/.pylintrc", # Link to your config file + ] + files: ^(tests/) + diff --git a/.pylintrc b/.pylintrc index fa05d80..c676b46 100644 --- a/.pylintrc +++ b/.pylintrc @@ -28,4 +28,4 @@ ignored-classes=numpy,scipy,autograd # can either give multiple identifiers separated by comma (,) or put this option # multiple times (only on the command line, not in the configuration file where # it should appear only once). -disable=line-too-long,invalid-name,too-many-lines,redefined-builtin,too-many-locals,duplicate-code,bad-continuation +disable=invalid-name,too-many-locals,duplicate-code,import-error diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a4299c..bff96f7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ ### Improvements 🛠 +* Add support for `ruff` and `pylint`. + [(#78)](https://github.com/PennyLaneAI/pennylane-aqt/pull/78) + ### Breaking changes 💔 * The ``qml.QubitStateVector`` template has been removed. Instead, use :class:`~pennylane.StatePrep`. diff --git a/Makefile b/Makefile index 10c4717..ed0cf42 100644 --- a/Makefile +++ b/Makefile @@ -32,12 +32,15 @@ dist: .PHONY : clean clean: - rm -rf pennylane-aqt/__pycache__ + rm -rf __pycache__ + rm -rf pennylane_aqt/__pycache__ rm -rf tests/__pycache__ rm -rf dist rm -rf build rm -rf .pytest_cache rm -rf .coverage coverage_html_report/ + rm -rf .ruff_cache + rm -rf tests/.ruff_cache docs: make -C doc html diff --git a/README.rst b/README.rst index 10196fb..b20728f 100644 --- a/README.rst +++ b/README.rst @@ -77,8 +77,13 @@ Alternatively, you can install PennyLane-AQT from the source code by navigating Software tests ~~~~~~~~~~~~~~ -To ensure that PennyLane-AQT is working correctly after installation, the test suite can be -run by navigating to the source code folder and running +To ensure that PennyLane-AQT is working correctly after installation, the test suite should be +run. Begin by installing the required packages via ``pip`` +:: + + $ pip install -r requirements-ci.txt + +Then navigate to the source code folder and run :: $ make test @@ -129,6 +134,29 @@ All contributers to PennyLane-AQT will be listed as contributors on the releases We also encourage bug reports, suggestions for new features and enhancements, and even links to cool projects or applications built on PennyLane and AQT. +The PennyLane-AQT repository provide a top-level file (``.pre-commit-config.yaml``) +for configuring `pre-commit `_ to run ``black``, ``isort`` and ``pylint`` as a ``git`` +pre-commit hook. Once configured, issuing ``git commit`` will run the tools +automatically. If any of the checks fail, committing fails too. A failed +``black`` check will reformat the required files. Running the pre-commit hook +mechanisms can be disabled for a commit by passing the ``-n/--no-verify`` +option. + +The ``pre-commit`` package can be installed e.g., via ``pip``: + +.. code-block:: bash + + pip install pre-commit + +Then, it can be installed for a specific repository by running + +.. code-block:: bash + + pre-commit install + +in the folder where the ``.pre-commit-config.yaml`` file exists (the top-level +folder for PennyLane-AQT). + Contributors ============ diff --git a/pennylane_aqt/__init__.py b/pennylane_aqt/__init__.py index 249bbc9..06d91fe 100644 --- a/pennylane_aqt/__init__.py +++ b/pennylane_aqt/__init__.py @@ -11,9 +11,9 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -This is the top level module from which all PennyLane-AQT device classes can be directly imported. -""" -from .simulator import AQTSimulatorDevice, AQTNoisySimulatorDevice -from ._version import __version__ +"""Top level module from which all PennyLane-AQT device classes can be directly imported.""" from . import ops +from ._version import __version__ +from .simulator import AQTNoisySimulatorDevice, AQTSimulatorDevice + +__all__ = ["AQTNoisySimulatorDevice", "AQTSimulatorDevice", "__version__", "ops"] diff --git a/pennylane_aqt/_version.py b/pennylane_aqt/_version.py index 88f89f1..75ce6d3 100644 --- a/pennylane_aqt/_version.py +++ b/pennylane_aqt/_version.py @@ -11,9 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +"""Version information.""" -"""Version information. - Version number (major.minor.patch[-label]) -""" - +# Format: (major.minor.patch[-label]). __version__ = "0.40.0-dev" diff --git a/pennylane_aqt/api_client.py b/pennylane_aqt/api_client.py index 4adeaf7..7674959 100644 --- a/pennylane_aqt/api_client.py +++ b/pennylane_aqt/api_client.py @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -API Client +# ruff: noqa: D205 +"""API Client. ========== **Module name:** :mod:`pennylane_aqt.api_client` @@ -32,7 +32,6 @@ ~~~~~~~~~~~~ """ -import urllib import requests SUPPORTED_HTTP_REQUESTS = ["PUT", "POST"] @@ -41,18 +40,14 @@ def verify_valid_status(response): - """ - Check a HTTP response for valid status codes, and raise an exception if - the code is invalid + """Check a HTTP response for valid status codes, and raise an exception if the code is invalid. Args: - response[requests.model.Response]: the response containing the error - - Returns: - bool: whether the response has an acceptable HTTP status code + response (requests.model.Response): the response containing the error Raises: requests.HTTPError: if the status is not valid + """ if response.status_code not in VALID_STATUS_CODES: raise requests.HTTPError(response, response.text) @@ -67,8 +62,12 @@ def submit(request_type, url, request, headers): request (str): JSON-formatted payload headers (dict): HTTP request header + Raises: + ValueError: if invalid HTTP request was provided + Returns: requests.models.Response: the response from the API + """ if request_type not in SUPPORTED_HTTP_REQUESTS: raise ValueError("""Invalid HTTP request method provided. Options are "PUT" or "POST".""") @@ -76,3 +75,4 @@ def submit(request_type, url, request, headers): return requests.put(url, request, headers=headers, timeout=DEFAULT_TIMEOUT) if request_type == "POST": return requests.post(url, request, headers=headers, timeout=DEFAULT_TIMEOUT) + return None # pragma: no cover diff --git a/pennylane_aqt/device.py b/pennylane_aqt/device.py index b37b13f..7f900cc 100644 --- a/pennylane_aqt/device.py +++ b/pennylane_aqt/device.py @@ -11,15 +11,15 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -Alpine Quantum Technologies device class +# ruff: noqa: D205 +"""Alpine Quantum Technologies device class. ======================================== This module contains an abstract base class for constructing AQT devices for PennyLane. """ -import os import json +import os from time import sleep import numpy as np @@ -28,7 +28,7 @@ from pennylane.ops import Adjoint from ._version import __version__ -from .api_client import verify_valid_status, submit +from .api_client import submit, verify_valid_status BASE_SHOTS = 200 @@ -38,7 +38,7 @@ class AQTDevice(QubitDevice): 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 iterable that contains unique number labels for the subsystems (i.e., ``[-1, 0, 2]``) or strings (``['ancilla', 'q1', 'q2']``). shots (int): number of circuit evaluations/random samples used to estimate expectation values of observables @@ -47,6 +47,7 @@ class AQTDevice(QubitDevice): retry_delay (float): The time (in seconds) to wait between requests to the remote server when checking for completion of circuit execution. + """ # pylint: disable=too-many-instance-attributes @@ -95,9 +96,27 @@ class AQTDevice(QubitDevice): HTTP_METHOD = "PUT" def __init__(self, wires, shots=BASE_SHOTS, api_key=None, retry_delay=1): + """Initialize the AQT Device. + + Args: + wires (int or Iterable[Number, str]]): Number of subsystems represented by the device, + or iterable that contains unique number labels (i.e., ``[-1, 0, 2]``) + or strings (``['ancilla', 'q1', 'q2']``). + shots (int): number of circuit evaluations/random samples used + to estimate expectation values of observables + api_key (str): The AQT API key. If not provided, the environment + variable ``AQT_TOKEN`` is used. + retry_delay (float): The time (in seconds) to wait between requests + to the remote server when checking for completion of circuit + execution. + + Raises: + ValueError: AQT base device does not support analytic expectation values + + """ if shots is None: raise ValueError( - "The aqt.base_device device does not support analytic expectation values" + "The aqt.base_device device does not support analytic expectation values", ) super().__init__(wires=wires, shots=shots) @@ -116,39 +135,39 @@ def reset(self): self.samples = None def set_api_configs(self): - """ - Set the configurations needed to connect to AQT API. + """Set the configurations needed to connect to AQT API. + + Raises: + ValueError: if no valid api key for the AQT platform is found + """ self._api_key = self._api_key or os.getenv("AQT_TOKEN") if not self._api_key: raise ValueError("No valid api key for AQT platform found.") self.header = {"Ocp-Apim-Subscription-Key": self._api_key, "SDK": "pennylane"} self.data = {"access_token": self._api_key, "no_qubits": self.num_wires} - self.hostname = "/".join([self.BASE_HOSTNAME, self.TARGET_PATH]) + self.hostname = f"{self.BASE_HOSTNAME}/{self.TARGET_PATH}" @property def retry_delay(self): - """ - The time (in seconds) to wait between requests - to the remote server when checking for completion of circuit - execution. - - """ + """The time (in seconds) to wait between requests to the remote server.""" return self._retry_delay @retry_delay.setter def retry_delay(self, time): - """Changes the devices's ``retry_delay`` property. + """Change devices's ``retry_delay`` property. Args: time (float): time (in seconds) to wait between calls to remote server Raises: DeviceError: if the retry delay is not a positive number + """ if time <= 0: + msg = f"The specified retry delay needs to be positive. Got {time}." raise DeviceError( - "The specified retry delay needs to be positive. Got {}.".format(time) + msg, ) self._retry_delay = float(time) @@ -159,18 +178,57 @@ def operations(self): Returns: set[str]: the set of PennyLane operation names the device supports + """ return set(self._operation_map.keys()) + # ruff: noqa: D417 def apply(self, operations, **kwargs): + """Apply quantum operations, rotate the circuit into the measurement + basis, and compile and execute the quantum circuit. + + This method receives a list of quantum operations queued by the QNode, + and should be responsible for: + + * Constructing the quantum program + * (Optional) Rotating the quantum circuit using the rotation + operations provided. This diagonalizes the circuit so that arbitrary + observables can be measured in the computational basis. + * Compile the circuit + * Execute the quantum circuit + + Both arguments are provided as lists of PennyLane :class:`~.Operation` + instances. Useful properties include :attr:`~.Operation.name`, + :attr:`~.Operation.wires`, and :attr:`~.Operation.parameters`: + + >>> op = qml.RX(0.2, wires=[0]) + >>> op.name # returns the operation name + "RX" + >>> op.wires # returns a Wires object representing the wires that the operation acts on + Wires([0]) + >>> op.parameters # returns a list of parameters + [0.2] + + Args: + operations (list[~.Operation]): operations to apply to the device + + Keyword Args: + rotations (list[~.Operation]): operations that rotate the circuit + pre-measurement into the eigenbasis of the observables. + hash (int): the hash value of the circuit constructed by `CircuitGraph.hash` + + Raises: + DeviceError: If an operation is not where it is supposed to be in the circuit. + ValueError: If something went wrong with the HTTP request. + + """ rotations = kwargs.pop("rotations", []) for i, operation in enumerate(operations): if i > 0 and operation.name in {"BasisState", "StatePrep"}: + msg = f"Operation {operation.name} is only supported at the beginning of a circuit." raise DeviceError( - "The operation {} is only supported at the beginning of a circuit.".format( - operation.name - ) + msg, ) self._apply_operation(operation) @@ -179,6 +237,7 @@ def apply(self, operations, **kwargs): self._apply_operation(operation) # create circuit job for submission + # pylint: disable = attribute-defined-outside-init self.circuit_json = self.serialize(self.circuit) self.data["repetitions"] = self.shots job_submission = {**self.data, "data": self.circuit_json} @@ -195,18 +254,23 @@ def apply(self, operations, **kwargs): error_msg = job.get("ERROR", None) if error_msg: + msg = f"Something went wrong with the request, got the error message: {error_msg}" raise ValueError( - f"Something went wrong with the request, got the error message: {error_msg}" + msg, ) + # pylint: disable = attribute-defined-outside-init self.samples = job["samples"] + # ruff: noqa: PLR0912 + # ruff: noqa: C901 + # pylint: disable = too-many-branches def _apply_operation(self, operation): - """ - Add the specified operation to ``self.circuit`` with the native AQT op name. + """Add the specified operation to ``self.circuit`` with the native AQT op name. Args: - operation[pennylane.operation.Operation]: the operation instance to be applied + operation (pennylane.operation.Operation): the operation instance to be applied + """ op_name = operation.name if isinstance(operation, Adjoint): @@ -229,7 +293,7 @@ def _apply_operation(self, operation): self.circuit.append([op_name, par[0], par[1], device_wire_labels]) return if op_name == "BasisState": - for bit, label in zip(par, device_wire_labels): + for bit, label in zip(par, device_wire_labels, strict=False): if bit == 1: self._append_op_to_queue("RX", np.pi, device_wire_labels=[label]) return @@ -251,8 +315,8 @@ def _apply_operation(self, operation): if op_name == "S": op_name = "RZ" par = 0.5 * np.pi - elif op_name in ("PauliX", "PauliY", "PauliZ"): - op_name = "R{}".format(op_name[-1]) + elif op_name in {"PauliX", "PauliY", "PauliZ"}: + op_name = f"R{op_name[-1]}" par = np.pi elif op_name == "MS": par *= np.pi @@ -263,32 +327,43 @@ def _apply_operation(self, operation): self._append_op_to_queue(op_name, par, device_wire_labels) def _append_op_to_queue(self, op_name, par, device_wire_labels): - """ - Append the given operation to the circuit queue in the correct format for AQT API. + """Append the given operation to the circuit queue in the correct format for AQT API. Args: - op_name[str]: the PennyLane name of the op - par[float]: the numeric parameter value for the op - device_wire_labels[list[int]]: wire labels on the device which the op is to be applied on + op_name (str): the PennyLane name of the op + par (float): the numeric parameter value for the op + device_wire_labels (list[int]): device wire labels for which the op is to be applied on + + Raises: + DeviceError: if operation is not supported on AQT devices + """ - if not op_name in self.operations: + if op_name not in self.operations: raise DeviceError("Operation {} is not supported on AQT devices.") - par = par / np.pi # AQT convention: all gates differ from PennyLane by factor of pi + par /= np.pi # AQT convention: all gates differ from PennyLane by factor of pi aqt_op_name = self._operation_map[op_name] self.circuit.append([aqt_op_name, par, device_wire_labels]) @staticmethod def serialize(circuit): - """ - Serialize ``circuit`` to a valid AQT-formatted JSON string. + """Serialize ``circuit`` to a valid AQT-formatted JSON string. Args: - circuit[list[list]]: a list of lists of the form - [["X", 0.3, [0]], ["Z", 0.1, [2]], ...] + circuit (list[list]): a list of lists of the form + [["X", 0.3, [0]], ["Z", 0.1, [2]], ...] + + Returns: + serialized_circuit (str): the serialized circuit as a JSON string. + """ return json.dumps(circuit) def generate_samples(self): + r"""Return the computational basis samples generated for all wires. + + Returns: + array[complex]: array of samples in the shape ``(dev.shots, dev.num_wires)`` + + """ # AQT indexes in reverse scheme to PennyLane, so we have to specify "F" ordering - samples_array = np.stack(np.unravel_index(self.samples, [2] * self.num_wires, order="F")).T - return samples_array + return np.stack(np.unravel_index(self.samples, [2] * self.num_wires, order="F")).T diff --git a/pennylane_aqt/ops.py b/pennylane_aqt/ops.py index 42fff4b..1a6c039 100644 --- a/pennylane_aqt/ops.py +++ b/pennylane_aqt/ops.py @@ -11,16 +11,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -This module contains custom AQT operations, designed to be used in PennyLane -QNodes when using the PennyLane-AQT devices. -""" +"""Contains custom AQT operations, designed to be used in PennyLane QNodes.""" +# pylint: disable = too-few-public-methods from pennylane.operation import Operation class R(Operation): - r"""R(wires) - Two-parameter rotation gate. + r"""R(wires), Two-parameter rotation gate. .. math:: R(t,p) = \begin{bmatrix} \cos(t\tfrac{\pi}{2}) & -i e^{-ip\pi}\sin(t\tfrac{\pi}{2}) \\ @@ -36,6 +33,7 @@ class R(Operation): Args: wires (int): the subsystem the gate acts on + """ num_params = 2 @@ -45,8 +43,7 @@ class R(Operation): class MS(Operation): - r"""MS(wires) - Mølmer-Sørenson gate. + r"""MS(wires), Mølmer-Sørenson gate. .. math:: MS(t) = \begin{bmatrix} \cos(t\tfrac{\pi}{2}) & 0 & 0 & -i\sin(t\tfrac{\pi}{2}) \\ @@ -64,6 +61,7 @@ class MS(Operation): Args: wires (int): the subsystem the gate acts on + """ num_params = 1 diff --git a/pennylane_aqt/simulator.py b/pennylane_aqt/simulator.py index 86e24b7..4c8656b 100644 --- a/pennylane_aqt/simulator.py +++ b/pennylane_aqt/simulator.py @@ -11,8 +11,8 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -""" -AQT Simulator Devices +# ruff: noqa: D205 +"""AQT Simulator Devices. ===================== **Module name:** :mod:`pennylane_aqt.simulator` @@ -65,6 +65,7 @@ class AQTNoisySimulatorDevice(AQTDevice): to estimate expectation values of observables api_key (str): The AQT API key. If not provided, the environment variable ``AQT_TOKEN`` is used. + """ name = "AQT Noisy Simulator device for PennyLane" diff --git a/requirements-ci.txt b/requirements-ci.txt index ba17534..2001f3e 100644 --- a/requirements-ci.txt +++ b/requirements-ci.txt @@ -1,2 +1,2 @@ -pennylane requests +pytest diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 0000000..3c1c20c --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,5 @@ +ruff +pylint +black +isort +pre-commit diff --git a/requirements.txt b/requirements.txt index 97f6a11..18b2825 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,19 +1 @@ -appdirs==1.4.4 -autograd==1.4 -autoray==0.2.5 -cachetools==5.0.0 -certifi==2021.10.8 -charset-normalizer==2.0.12 -future==0.18.2 -idna==3.3 -networkx==2.8 -ninja==1.10.2.3 -numpy==1.22.3 -PennyLane==0.23.0 -PennyLane-Lightning==0.23.0 -requests==2.27.1 -retworkx==0.11.0 -scipy==1.8.0 -semantic-version==2.6.0 -toml==0.10.2 -urllib3==1.26.9 \ No newline at end of file +pennylane diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..d8385b9 --- /dev/null +++ b/ruff.toml @@ -0,0 +1,61 @@ +target-version = "py310" + +line-length = 100 + +[format] + +[lint] +select = ["ALL"] + +ignore = [ + "ANN001", # Missing type annotation for function argument + "ANN002", # Missing type annotation for `*args` + "ANN003", # Missing type annotation for `**kwargs` + "ANN201", # Missing return type annotation for public function + "ANN202", # Missing return type annotation for private function + "ANN204", # Missing return type annotation for special method `__init__` + "ANN205", # Missing return type annotation for staticmethod + "ANN401", # Dynamical typed expressions (typing.Any) are disallowed + # "B905", # `zip()` without an explicit `strict=` parameter + # "D101", # Missing docstring in public method + # "D102", # Missing docstring in public class + # "D103", # Missing docstring in public function + # "D107", # Missing docstring in `__init__` + "D203", # One blank line before class + # "D205", # 1 blank line required between summary line and description + # "D212", # Multi-line docstring summary should start at the first line + # "D213", # Multi-line docstring summary should start at the second line + # "D404", # First word of the docstring should not be "This" + # "DOC201", # `return` is not documented in docstring + # "DOC402", # `yield` is not documented in docstring + # "E741", # Ambiguous variable name + # "E501", # Line too long + "EM101", # Exception must not use a string literal, assign to variable first + # "EXE002", # The file is executable but no shebang is present + "TRY003", # Avoid specifying long messages outside the exception class + # "S101", # Use of `assert` detected + # "SLF001", # Private member accessed + # "PLR0913", # pylint: too-many-arguments, Too many arguments in function definition + "PLR2004", # pylint: magic-value-comparison, Magic value used in comparison, consider replacing with a constant variable + # "PLR6301", # pylint: no-self-use, Method could be a function, class method, or static method + # "PLR0917", # pylint: too-many-positional-arguments, Too many positional arguments + # "PLR0914", # pylint: too-many-locals, Too many local variables + # "PLC0415", # pylint: import-outside-toplevel, `import` should be at the top-level of the file + # "PLC2701", # pylint: import-private-name, Private name import from external module + # "PLR0915", # pylint: too-many-statements, Too many statements + # "PLR0904", # pylint: too-many-public-methods, Too many public methods + "RUF012", # Mutable class attributes should be annotated with `typing.ClassVar` + # "N806", # Variable in function should be lowercase + # "N802", # Function name should be lowercase + # "FIX002", # Line contains TODO, consider resolving the issue + # "FIX004", # Line contains HACK, consider resolving the issue + # "TD002", # Missing author in TODO + # "TD003", # Missing issue link on the line following this TODO + # "FURB101", # `open` and `read` should be replaced by `Path(X).read_text(encoding="utf")` + # "FURB103", # `open` and `write` should be replaced by `Path(X)....` + # "PTH123", # `open()` should be replaced by `Path.open()` + # "PTH111", # `os.path.expanduser()` should be replaced by `Path.expanduser()` + # "UP015", # Unnecessary open mode parameters + # "NPY002", # Replace legacy `np.random.seed` call with `np.random.Generator` + "I", # Import related checks +] \ No newline at end of file diff --git a/tach.toml b/tach.toml new file mode 100644 index 0000000..59e3b1a --- /dev/null +++ b/tach.toml @@ -0,0 +1,10 @@ +exclude = [ + ".*__pycache__", + ".*egg-info", + "docs", + "tests", +] +source_roots = [ + ".", + "pennylane_aqt", +] diff --git a/tests/.pylintrc b/tests/.pylintrc new file mode 100644 index 0000000..8104208 --- /dev/null +++ b/tests/.pylintrc @@ -0,0 +1,31 @@ +[MASTER] +# A comma-separated list of packages or module names from where C extensions may +# be loaded. Extensions are loaded into the active Python interpreter and may +# run arbitrary code +extension-pkg-whitelist=numpy,scipy,autograd + +[TYPECHECK] + +# List of module names for which member attributes should not be checked +# (useful for modules/projects where namespaces are manipulated during runtime +# and thus existing member attributes cannot be deduced by static analysis. It +# supports qualified module names, as well as Unix pattern matching. +ignored-modules=numpy,scipy,autograd + +# List of class names for which member attributes should not be checked +# (useful for classes with attributes dynamically set). This can work +# with qualified names. +ignored-classes=numpy,scipy,autograd + +[MESSAGES CONTROL] + +# Enable the message, report, category or checker with the given id(s). You can +# either give multiple identifiers separated by comma (,) or put this option +# multiple times. +#enable= + +# Disable the message, report, category or checker with the given id(s). You +# can either give multiple identifiers separated by comma (,) or put this option +# multiple times (only on the command line, not in the configuration file where +# it should appear only once). +disable=too-few-public-methods, too-many-public-methods, invalid-name, import-error diff --git a/tests/conftest.py b/tests/conftest.py index 7aff145..e0defe2 100755 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +"""Configuration files for tests.""" import numpy as np np.random.seed(42) diff --git a/tests/ruff.toml b/tests/ruff.toml new file mode 100644 index 0000000..4946c7d --- /dev/null +++ b/tests/ruff.toml @@ -0,0 +1,60 @@ +target-version = "py310" + +line-length = 100 + +[format] + +[lint] +select = ["ALL"] + +ignore = [ + "ANN001", # Missing type annotation for function argument + "ANN002", # Missing type annotation for `*args` + "ANN003", # Missing type annotation for `**kwargs` + "ANN201", # Missing return type annotation for public function + "ANN202", # Missing return type annotation for private function + "ANN204", # Missing return type annotation for special method `__init__` + "ANN205", # Missing return type annotation for staticmethod + "ANN401", # Dynamical typed expressions (typing.Any) are disallowed + "B905", # `zip()` without an explicit `strict=` parameter + "D101", # Missing docstring in public method + "D102", # Missing docstring in public class + "D103", # Missing docstring in public function + "D107", # Missing docstring in `__init__` + "D203", # One blank line before class + "D205", # 1 blank line required between summary line and description + "D212", # Multi-line docstring summary should start at the first line + "D213", # Multi-line docstring summary should start at the second line + "D404", # First word of the docstring should not be "This" + "DOC201", # `return` is not documented in docstring + "DOC402", # `yield` is not documented in docstring + "E741", # Ambiguous variable name + "E501", # Line too long + "EM101", # Exception must not use a string literal, assign to variable first + "EXE002", # The file is executable but no shebang is present + "TRY003", # Avoid specifying long messages outside the exception class + "S101", # Use of `assert` detected + "SLF001", # Private member accessed + "PLR0913", # pylint: too-many-arguments, Too many arguments in function definition + "PLR2004", # pylint: magic-value-comparison, Magic value used in comparison, consider replacing with a constant variable + "PLR6301", # pylint: no-self-use, Method could be a function, class method, or static method + "PLR0917", # pylint: too-many-positional-arguments, Too many positional arguments + "PLR0914", # pylint: too-many-locals, Too many local variables + "PLC0415", # pylint: import-outside-toplevel, `import` should be at the top-level of the file + "PLC2701", # pylint: import-private-name, Private name import from external module + "PLR0915", # pylint: too-many-statements, Too many statements + "PLR0904", # pylint: too-many-public-methods, Too many public methods + "N806", # Variable in function should be lowercase + "N802", # Function name should be lowercase + "FIX002", # Line contains TODO, consider resolving the issue + "FIX004", # Line contains HACK, consider resolving the issue + "TD002", # Missing author in TODO + "TD003", # Missing issue link on the line following this TODO + "FURB101", # `open` and `read` should be replaced by `Path(X).read_text(encoding="utf")` + "FURB103", # `open` and `write` should be replaced by `Path(X)....` + "PTH123", # `open()` should be replaced by `Path.open()` + "PTH111", # `os.path.expanduser()` should be replaced by `Path.expanduser()` + "UP015", # Unnecessary open mode parameters + "NPY002", # Replace legacy `np.random.seed` call with `np.random.Generator` + "I", # Import related checks +] \ No newline at end of file diff --git a/tests/test_api_client.py b/tests/test_api_client.py index 9691438..1c66c93 100755 --- a/tests/test_api_client.py +++ b/tests/test_api_client.py @@ -11,11 +11,11 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for the api_client module""" -import pytest +"""Tests for the api_client module.""" +import json +import pytest import requests -import json from pennylane_aqt import api_client @@ -24,6 +24,7 @@ SOME_HEADER = {"Auth-token": "ABC123"} +# pylint: disable = missing-class-docstring, too-few-public-methods class MockResponse: def __init__(self, *args, **kwargs): self.args = args @@ -35,16 +36,14 @@ class TestAPIClient: @pytest.mark.parametrize("status_code", [200, 201, 202]) def test_verify_valid_status_codes(self, status_code): - """Tests that the function ``verify_valid_status` returns does not raise - exceptions for responses with valid status codes.""" + """Tests that not exceptions are raised for responses with valid status codes.""" resp = requests.Response() resp.status_code = status_code api_client.verify_valid_status(resp) @pytest.mark.parametrize("status_code", [404, 123, 400]) def test_raise_invalid_status_exception(self, status_code): - """Tests that the function ``verify_valid_status`` raises - HTTPError exceptions for bad status codes.""" + """Tests that HTTPError is raised for bad status codes.""" resp = requests.Response() resp.status_code = status_code with pytest.raises(requests.HTTPError): @@ -52,14 +51,12 @@ def test_raise_invalid_status_exception(self, status_code): @pytest.mark.parametrize("method", ["PUSH", "GET", "BREAD", "CHOCOLATE"]) def test_submit_invalid_method(self, method): - """Tests that ``submit`` raises an exception when the request type is - invalid.""" - - with pytest.raises(ValueError, match="Invalid HTTP request method provided."): + """Tests that ``submit`` raises an exception when the request type is invalid.""" + with pytest.raises(ValueError, match=r"Invalid HTTP request method provided."): api_client.submit(method, SOME_URL, SOME_PAYLOAD, SOME_HEADER) def test_submit_put_request(self, monkeypatch): - """Tests that passing the arg "PUT" creates a response via ``requests.put``""" + """Tests that passing the arg "PUT" creates a response via ``requests.put``.""" def mock_put(*args, **kwargs): return MockResponse(*args, **kwargs) @@ -71,7 +68,7 @@ def mock_put(*args, **kwargs): assert response.kwargs == ({"headers": SOME_HEADER, "timeout": 1.0}) def test_submit_post_request(self, monkeypatch): - """Tests that passing the arg "POST" creates a response via ``requests.post``""" + """Tests that passing the arg "POST" creates a response via ``requests.post``.""" def mock_put(*args, **kwargs): return MockResponse(*args, **kwargs) diff --git a/tests/test_device.py b/tests/test_device.py index 7368e63..e84634f 100755 --- a/tests/test_device.py +++ b/tests/test_device.py @@ -11,19 +11,19 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -"""Tests for the AQTDevice class""" +"""Tests for the AQTDevice class.""" +# pylint: disable = protected-access, missing-class-docstring, missing-function-docstring, too-few-public-methods, too-many-public-methods import os -import pytest -import appdirs -import requests -import pennylane as qml import numpy as np +import pennylane as qml +import pytest +import requests import pennylane_aqt.device from pennylane_aqt import ops from pennylane_aqt.device import AQTDevice -from pennylane_aqt.simulator import AQTSimulatorDevice, AQTNoisySimulatorDevice +from pennylane_aqt.simulator import AQTNoisySimulatorDevice, AQTSimulatorDevice API_HEADER_KEY = "Ocp-Apim-Subscription-Key" BASE_HOSTNAME = "https://gateway.aqt.eu/marmot" @@ -31,7 +31,7 @@ SOME_API_KEY = "ABC123" -test_config = """\ +test_config = f"""\ [main] shots = 1000 @@ -40,10 +40,8 @@ [aqt.sim] shots = 99 -api_key = "{}" -""".format( - SOME_API_KEY -) +api_key = "{SOME_API_KEY}" +""" # samples obtained directly from AQT platform # for a three-qubit circuit ([q0, q1, q2]) @@ -68,26 +66,24 @@ class TestAQTDevice: @pytest.mark.parametrize("retry_delay", [0.1, 1.0]) def test_default_init(self, num_wires, shots, retry_delay): """Tests that the device is properly initialized.""" - dev = AQTDevice(num_wires, shots, SOME_API_KEY, retry_delay) assert dev.num_wires == num_wires assert dev.shots == shots assert dev.retry_delay == retry_delay - assert dev.analytic == False - assert dev.circuit == [] - assert dev.circuit_json == "" + assert dev.analytic is False + assert not dev.circuit + assert not dev.circuit_json assert dev.samples is None assert dev.BASE_HOSTNAME == BASE_HOSTNAME assert dev.HTTP_METHOD == HTTP_METHOD - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY def test_reset(self): """Tests that the ``reset`` method corretly resets data.""" - dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev.circuit = [["RX", 0.5, [0]]] dev.circuit_json = "some dummy string" @@ -96,14 +92,13 @@ def test_reset(self): dev.reset() - assert dev.circuit == [] - assert dev.circuit_json == "" - assert dev.samples == None + assert not dev.circuit + assert not dev.circuit_json + assert dev.samples is None assert dev.shots == 55 # should not be reset def test_retry_delay(self): """Tests that the ``retry_delay`` property can be set manually.""" - dev = AQTDevice(3, api_key=SOME_API_KEY, retry_delay=2.5) assert dev.retry_delay == 2.5 @@ -115,7 +110,6 @@ def test_retry_delay(self): def test_set_api_configs(self): """Tests that the ``set_api_configs`` method properly (re)sets the API configs.""" - dev = AQTDevice(3, api_key=SOME_API_KEY) new_api_key = "ZZZ000" dev._api_key = new_api_key @@ -129,20 +123,21 @@ def test_set_api_configs(self): def test_api_key_not_found_error(self, monkeypatch, tmpdir): """Tests that an error is thrown with the device is created without - a valid API token.""" - + a valid API token. + """ monkeypatch.setenv("AQT_TOKEN", "") monkeypatch.setenv("PENNYLANE_CONF", "") monkeypatch.setattr("os.curdir", tmpdir.join("folder_without_a_config_file")) monkeypatch.setattr( - "pennylane.default_config", qml.Configuration("config.toml") + "pennylane.default_config", + qml.Configuration("config.toml"), ) # force loading of config with pytest.raises(ValueError, match="No valid api key for AQT platform found"): - dev = AQTDevice(2) + AQTDevice(2) @pytest.mark.parametrize( - "circuit, expected", + ("circuit", "expected"), [ ([["X", 0.33, [1]], ["Y", 1.55, [2]]], '[["X", 0.33, [1]], ["Y", 1.55, [2]]]'), ([["MS", 1.2, [0, 1]], ["Y", 1.55, [2]]], '[["MS", 1.2, [0, 1]], ["Y", 1.55, [2]]]'), @@ -151,13 +146,14 @@ def test_api_key_not_found_error(self, monkeypatch, tmpdir): ) def test_serialize(self, circuit, expected): """Tests that the ``serialize`` static method correctly converts - from a list of lists into an acceptable JSON string.""" + from a list of lists into an acceptable JSON string. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) res = dev.serialize(circuit) assert res == expected @pytest.mark.parametrize( - "samples, indices", + ("samples", "indices"), [ (REF_SAMPLES_000, [0, 0, 0]), (REF_SAMPLES_001, [0, 0, 1]), @@ -171,8 +167,8 @@ def test_serialize(self, circuit, expected): ) def test_generate_samples(self, samples, indices): """Tests that the generate_samples function of AQTDevice provides samples in - the correct format expected by PennyLane.""" - + the correct format expected by PennyLane. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) dev.shots = 10 dev.samples = samples @@ -185,10 +181,10 @@ def test_generate_samples(self, samples, indices): @pytest.mark.parametrize("wires", [[0], [1], [2]]) def test_apply_operation_hadamard(self, wires): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane Hadamard operation is provided.""" - + queue when a PennyLane Hadamard operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(qml.Hadamard(wires=wires)) @@ -197,10 +193,10 @@ def test_apply_operation_hadamard(self, wires): @pytest.mark.parametrize("wires", [[0, 1], [1, 0], [1, 2], [2, 1], [0, 2], [2, 0]]) def test_operation_cnot(self, wires): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane CNOT operation is provided.""" - + queue when a PennyLane CNOT operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(qml.CNOT(wires=wires)) @@ -216,10 +212,10 @@ def test_operation_cnot(self, wires): @pytest.mark.parametrize("wires", [[0], [1], [2]]) def test_apply_operation_S(self, wires): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane S operation is provided.""" - + queue when a PennyLane S operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(qml.S(wires=wires)) @@ -227,21 +223,22 @@ def test_apply_operation_S(self, wires): @pytest.mark.parametrize("wires", [[0], [1], [2]]) @pytest.mark.parametrize( - "op, aqt_name", [(qml.PauliX, "X"), (qml.PauliY, "Y"), (qml.PauliZ, "Z")] + ("op", "aqt_name"), + [(qml.PauliX, "X"), (qml.PauliY, "Y"), (qml.PauliZ, "Z")], ) def test_apply_operation_pauli(self, wires, op, aqt_name): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane Pauli operation is provided.""" - + queue when a PennyLane Pauli operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(op(wires=wires)) assert dev.circuit == [[aqt_name, 1.0, wires]] @pytest.mark.parametrize( - "op,par,wires,aqt_name", + ("op", "par", "wires", "aqt_name"), [ (qml.RX, 0.51, [0], "X"), (qml.RX, 0.22, [1], "X"), @@ -253,10 +250,10 @@ def test_apply_operation_pauli(self, wires, op, aqt_name): ) def test_apply_operation_rotations(self, op, par, wires, aqt_name): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane RX, RY, or RZ operation is provided.""" - + queue when a PennyLane RX, RY, or RZ operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(op(par, wires=wires)) aqt_par = par / np.pi @@ -264,7 +261,7 @@ def test_apply_operation_rotations(self, op, par, wires, aqt_name): assert dev.circuit == [[aqt_name, aqt_par, wires]] @pytest.mark.parametrize( - "wires,state", + ("wires", "state"), [ ([0], [0]), ([0], [1]), @@ -285,10 +282,10 @@ def test_apply_operation_rotations(self, op, par, wires, aqt_name): ) def test_apply_operation_basisstate(self, wires, state): """Tests that the _apply_operation method correctly populates the circuit - queue when a PennyLane BasisState operation is provided.""" - + queue when a PennyLane BasisState operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(qml.BasisState(np.array(state), wires=wires)) expected_circuit = [] @@ -300,8 +297,8 @@ def test_apply_operation_basisstate(self, wires, state): def test_apply_basisstate_not_first_exception(self): """Tests that the apply method raises an exception when BasisState - is not the first operation.""" - + is not the first operation. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) with pytest.raises(qml.DeviceError, match="only supported at the beginning of a circuit"): @@ -309,8 +306,8 @@ def test_apply_basisstate_not_first_exception(self): def test_apply_statevector_not_first_exception(self): """Tests that the apply method raises an exception when StatePrep - is not the first operation.""" - + is not the first operation. + """ dev = AQTDevice(2, api_key=SOME_API_KEY) state = np.ones(8) / np.sqrt(8) @@ -319,8 +316,8 @@ def test_apply_statevector_not_first_exception(self): def test_apply_raises_for_error(self, monkeypatch): """Tests that the apply method raises an exception when an Error has - been recorded in the response.""" - + been recorded in the response. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) some_error_msg = "Error happened." @@ -332,12 +329,13 @@ def __init__(self): def json(self): return {"ERROR": some_error_msg, "status": "finished", "id": 1} + # ruff: noqa: ARG005 monkeypatch.setattr(pennylane_aqt.device, "submit", lambda *args, **kwargs: MockResponse()) with pytest.raises(ValueError, match="Something went wrong with the request"): dev.apply([]) @pytest.mark.parametrize( - "op, wires, expected_circuit", + ("op", "wires", "expected_circuit"), [ (qml.PauliX, [0], [["X", -1.0, [0]]]), (qml.PauliY, [1], [["Y", -1.0, [1]]]), @@ -348,15 +346,15 @@ def json(self): ) def test_apply_unparametrized_operation_inverse(self, op, wires, expected_circuit): """Tests that inverse operations get recognized and converted to correct parameters for - unparametrized ops.""" - + unparametrized ops. + """ dev = AQTDevice(2, api_key=SOME_API_KEY) dev._apply_operation(qml.adjoint(op(wires=wires))) assert dev.circuit == expected_circuit @pytest.mark.parametrize( - "op, pars, wires, expected_circuit", + ("op", "pars", "wires", "expected_circuit"), [ (qml.RX, [0.5], [0], [["X", -0.5 / np.pi, [0]]]), (qml.RY, [1.3], [1], [["Y", -1.3 / np.pi, [1]]]), @@ -369,8 +367,8 @@ def test_apply_unparametrized_operation_inverse(self, op, wires, expected_circui ) def test_apply_parametrized_operation_inverse(self, op, pars, wires, expected_circuit): """Tests that inverse operations get recognized and converted to correct parameters for - parametrized ops.""" - + parametrized ops. + """ dev = AQTDevice(2, api_key=SOME_API_KEY) dev._apply_operation(qml.adjoint(op(*pars, wires=wires))) @@ -380,10 +378,10 @@ def test_apply_parametrized_operation_inverse(self, op, pars, wires, expected_ci @pytest.mark.parametrize("par", [0.5, 0.3, -1.1]) def test_apply_operation_MS(self, wires, par): """Tests that the _apply_operation method correctly populates the circuit - queue when a MS gate operation is provided.""" - + queue when a MS gate operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(ops.MS(par, wires=wires)) @@ -394,10 +392,10 @@ def test_apply_operation_MS(self, wires, par): @pytest.mark.parametrize("par1", [1.1, -0.8, 0.0]) def test_apply_operation_R(self, wires, par0, par1): """Tests that the _apply_operation method correctly populates the circuit - queue when a R gate operation is provided.""" - + queue when a R gate operation is provided. + """ dev = AQTDevice(3, api_key=SOME_API_KEY) - assert dev.circuit == [] + assert not dev.circuit dev._apply_operation(ops.R(par0, par1, wires=wires)) @@ -405,8 +403,8 @@ def test_apply_operation_R(self, wires, par0, par1): def test_unsupported_operation_exception(self): """Tests whether an exception is raised if an unsupported operation - is attempted to be appended to queue.""" - + is attempted to be appended to queue. + """ dev = AQTDevice(1, api_key=SOME_API_KEY) with pytest.raises(qml.DeviceError, match="is not supported on AQT devices"): @@ -414,68 +412,70 @@ def test_unsupported_operation_exception(self): class TestAQTDeviceIntegration: - """Integration tests of AQTDevice base class with PennyLane""" + """Integration tests of AQTDevice base class with PennyLane.""" @pytest.mark.parametrize("num_wires", [1, 3]) @pytest.mark.parametrize("shots", [1, 200]) def test_load_from_device_function(self, num_wires, shots): """Tests that the AQTDevice can be loaded from PennyLane `device` function.""" - dev = qml.device("aqt.sim", wires=num_wires, shots=shots, api_key=SOME_API_KEY) assert dev.num_wires == num_wires assert dev.shots.total_shots == shots - assert dev.analytic == False - assert dev.circuit == [] - assert dev.circuit_json == "" + assert dev.analytic is False + assert not dev.circuit + assert not dev.circuit_json assert dev.samples is None assert dev.BASE_HOSTNAME == BASE_HOSTNAME assert dev.HTTP_METHOD == HTTP_METHOD - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY def test_api_key_not_found_error(self, monkeypatch, tmpdir): """Tests that an error is thrown with the device is created without - a valid API token.""" - + a valid API token. + """ monkeypatch.setenv("AQT_TOKEN", "") monkeypatch.setenv("PENNYLANE_CONF", "") monkeypatch.setattr("os.curdir", tmpdir.join("folder_without_a_config_file")) monkeypatch.setattr( - "pennylane.default_config", qml.Configuration("config.toml") + "pennylane.default_config", + qml.Configuration("config.toml"), ) # force loading of config with pytest.raises(ValueError, match="No valid api key for AQT platform found"): - dev = qml.device("aqt.sim", 2) + qml.device("aqt.sim", 2) def test_device_gets_local_config(self, monkeypatch, tmpdir): """Tests that the device successfully reads a config from the local directory.""" - monkeypatch.setenv("PENNYLANE_CONF", "") monkeypatch.setenv("AQT_TOKEN", "") tmpdir.join("config.toml").write(test_config) monkeypatch.setattr("os.curdir", tmpdir) monkeypatch.setattr( - "pennylane.default_config", qml.Configuration("config.toml") + "pennylane.default_config", + qml.Configuration("config.toml"), ) # force loading of config dev = qml.device("aqt.sim", wires=2) assert dev.shots.total_shots == 99 - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY def test_device_gets_api_key_default_config_directory(self, monkeypatch, tmpdir): """Tests that the device gets an api key that is stored in the default - config directory.""" + config directory. + """ monkeypatch.setenv("AQT_TOKEN", "") monkeypatch.setenv("PENNYLANE_CONF", "") config_dir = tmpdir.mkdir("pennylane") # fake default config directory config_dir.join("config.toml").write(test_config) monkeypatch.setenv( - "XDG_CONFIG_HOME", os.path.expanduser(tmpdir) + "XDG_CONFIG_HOME", + os.path.expanduser(tmpdir), ) # HACK: only works on linux monkeypatch.setattr("os.curdir", tmpdir.join("folder_without_a_config_file")) @@ -485,12 +485,13 @@ def test_device_gets_api_key_default_config_directory(self, monkeypatch, tmpdir) dev = qml.device("aqt.sim", wires=2) - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY def test_device_gets_api_key_pennylane_conf_env_var(self, monkeypatch, tmpdir): """Tests that the device gets an api key via the PENNYLANE_CONF - environment variable.""" + environment variable. + """ monkeypatch.setenv("AQT_TOKEN", "") filepath = tmpdir.join("config.toml") @@ -499,31 +500,32 @@ def test_device_gets_api_key_pennylane_conf_env_var(self, monkeypatch, tmpdir): monkeypatch.setattr("os.curdir", tmpdir.join("folder_without_a_config_file")) monkeypatch.setattr( - "pennylane.default_config", qml.Configuration("config.toml") + "pennylane.default_config", + qml.Configuration("config.toml"), ) # force loading of config dev = qml.device("aqt.sim", wires=2) - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY def test_device_gets_api_key_aqt_token_env_var(self, monkeypatch): """Tests that the device gets an api key that is stored in AQT_TOKEN - environment variable.""" - + environment variable. + """ NEW_API_KEY = SOME_API_KEY + "XYZ987" monkeypatch.setenv("PENNYLANE_CONF", "") monkeypatch.setenv("AQT_TOKEN", SOME_API_KEY + "XYZ987") dev = qml.device("aqt.sim", wires=2) - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == NEW_API_KEY def test_executes_with_online_api(self, monkeypatch): """Tests that a PennyLane QNode successfully executes with a - mocked out online API.""" - + mocked out online API. + """ dev = qml.device("aqt.sim", wires=2, shots=10, api_key=SOME_API_KEY) @qml.qnode(dev) @@ -545,19 +547,19 @@ def json(self): if self.num_calls == 0: self.num_calls = 1 return self.mock_json1 - else: - return self.mock_json2 + return self.mock_json2 mock_response = MockResponse() + # ruff: noqa: ARG005 monkeypatch.setattr(requests, "put", lambda *args, **kwargs: mock_response) circuit(0.5, 1.2) assert dev.samples == MOCK_SAMPLES def test_analytic_error(self): - """Test that instantiating the device with `shots=None` results in an error""" + """Test that instantiating the device with `shots=None` results in an error.""" with pytest.raises(ValueError, match="does not support analytic"): - dev = qml.device("aqt.sim", wires=2, shots=None) + qml.device("aqt.sim", wires=2, shots=None) class TestAQTSimulatorDevices: @@ -565,38 +567,36 @@ class TestAQTSimulatorDevices: @pytest.mark.parametrize("num_wires", [1, 3]) @pytest.mark.parametrize("shots", [1, 100]) - def test_simulator_default_init(self, num_wires, shots): + def test_simulator_default_initialization(self, num_wires, shots): """Tests that the device is properly initialized.""" - dev = AQTSimulatorDevice(num_wires, shots, SOME_API_KEY) assert dev.num_wires == num_wires assert dev.shots == shots - assert dev.analytic == False - assert dev.circuit == [] - assert dev.circuit_json == "" + assert dev.analytic is False + assert not dev.circuit + assert not dev.circuit_json assert dev.samples is None assert dev.hostname == BASE_HOSTNAME + "/sim" assert dev.HTTP_METHOD == HTTP_METHOD - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY @pytest.mark.parametrize("num_wires", [1, 3]) @pytest.mark.parametrize("shots", [1, 100]) def test_simulator_default_init(self, num_wires, shots): """Tests that the device is properly initialized.""" - dev = AQTNoisySimulatorDevice(num_wires, shots, SOME_API_KEY) assert dev.num_wires == num_wires assert dev.shots == shots - assert dev.analytic == False - assert dev.circuit == [] - assert dev.circuit_json == "" + assert dev.analytic is False + assert not dev.circuit + assert not dev.circuit_json assert dev.samples is None assert dev.hostname == BASE_HOSTNAME + "/sim/noise-model-1" assert dev.HTTP_METHOD == HTTP_METHOD - assert API_HEADER_KEY in dev.header.keys() + assert API_HEADER_KEY in dev.header assert dev.header[API_HEADER_KEY] == SOME_API_KEY @pytest.mark.skip("API key needs to be inputted")